@@ -6,7 +6,13 @@ import type {
66 LanguageServicePlugin ,
77 TextDocument ,
88} from '@volar/language-service' ;
9- import { hyphenateAttr , hyphenateTag , tsCodegen , type VueVirtualCode } from '@vue/language-core' ;
9+ import {
10+ forEachInterpolationNode ,
11+ hyphenateAttr ,
12+ hyphenateTag ,
13+ tsCodegen ,
14+ type VueVirtualCode ,
15+ } from '@vue/language-core' ;
1016import { camelize , capitalize } from '@vue/shared' ;
1117import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps' ;
1218import { create as createHtmlService } from 'volar-service-html' ;
@@ -97,6 +103,31 @@ export function create(
97103 create ( context ) {
98104 const baseServiceInstance = baseService . create ( context ) ;
99105
106+ if ( baseServiceInstance . provide [ 'html/languageService' ] ) {
107+ const htmlService : html . LanguageService = baseServiceInstance . provide [ 'html/languageService' ] ( ) ;
108+ const parseHTMLDocument = htmlService . parseHTMLDocument . bind ( htmlService ) ;
109+
110+ htmlService . parseHTMLDocument = document => {
111+ const info = resolveEmbeddedCode ( context , document . uri ) ;
112+ if ( info ?. code . id === 'template' ) {
113+ const templateAst = info . root . sfc . template ?. ast ;
114+ if ( templateAst ) {
115+ let text = document . getText ( ) ;
116+ for ( const node of forEachInterpolationNode ( templateAst ) ) {
117+ text = text . substring ( 0 , node . loc . start . offset )
118+ + ' ' . repeat ( node . loc . end . offset - node . loc . start . offset )
119+ + text . substring ( node . loc . end . offset ) ;
120+ }
121+ return parseHTMLDocument ( {
122+ ...document ,
123+ getText : ( ) => text ,
124+ } ) ;
125+ }
126+ }
127+ return parseHTMLDocument ( document ) ;
128+ } ;
129+ }
130+
100131 builtInData ??= loadTemplateData ( context . env . locale ?? 'en' ) ;
101132 modelData ??= loadModelModifiersData ( context . env . locale ?? 'en' ) ;
102133
@@ -314,22 +345,6 @@ export function create(
314345 }
315346 } ,
316347
317- async provideAutoInsertSnippet ( document , selection , lastChange , token ) {
318- if ( document . languageId !== languageId ) {
319- return ;
320- }
321- const info = resolveEmbeddedCode ( context , document . uri ) ;
322- if ( info ?. code . id !== 'template' ) {
323- return ;
324- }
325-
326- const snippet = await baseServiceInstance . provideAutoInsertSnippet ?.( document , selection , lastChange , token ) ;
327- if ( shouldSkipClosingTagFromInterpolation ( document , selection , lastChange , snippet ) ) {
328- return ;
329- }
330- return snippet ;
331- } ,
332-
333348 provideHover ( document , position , token ) {
334349 if ( document . languageId !== languageId ) {
335350 return ;
@@ -764,85 +779,3 @@ function getPropName(
764779 }
765780 return { isEvent, propName : name } ;
766781}
767-
768- function shouldSkipClosingTagFromInterpolation (
769- doc : TextDocument ,
770- selection : html . Position ,
771- lastChange : { text : string } | undefined ,
772- snippet : string | null | undefined ,
773- ) {
774- if ( ! snippet || ! lastChange || ( lastChange . text !== '/' && lastChange . text !== '>' ) ) {
775- return false ;
776- }
777- const tagName = / ^ \$ 0 < \/ ( [ ^ \s > / ] + ) > $ / . exec ( snippet ) ?. [ 1 ] ?? / ^ ( [ ^ \s > / ] + ) > $ / . exec ( snippet ) ?. [ 1 ] ;
778- if ( ! tagName ) {
779- return false ;
780- }
781-
782- // check if the open tag inside bracket
783- const textUpToSelection = doc . getText ( {
784- start : { line : 0 , character : 0 } ,
785- end : selection ,
786- } ) ;
787-
788- const lowerText = textUpToSelection . toLowerCase ( ) ;
789- const targetTag = `<${ tagName . toLowerCase ( ) } ` ;
790- let searchIndex = lowerText . lastIndexOf ( targetTag ) ;
791- let foundInsideInterpolation = false ;
792-
793- while ( searchIndex !== - 1 ) {
794- const nextChar = lowerText . charAt ( searchIndex + targetTag . length ) ;
795-
796- // if the next character continues the tag name, skip this occurrence
797- const isNameContinuation = nextChar && / [ 0 - 9 a - z : _ - ] / . test ( nextChar ) ;
798- if ( isNameContinuation ) {
799- searchIndex = lowerText . lastIndexOf ( targetTag , searchIndex - 1 ) ;
800- continue ;
801- }
802-
803- const tagPosition = doc . positionAt ( searchIndex ) ;
804- if ( ! isInsideBracketExpression ( doc , tagPosition ) ) {
805- return false ;
806- }
807-
808- foundInsideInterpolation = true ;
809- searchIndex = lowerText . lastIndexOf ( targetTag , searchIndex - 1 ) ;
810- }
811-
812- return foundInsideInterpolation ;
813- }
814-
815- function isInsideBracketExpression ( doc : TextDocument , selection : html . Position ) {
816- const text = doc . getText ( {
817- start : { line : 0 , character : 0 } ,
818- end : selection ,
819- } ) ;
820- const tokenMatcher = / < ! - - | - - > | { { | } } / g;
821- let match : RegExpExecArray | null ;
822- let inComment = false ;
823- let lastOpen = - 1 ;
824- let lastClose = - 1 ;
825-
826- while ( ( match = tokenMatcher . exec ( text ) ) !== null ) {
827- switch ( match [ 0 ] ) {
828- case '<!--' :
829- inComment = true ;
830- break ;
831- case '-->' :
832- inComment = false ;
833- break ;
834- case '{{' :
835- if ( ! inComment ) {
836- lastOpen = match . index ;
837- }
838- break ;
839- case '}}' :
840- if ( ! inComment ) {
841- lastClose = match . index ;
842- }
843- break ;
844- }
845- }
846-
847- return lastOpen !== - 1 && lastClose < lastOpen ;
848- }
0 commit comments