1
1
import type { Disposable , LanguageServiceContext , LanguageServicePluginInstance } from '@volar/language-service' ;
2
2
import { VueCompilerOptions , VueVirtualCode , hyphenateAttr , hyphenateTag , parseScriptSetupRanges , tsCodegen } from '@vue/language-core' ;
3
- import { camelize , capitalize , hyphenate } from '@vue/shared' ;
3
+ import { camelize , capitalize } from '@vue/shared' ;
4
4
import { getComponentSpans } from '@vue/typescript-plugin/lib/common' ;
5
5
import { create as createHtmlService } from 'volar-service-html' ;
6
6
import { create as createPugService } from 'volar-service-pug' ;
@@ -12,10 +12,16 @@ import { getNameCasing } from '../ideFeatures/nameCasing';
12
12
import { AttrNameCasing , LanguageServicePlugin , TagNameCasing } from '../types' ;
13
13
import { loadModelModifiersData , loadTemplateData } from './data' ;
14
14
15
- let builtInData : html . HTMLDataV1 ;
16
- let modelData : html . HTMLDataV1 ;
15
+ type InternalItemId =
16
+ | 'componentEvent'
17
+ | 'componentProp'
18
+ | 'specialTag' ;
17
19
18
20
const specialTags = new Set ( [ 'slot' , 'component' , 'template' ] ) ;
21
+ const specialProps = new Set ( [ 'class' , 'is' , 'key' , 'ref' , 'style' ] ) ;
22
+
23
+ let builtInData : html . HTMLDataV1 ;
24
+ let modelData : html . HTMLDataV1 ;
19
25
20
26
export function create (
21
27
mode : 'html' | 'pug' ,
@@ -522,7 +528,6 @@ export function create(
522
528
attrs,
523
529
props : props . filter ( prop =>
524
530
! prop . startsWith ( 'ref_' )
525
- && ! hyphenate ( prop ) . startsWith ( 'on-vnode-' )
526
531
) ,
527
532
events,
528
533
} ) ;
@@ -566,7 +571,9 @@ export function create(
566
571
const isGlobal = ! propsSet . has ( prop ) ;
567
572
const name = casing . attr === AttrNameCasing . Camel ? prop : hyphenateAttr ( prop ) ;
568
573
569
- if ( hyphenateAttr ( name ) . startsWith ( 'on-' ) ) {
574
+ const isEvent = hyphenateAttr ( name ) . startsWith ( 'on-' ) ;
575
+
576
+ if ( isEvent ) {
570
577
571
578
const propNameBase = name . startsWith ( 'on-' )
572
579
? name . slice ( 'on-' . length )
@@ -584,7 +591,7 @@ export function create(
584
591
}
585
592
) ;
586
593
}
587
- {
594
+ else {
588
595
589
596
const propName = name ;
590
597
const propKey = createInternalItemId ( 'componentProp' , [ isGlobal ? '*' : tag , propName ] ) ;
@@ -611,14 +618,16 @@ export function create(
611
618
const name = casing . attr === AttrNameCasing . Camel ? event : hyphenateAttr ( event ) ;
612
619
const propKey = createInternalItemId ( 'componentEvent' , [ tag , name ] ) ;
613
620
614
- attributes . push ( {
615
- name : 'v-on:' + name ,
616
- description : propKey ,
617
- } ) ;
618
- attributes . push ( {
619
- name : '@' + name ,
620
- description : propKey ,
621
- } ) ;
621
+ attributes . push (
622
+ {
623
+ name : 'v-on:' + name ,
624
+ description : propKey ,
625
+ } ,
626
+ {
627
+ name : '@' + name ,
628
+ description : propKey ,
629
+ }
630
+ ) ;
622
631
}
623
632
624
633
const models : [ boolean , string ] [ ] = [ ] ;
@@ -765,70 +774,116 @@ export function create(
765
774
}
766
775
767
776
const itemIdKey = typeof item . documentation === 'string' ? item . documentation : item . documentation ?. value ;
768
- const itemId = itemIdKey ? readInternalItemId ( itemIdKey ) : undefined ;
777
+ let itemId = itemIdKey ? readInternalItemId ( itemIdKey ) : undefined ;
769
778
770
779
if ( itemId ) {
771
- let label = hyphenate ( itemId . args [ 1 ] ) ;
772
- if ( label . startsWith ( 'on-' ) ) {
773
- label = 'on' + label . slice ( 'on-' . length ) ;
774
- }
775
- else if ( itemId . type === 'componentEvent' ) {
776
- label = 'on' + label ;
780
+ let [ isEvent , name ] = tryGetEventName ( itemId ) ;
781
+ if ( isEvent ) {
782
+ name = 'on' + name ;
777
783
}
778
- const original = originals . get ( label ) ;
784
+ const original = originals . get ( name ) ;
779
785
item . documentation = original ?. documentation ;
780
786
}
781
- else if ( ! originals . has ( item . label ) ) {
782
- originals . set ( item . label , item ) ;
787
+ else {
788
+ let name = item . label ;
789
+ const isVBind = name . startsWith ( 'v-bind:' ) ? (
790
+ name = name . slice ( 'v-bind:' . length ) , true
791
+ ) : false ;
792
+ const isVBindAbbr = name . startsWith ( ':' ) && name !== ':' ? (
793
+ name = name . slice ( ':' . length ) , true
794
+ ) : false ;
795
+
796
+ /**
797
+ * for `is`, `key` and `ref` starting with `v-bind:` or `:`
798
+ * that without `internalItemId`.
799
+ */
800
+ if ( isVBind || isVBindAbbr ) {
801
+ itemId = {
802
+ type : 'componentProp' ,
803
+ args : [ '^' , name ]
804
+ } ;
805
+ }
806
+ else if ( ! originals . has ( item . label ) ) {
807
+ originals . set ( item . label , item ) ;
808
+ }
783
809
}
784
810
811
+ const tokens : string [ ] = [ ] ;
812
+
785
813
if ( item . kind === 10 satisfies typeof vscode . CompletionItemKind . Property && lastCompletionComponentNames . has ( hyphenateTag ( item . label ) ) ) {
786
814
item . kind = 6 satisfies typeof vscode . CompletionItemKind . Variable ;
787
- item . sortText = '\u0000' + ( item . sortText ?? item . label ) ;
815
+ tokens . push ( '\u0000' ) ;
788
816
}
789
- else if ( itemId && ( itemId . type === 'componentProp' || itemId . type === 'componentEvent' ) ) {
817
+ else if ( itemId ) {
818
+
819
+ const isComponent = itemId . args [ 0 ] !== '*' ;
820
+ const [ isEvent , name ] = tryGetEventName ( itemId ) ;
790
821
791
- const [ componentName ] = itemId . args ;
822
+ if ( itemId . type === 'componentProp' ) {
823
+ if ( isComponent || specialProps . has ( name ) ) {
824
+ item . kind = 5 satisfies typeof vscode . CompletionItemKind . Field ;
825
+ }
826
+ }
827
+ else if ( isEvent ) {
828
+ item . kind = 23 satisfies typeof vscode . CompletionItemKind . Event ;
829
+ if ( name . startsWith ( 'vnode-' ) ) {
830
+ tokens . push ( '\u0004' ) ;
831
+ }
832
+ }
792
833
793
- if ( componentName !== '*' ) {
794
- if (
795
- item . label === 'class'
796
- || item . label === 'ref'
797
- || item . label . endsWith ( ':class' )
798
- || item . label . endsWith ( ':ref' )
799
- ) {
800
- item . sortText = '\u0000' + ( item . sortText ?? item . label ) ;
834
+ if (
835
+ isComponent
836
+ || ( isComponent && isEvent )
837
+ || specialProps . has ( name )
838
+ ) {
839
+ tokens . push ( '\u0000' ) ;
840
+
841
+ if ( item . label . startsWith ( ':' ) ) {
842
+ tokens . push ( '\u0001' ) ;
843
+ }
844
+ else if ( item . label . startsWith ( '@' ) ) {
845
+ tokens . push ( '\u0002' ) ;
846
+ }
847
+ else if ( item . label . startsWith ( 'v-bind:' ) ) {
848
+ tokens . push ( '\u0003' ) ;
849
+ }
850
+ else if ( item . label . startsWith ( 'v-on:' ) ) {
851
+ tokens . push ( '\u0004' ) ;
801
852
}
802
853
else {
803
- item . sortText = '\u0000\u0000' + ( item . sortText ?? item . label ) ;
854
+ tokens . push ( '\u0000' ) ;
804
855
}
805
- }
806
856
807
- if ( itemId . type === 'componentProp' ) {
808
- if ( componentName !== '*' ) {
809
- item . kind = 5 satisfies typeof vscode . CompletionItemKind . Field ;
857
+ if ( specialProps . has ( name ) ) {
858
+ tokens . push ( '\u0001' ) ;
859
+ }
860
+ else {
861
+ tokens . push ( '\u0000' ) ;
810
862
}
811
- }
812
- else {
813
- item . kind = componentName !== '*' ? 3 satisfies typeof vscode . CompletionItemKind . Function : 23 satisfies typeof vscode . CompletionItemKind . Event ;
814
863
}
815
864
}
865
+ else if ( specialProps . has ( item . label ) ) {
866
+ item . kind = 5 satisfies typeof vscode . CompletionItemKind . Field ;
867
+ tokens . push ( '\u0000' , '\u0000' , '\u0001' ) ;
868
+ }
816
869
else if (
817
870
item . label === 'v-if'
818
871
|| item . label === 'v-else-if'
819
872
|| item . label === 'v-else'
820
873
|| item . label === 'v-for'
821
874
) {
822
875
item . kind = 14 satisfies typeof vscode . CompletionItemKind . Keyword ;
823
- item . sortText = '\u0003' + ( item . sortText ?? item . label ) ;
876
+ tokens . push ( '\u0003' ) ;
824
877
}
825
878
else if ( item . label . startsWith ( 'v-' ) ) {
826
879
item . kind = 3 satisfies typeof vscode . CompletionItemKind . Function ;
827
- item . sortText = '\u0002' + ( item . sortText ?? item . label ) ;
880
+ tokens . push ( '\u0002' ) ;
828
881
}
829
882
else {
830
- item . sortText = '\u0001' + ( item . sortText ?? item . label ) ;
883
+ tokens . push ( '\u0001' ) ;
831
884
}
885
+
886
+ item . sortText = tokens . join ( '' ) + ( item . sortText ?? item . label ) ;
832
887
}
833
888
834
889
updateExtraCustomData ( [ ] ) ;
@@ -888,7 +943,7 @@ export function create(
888
943
}
889
944
} ;
890
945
891
- function createInternalItemId ( type : 'componentEvent' | 'componentProp' | 'specialTag' , args : string [ ] ) {
946
+ function createInternalItemId ( type : InternalItemId , args : string [ ] ) {
892
947
return '__VLS_::' + type + '::' + args . join ( ',' ) ;
893
948
}
894
949
@@ -900,7 +955,7 @@ function readInternalItemId(key: string) {
900
955
if ( isInternalItemId ( key ) ) {
901
956
const strs = key . split ( '::' ) ;
902
957
return {
903
- type : strs [ 1 ] as 'componentEvent' | 'componentProp' | 'specialTag' ,
958
+ type : strs [ 1 ] as InternalItemId ,
904
959
args : strs [ 2 ] . split ( ',' ) ,
905
960
} ;
906
961
}
@@ -917,3 +972,16 @@ function getReplacement(list: html.CompletionList, doc: TextDocument) {
917
972
}
918
973
}
919
974
}
975
+
976
+ function tryGetEventName (
977
+ itemId : ReturnType < typeof readInternalItemId > & { }
978
+ ) : [ isEvent : boolean , name : string ] {
979
+ const name = hyphenateAttr ( itemId . args [ 1 ] ) ;
980
+ if ( name . startsWith ( 'on-' ) ) {
981
+ return [ true , name . slice ( 'on-' . length ) ] ;
982
+ }
983
+ else if ( itemId . type === 'componentEvent' ) {
984
+ return [ true , name ] ;
985
+ }
986
+ return [ false , name ] ;
987
+ }
0 commit comments