@@ -19,8 +19,7 @@ import { loadModelModifiersData, loadTemplateData } from './data';
19
19
20
20
type InternalItemId =
21
21
| 'componentEvent'
22
- | 'componentProp'
23
- | 'specialTag' ;
22
+ | 'componentProp' ;
24
23
25
24
const specialTags = new Set ( [
26
25
'slot' ,
@@ -174,24 +173,20 @@ export function create(
174
173
return ;
175
174
}
176
175
177
- // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver
178
- baseServiceInstance . provideCompletionItems ?.( document , position , completionContext , token ) ;
179
-
180
- let sync = ( await provideHtmlData ( sourceScript . id , root ) ) . sync ;
181
- let currentVersion : number | undefined ;
182
- let completionList : CompletionList | null | undefined ;
183
-
184
- while ( currentVersion !== ( currentVersion = await sync ?.( ) ) ) {
185
- completionList = await baseServiceInstance . provideCompletionItems ?.(
186
- document ,
187
- position ,
188
- completionContext ,
189
- token ,
190
- ) ;
191
- }
176
+ const completionList = await runWithVueData (
177
+ sourceScript . id ,
178
+ root ,
179
+ ( ) =>
180
+ baseServiceInstance . provideCompletionItems ! (
181
+ document ,
182
+ position ,
183
+ completionContext ,
184
+ token ,
185
+ ) ,
186
+ ) ;
192
187
193
188
if ( completionList ) {
194
- transformCompletionList ( completionList , document ) ;
189
+ postProcessCompletionList ( completionList , document ) ;
195
190
return completionList ;
196
191
}
197
192
} ,
@@ -209,30 +204,37 @@ export function create(
209
204
} ,
210
205
} ;
211
206
207
+ async function runWithVueData < T > ( sourceDocumentUri : URI , vueCode : VueVirtualCode , fn : ( ) => T ) {
208
+ // #4298: Precompute HTMLDocument before provideHtmlData to avoid parseHTMLDocument requesting component names from tsserver
209
+ await fn ( ) ;
210
+
211
+ const { sync } = await provideHtmlData ( sourceDocumentUri , vueCode ) ;
212
+ let lastVersion = await sync ( ) ;
213
+ let result = await fn ( ) ;
214
+ while ( lastVersion !== ( lastVersion = await sync ( ) ) ) {
215
+ result = await fn ( ) ;
216
+ }
217
+ return result ;
218
+ }
219
+
212
220
async function provideHtmlData ( sourceDocumentUri : URI , vueCode : VueVirtualCode ) {
213
221
await ( initializing ??= initialize ( ) ) ;
214
222
215
223
const casing = await checkCasing ( context , sourceDocumentUri ) ;
216
224
217
- if ( builtInData . tags ) {
218
- for ( const tag of builtInData . tags ) {
219
- if ( isItemKey ( tag . name ) ) {
220
- continue ;
221
- }
222
-
223
- if ( specialTags . has ( tag . name ) ) {
224
- tag . name = generateItemKey ( 'specialTag' , tag . name , '' ) ;
225
- }
226
- else if ( casing . tag === TagNameCasing . Kebab ) {
227
- tag . name = hyphenateTag ( tag . name ) ;
228
- }
229
- else {
230
- tag . name = camelize ( capitalize ( tag . name ) ) ;
231
- }
225
+ for ( const tag of builtInData . tags ?? [ ] ) {
226
+ if ( specialTags . has ( tag . name ) ) {
227
+ continue ;
228
+ }
229
+ if ( casing . tag === TagNameCasing . Kebab ) {
230
+ tag . name = hyphenateTag ( tag . name ) ;
231
+ }
232
+ else {
233
+ tag . name = camelize ( capitalize ( tag . name ) ) ;
232
234
}
233
235
}
234
236
235
- const promises : Promise < void > [ ] = [ ] ;
237
+ const tasks : Promise < void > [ ] = [ ] ;
236
238
const tagInfos = new Map < string , {
237
239
attrs : string [ ] ;
238
240
propInfos : ComponentPropInfo [ ] ;
@@ -252,7 +254,8 @@ export function create(
252
254
isApplicable : ( ) => true ,
253
255
provideTags : ( ) => {
254
256
if ( ! components ) {
255
- promises . push ( ( async ( ) => {
257
+ components = [ ] ;
258
+ tasks . push ( ( async ( ) => {
256
259
components = ( await tsPluginClient ?. getComponentNames ( vueCode . fileName ) ?? [ ] )
257
260
. filter ( name =>
258
261
name !== 'Transition'
@@ -264,7 +267,6 @@ export function create(
264
267
lastCompletionComponentNames = new Set ( components ) ;
265
268
version ++ ;
266
269
} ) ( ) ) ;
267
- return [ ] ;
268
270
}
269
271
const scriptSetupRanges = tsCodegen . get ( vueCode . sfc ) ?. getScriptSetupRanges ( ) ;
270
272
const names = new Set < string > ( ) ;
@@ -299,10 +301,16 @@ export function create(
299
301
return tags ;
300
302
} ,
301
303
provideAttributes : tag => {
302
- const tagInfo = tagInfos . get ( tag ) ;
303
-
304
+ let tagInfo = tagInfos . get ( tag ) ;
304
305
if ( ! tagInfo ) {
305
- promises . push ( ( async ( ) => {
306
+ tagInfo = {
307
+ attrs : [ ] ,
308
+ propInfos : [ ] ,
309
+ events : [ ] ,
310
+ directives : [ ] ,
311
+ } ;
312
+ tagInfos . set ( tag , tagInfo ) ;
313
+ tasks . push ( ( async ( ) => {
306
314
const attrs = await tsPluginClient ?. getElementAttrs ( vueCode . fileName , tag ) ?? [ ] ;
307
315
const propInfos = await tsPluginClient ?. getComponentProps ( vueCode . fileName , tag ) ?? [ ] ;
308
316
const events = await tsPluginClient ?. getComponentEvents ( vueCode . fileName , tag ) ?? [ ] ;
@@ -315,7 +323,6 @@ export function create(
315
323
} ) ;
316
324
version ++ ;
317
325
} ) ( ) ) ;
318
- return [ ] ;
319
326
}
320
327
321
328
const { attrs, propInfos, events, directives } = tagInfo ;
@@ -458,63 +465,26 @@ export function create(
458
465
459
466
return {
460
467
async sync ( ) {
461
- await Promise . all ( promises ) ;
468
+ await Promise . all ( tasks ) ;
462
469
return version ;
463
470
} ,
464
471
} ;
465
472
}
466
473
467
- function transformCompletionList ( completionList : CompletionList , document : TextDocument ) {
468
- addDirectiveModifiers ( ) ;
474
+ function postProcessCompletionList ( completionList : CompletionList , document : TextDocument ) {
475
+ addDirectiveModifiers ( completionList , document ) ;
469
476
470
- function addDirectiveModifiers ( ) {
471
- const replacement = getReplacement ( completionList , document ) ;
472
- if ( ! replacement ?. text . includes ( '.' ) ) {
473
- return ;
474
- }
477
+ const tagMap = new Map < string , html . CompletionItem > ( ) ;
475
478
476
- const [ text , ...modifiers ] = replacement . text . split ( '.' ) ;
477
- const isVOn = text . startsWith ( 'v-on:' ) || text . startsWith ( '@' ) && text . length > 1 ;
478
- const isVBind = text . startsWith ( 'v-bind:' ) || text . startsWith ( ':' ) && text . length > 1 ;
479
- const isVModel = text . startsWith ( 'v-model:' ) || text === 'v-model' ;
480
- const currentModifiers = isVOn
481
- ? vOnModifiers
482
- : isVBind
483
- ? vBindModifiers
484
- : isVModel
485
- ? vModelModifiers
486
- : undefined ;
487
-
488
- if ( ! currentModifiers ) {
489
- return ;
479
+ completionList . items = completionList . items . filter ( item => {
480
+ const key = item . kind + '_' + item . label ;
481
+ if ( ! tagMap . has ( key ) ) {
482
+ tagMap . set ( key , item ) ;
483
+ return true ;
490
484
}
491
-
492
- for ( const modifier in currentModifiers ) {
493
- if ( modifiers . includes ( modifier ) ) {
494
- continue ;
495
- }
496
-
497
- const description = currentModifiers [ modifier ] ;
498
- const insertText = text + modifiers . slice ( 0 , - 1 ) . map ( m => '.' + m ) . join ( '' ) + '.' + modifier ;
499
- const newItem : html . CompletionItem = {
500
- label : modifier ,
501
- filterText : insertText ,
502
- documentation : {
503
- kind : 'markdown' ,
504
- value : description ,
505
- } ,
506
- textEdit : {
507
- range : replacement . textEdit . range ,
508
- newText : insertText ,
509
- } ,
510
- kind : 20 satisfies typeof CompletionItemKind . EnumMember ,
511
- } ;
512
-
513
- completionList . items . push ( newItem ) ;
514
- }
515
- }
516
-
517
- completionList . items = completionList . items . filter ( item => ! specialTags . has ( parseLabel ( item . label ) . name ) ) ;
485
+ tagMap . get ( key ) ! . documentation = item . documentation ;
486
+ return false ;
487
+ } ) ;
518
488
519
489
const htmlDocumentations = new Map < string , string > ( ) ;
520
490
@@ -693,6 +663,53 @@ export function create(
693
663
updateExtraCustomData ( [ ] ) ;
694
664
}
695
665
666
+ function addDirectiveModifiers ( completionList : CompletionList , document : TextDocument ) {
667
+ const replacement = getReplacement ( completionList , document ) ;
668
+ if ( ! replacement ?. text . includes ( '.' ) ) {
669
+ return ;
670
+ }
671
+
672
+ const [ text , ...modifiers ] = replacement . text . split ( '.' ) ;
673
+ const isVOn = text . startsWith ( 'v-on:' ) || text . startsWith ( '@' ) && text . length > 1 ;
674
+ const isVBind = text . startsWith ( 'v-bind:' ) || text . startsWith ( ':' ) && text . length > 1 ;
675
+ const isVModel = text . startsWith ( 'v-model:' ) || text === 'v-model' ;
676
+ const currentModifiers = isVOn
677
+ ? vOnModifiers
678
+ : isVBind
679
+ ? vBindModifiers
680
+ : isVModel
681
+ ? vModelModifiers
682
+ : undefined ;
683
+
684
+ if ( ! currentModifiers ) {
685
+ return ;
686
+ }
687
+
688
+ for ( const modifier in currentModifiers ) {
689
+ if ( modifiers . includes ( modifier ) ) {
690
+ continue ;
691
+ }
692
+
693
+ const description = currentModifiers [ modifier ] ;
694
+ const insertText = text + modifiers . slice ( 0 , - 1 ) . map ( m => '.' + m ) . join ( '' ) + '.' + modifier ;
695
+ const newItem : html . CompletionItem = {
696
+ label : modifier ,
697
+ filterText : insertText ,
698
+ documentation : {
699
+ kind : 'markdown' ,
700
+ value : description ,
701
+ } ,
702
+ textEdit : {
703
+ range : replacement . textEdit . range ,
704
+ newText : insertText ,
705
+ } ,
706
+ kind : 20 satisfies typeof CompletionItemKind . EnumMember ,
707
+ } ;
708
+
709
+ completionList . items . push ( newItem ) ;
710
+ }
711
+ }
712
+
696
713
async function initialize ( ) {
697
714
customData = await getHtmlCustomData ( ) ;
698
715
}
0 commit comments