1- import type {
2- CompletionItemKind ,
3- CompletionItemTag ,
4- CompletionList ,
5- Disposable ,
6- LanguageServicePlugin ,
7- TextDocument ,
1+ import {
2+ type CompletionItemKind ,
3+ type CompletionItemTag ,
4+ type CompletionList ,
5+ type Disposable ,
6+ type LanguageServicePlugin ,
7+ type TextDocument ,
8+ transformCompletionItem ,
89} from '@volar/language-service' ;
10+ import { getSourceRange } from '@volar/language-service/lib/utils/featureWorkers' ;
911import {
1012 forEachInterpolationNode ,
1113 hyphenateAttr ,
@@ -17,6 +19,12 @@ import { camelize, capitalize } from '@vue/shared';
1719import type { ComponentPropInfo } from '@vue/typescript-plugin/lib/requests/getComponentProps' ;
1820import { create as createHtmlService , resolveReference } from 'volar-service-html' ;
1921import { create as createPugService } from 'volar-service-pug' ;
22+ import { getFormatCodeSettings } from 'volar-service-typescript/lib/configs/getFormatCodeSettings.js' ;
23+ import { getUserPreferences } from 'volar-service-typescript/lib/configs/getUserPreferences.js' ;
24+ import {
25+ applyCompletionEntryDetails ,
26+ convertCompletionInfo ,
27+ } from 'volar-service-typescript/lib/utils/lspConverters.js' ;
2028import * as html from 'vscode-html-languageservice' ;
2129import { URI , Utils } from 'vscode-uri' ;
2230import { loadModelModifiersData , loadTemplateData } from '../data' ;
@@ -51,6 +59,7 @@ let builtInData: html.HTMLDataV1 | undefined;
5159let modelData : html . HTMLDataV1 | undefined ;
5260
5361export function create (
62+ ts : typeof import ( 'typescript' ) ,
5463 languageId : 'html' | 'jade' ,
5564 {
5665 getComponentNames,
@@ -60,6 +69,8 @@ export function create(
6069 getComponentSlots,
6170 getElementAttrs,
6271 resolveModuleName,
72+ getAutoImportSuggestions,
73+ resolveAutoImportCompletionEntry,
6374 } : import ( '@vue/typescript-plugin/lib/requests' ) . Requests ,
6475) : LanguageServicePlugin {
6576 let customData : html . IHTMLDataProvider [ ] = [ ] ;
@@ -264,8 +275,11 @@ export function create(
264275 }
265276
266277 const disposable = context . env . onDidChangeConfiguration ?.( ( ) => initializing = undefined ) ;
278+ const transformedItems = new WeakSet < html . CompletionItem > ( ) ;
267279
268280 let initializing : Promise < void > | undefined ;
281+ let formattingOptions : html . FormattingOptions | undefined ;
282+ let lastCompletionDocument : TextDocument | undefined ;
269283
270284 return {
271285 ...baseServiceInstance ,
@@ -275,6 +289,16 @@ export function create(
275289 disposable ?. dispose ( ) ;
276290 } ,
277291
292+ provideDocumentFormattingEdits ( document , range , options , ...rest ) {
293+ formattingOptions = options ;
294+ return baseServiceInstance . provideDocumentFormattingEdits ?.( document , range , options , ...rest ) ;
295+ } ,
296+
297+ provideOnTypeFormattingEdits ( document , position , key , options , ...rest ) {
298+ formattingOptions = options ;
299+ return baseServiceInstance . provideOnTypeFormattingEdits ?.( document , position , key , options , ...rest ) ;
300+ } ,
301+
278302 async provideCompletionItems ( document , position , completionContext , token ) {
279303 if ( document . languageId !== languageId ) {
280304 return ;
@@ -285,9 +309,10 @@ export function create(
285309 }
286310
287311 const {
288- result : completionList ,
312+ result : htmlCompletion ,
289313 target,
290314 info : {
315+ tagNameCasing,
291316 components,
292317 propMap,
293318 } ,
@@ -303,24 +328,71 @@ export function create(
303328 ) ,
304329 ) ;
305330
306- if ( ! completionList ) {
331+ if ( ! htmlCompletion ) {
307332 return ;
308333 }
309334
335+ const autoImportPlaceholderIndex = htmlCompletion . items . findIndex ( item =>
336+ item . label === 'AutoImportsPlaceholder'
337+ ) ;
338+ if ( autoImportPlaceholderIndex !== - 1 ) {
339+ const offset = document . offsetAt ( position ) ;
340+ const map = context . language . maps . get ( info . code , info . script ) ;
341+ let spliced = false ;
342+ for ( const [ sourceOffset ] of map . toSourceLocation ( offset ) ) {
343+ const [ formatOptions , preferences ] = await Promise . all ( [
344+ getFormatCodeSettings ( context , document , formattingOptions ) ,
345+ getUserPreferences ( context , document ) ,
346+ ] ) ;
347+ const autoImport = await getAutoImportSuggestions (
348+ info . root . fileName ,
349+ sourceOffset ,
350+ preferences ,
351+ formatOptions ,
352+ ) ;
353+ if ( ! autoImport ) {
354+ continue ;
355+ }
356+ const tsCompletion = convertCompletionInfo ( ts , autoImport , document , position , entry => entry . data ) ;
357+ const placeholder = htmlCompletion . items [ autoImportPlaceholderIndex ] ! ;
358+ for ( const tsItem of tsCompletion . items ) {
359+ if ( placeholder . textEdit ) {
360+ const newText = tsItem . textEdit ?. newText ?? tsItem . label ;
361+ tsItem . textEdit = {
362+ ...placeholder . textEdit ,
363+ newText : tagNameCasing === TagNameCasing . Kebab
364+ ? hyphenateTag ( newText )
365+ : newText ,
366+ } ;
367+ }
368+ else {
369+ tsItem . textEdit = undefined ;
370+ }
371+ }
372+ htmlCompletion . items . splice ( autoImportPlaceholderIndex , 1 , ...tsCompletion . items ) ;
373+ spliced = true ;
374+ lastCompletionDocument = document ;
375+ break ;
376+ }
377+ if ( ! spliced ) {
378+ htmlCompletion . items . splice ( autoImportPlaceholderIndex , 1 ) ;
379+ }
380+ }
381+
310382 switch ( target ) {
311383 case 'tag' : {
312- completionList . items . forEach ( transformTag ) ;
384+ htmlCompletion . items . forEach ( transformTag ) ;
313385 break ;
314386 }
315387 case 'attribute' : {
316- addDirectiveModifiers ( completionList , document ) ;
317- completionList . items . forEach ( transformAttribute ) ;
388+ addDirectiveModifiers ( htmlCompletion , document ) ;
389+ htmlCompletion . items . forEach ( transformAttribute ) ;
318390 break ;
319391 }
320392 }
321393
322394 updateExtraCustomData ( [ ] ) ;
323- return completionList ;
395+ return htmlCompletion ;
324396
325397 function transformTag ( item : html . CompletionItem ) {
326398 const tagName = capitalize ( camelize ( item . label ) ) ;
@@ -432,6 +504,61 @@ export function create(
432504 }
433505 } ,
434506
507+ async resolveCompletionItem ( item ) {
508+ if ( item . data ?. __isAutoImport || item . data ?. __isComponentAutoImport ) {
509+ const embeddedUri = URI . parse ( lastCompletionDocument ! . uri ) ;
510+ const decoded = context . decodeEmbeddedDocumentUri ( embeddedUri ) ;
511+ if ( ! decoded ) {
512+ return item ;
513+ }
514+ const sourceScript = context . language . scripts . get ( decoded [ 0 ] ) ;
515+ if ( ! sourceScript ) {
516+ return item ;
517+ }
518+ const [ formatOptions , preferences ] = await Promise . all ( [
519+ getFormatCodeSettings ( context , lastCompletionDocument ! , formattingOptions ) ,
520+ getUserPreferences ( context , lastCompletionDocument ! ) ,
521+ ] ) ;
522+ const details = await resolveAutoImportCompletionEntry ( item . data , preferences , formatOptions ) ;
523+ if ( details ) {
524+ const virtualCode = sourceScript . generated ! . embeddedCodes . get ( decoded [ 1 ] ) ! ;
525+ const sourceDocument = context . documents . get (
526+ sourceScript . id ,
527+ sourceScript . languageId ,
528+ sourceScript . snapshot ,
529+ ) ;
530+ const embeddedDocument = context . documents . get ( embeddedUri , virtualCode . languageId , virtualCode . snapshot ) ;
531+ const map = context . language . maps . get ( virtualCode , sourceScript ) ;
532+ item = transformCompletionItem (
533+ item ,
534+ embeddedRange =>
535+ getSourceRange (
536+ [ sourceDocument , embeddedDocument , map ] ,
537+ embeddedRange ,
538+ ) ,
539+ embeddedDocument ,
540+ context ,
541+ ) ;
542+ applyCompletionEntryDetails (
543+ ts ,
544+ item ,
545+ details ,
546+ sourceDocument ,
547+ fileName => URI . file ( fileName ) ,
548+ ( ) => undefined ,
549+ ) ;
550+ transformedItems . add ( item ) ;
551+ }
552+ }
553+ return item ;
554+ } ,
555+
556+ transformCompletionItem ( item ) {
557+ if ( transformedItems . has ( item ) ) {
558+ return item ;
559+ }
560+ } ,
561+
435562 provideHover ( document , position , token ) {
436563 if ( document . languageId !== languageId ) {
437564 return ;
@@ -761,6 +888,19 @@ export function create(
761888 } ) ) ;
762889 } ,
763890 } ,
891+ {
892+ getId : ( ) => 'vue-auto-imports' ,
893+ isApplicable : ( ) => true ,
894+ provideTags ( ) {
895+ return [ { name : 'AutoImportsPlaceholder' , attributes : [ ] } ] ;
896+ } ,
897+ provideAttributes ( ) {
898+ return [ ] ;
899+ } ,
900+ provideValues ( ) {
901+ return [ ] ;
902+ } ,
903+ } ,
764904 ] ) ;
765905
766906 return {
@@ -770,6 +910,7 @@ export function create(
770910 version,
771911 target,
772912 info : {
913+ tagNameCasing,
773914 components,
774915 propMap,
775916 } ,
0 commit comments