@@ -11,20 +11,138 @@ import {
11
11
} from './utils' ;
12
12
import type { CamelCaseValues } from './utils' ;
13
13
14
- // check if current character or last character is .
15
- function isTrigger ( line : string , position : Position ) : boolean {
14
+ export const COMPLETION_TRIGGERS = [ '.' , '[' , '"' , "'" ] ;
15
+
16
+ type FieldOptions = {
17
+ wrappingBracket : boolean ;
18
+ startsWithQuote : boolean ;
19
+ endsWithQuote : boolean ;
20
+ } ;
21
+
22
+ /**
23
+ * check if current character or last character is any of the completion triggers (i.e. `.`, `[`) and return it
24
+ *
25
+ * @see COMPLETION_TRIGGERS
26
+ */
27
+ function findTrigger ( line : string , position : Position ) : string | undefined {
16
28
const i = position . character - 1 ;
17
- return line [ i ] === '.' || ( i > 1 && line [ i - 1 ] === '.' ) ;
29
+
30
+ for ( const trigger of COMPLETION_TRIGGERS ) {
31
+ if ( line [ i ] === trigger ) {
32
+ return trigger ;
33
+ }
34
+ if ( i > 1 && line [ i - 1 ] === trigger ) {
35
+ return trigger ;
36
+ }
37
+ }
38
+
39
+ return undefined ;
18
40
}
19
41
20
- function getWords ( line : string , position : Position ) : string {
42
+ /**
43
+ * Given the line, position and trigger, returns the identifier referencing the styles spreadsheet and the (partial) field selected with options to help construct the completion item later.
44
+ *
45
+ */
46
+ function getWords (
47
+ line : string ,
48
+ position : Position ,
49
+ trigger : string ,
50
+ ) : [ string , string , FieldOptions ?] | undefined {
21
51
const text = line . slice ( 0 , position . character ) ;
22
- const index = text . search ( / [ a - z 0 - 9 \. _ ] * $ / i) ;
52
+ const index = text . search ( / [ a - z 0 - 9 \. _ \[ \] ' " \- ] * $ / i) ;
23
53
if ( index === - 1 ) {
24
- return '' ;
54
+ return undefined ;
55
+ }
56
+
57
+ const words = text . slice ( index ) ;
58
+
59
+ if ( words === '' || words . indexOf ( trigger ) === - 1 ) {
60
+ return undefined ;
61
+ }
62
+
63
+ switch ( trigger ) {
64
+ // process `.` trigger
65
+ case '.' :
66
+ return words . split ( '.' ) as [ string , string ] ;
67
+ // process `[` trigger
68
+ case '[' : {
69
+ const [ obj , field ] = words . split ( '[' ) ;
70
+
71
+ let lineAhead = line . slice ( position . character ) ;
72
+ const endsWithQuote = lineAhead . search ( / ^ [ " ' ] / ) !== - 1 ;
73
+
74
+ lineAhead = endsWithQuote ? lineAhead . slice ( 1 ) : lineAhead ;
75
+ const wrappingBracket = lineAhead . search ( / ^ \s * \] / ) !== - 1 ;
76
+
77
+ const startsWithQuote =
78
+ field . length > 0 && ( field [ 0 ] === '"' || field [ 0 ] === "'" ) ;
79
+
80
+ return [
81
+ obj ,
82
+ field . slice ( startsWithQuote ? 1 : 0 ) ,
83
+ { wrappingBracket, startsWithQuote, endsWithQuote} ,
84
+ ] ;
85
+ }
86
+ default : {
87
+ throw new Error ( `Unsupported trigger character ${ trigger } ` ) ;
88
+ }
25
89
}
90
+ }
26
91
27
- return text . slice ( index ) ;
92
+ function createCompletionItem (
93
+ trigger : string ,
94
+ name : string ,
95
+ position : Position ,
96
+ fieldOptions : FieldOptions | undefined ,
97
+ ) : CompletionItem {
98
+ const nameIncludesDashes = name . includes ( '-' ) ;
99
+ const completionField =
100
+ trigger === '[' || nameIncludesDashes ? `['${ name } ']` : name ;
101
+
102
+ let completionItem : CompletionItem ;
103
+ // in case of items with dashes, we need to replace the `.` and suggest the field using the subscript expression `[`
104
+ if ( trigger === '.' ) {
105
+ if ( nameIncludesDashes ) {
106
+ const range = lsp . Range . create (
107
+ lsp . Position . create ( position . line , position . character - 1 ) , // replace the `.` character
108
+ position ,
109
+ ) ;
110
+
111
+ completionItem = CompletionItem . create ( completionField ) ;
112
+ completionItem . textEdit = lsp . InsertReplaceEdit . create (
113
+ completionField ,
114
+ range ,
115
+ range ,
116
+ ) ;
117
+ } else {
118
+ completionItem = CompletionItem . create ( completionField ) ;
119
+ }
120
+ } else {
121
+ // trigger === '['
122
+ const startPositionCharacter =
123
+ position . character -
124
+ 1 - // replace the `[` character
125
+ ( fieldOptions ?. startsWithQuote ? 1 : 0 ) ; // replace the starting quote if present
126
+
127
+ const endPositionCharacter =
128
+ position . character +
129
+ ( fieldOptions ?. endsWithQuote ? 1 : 0 ) + // replace the ending quote if present
130
+ ( fieldOptions ?. wrappingBracket ? 1 : 0 ) ; // replace the wrapping bracket if present
131
+
132
+ const range = lsp . Range . create (
133
+ lsp . Position . create ( position . line , startPositionCharacter ) ,
134
+ lsp . Position . create ( position . line , endPositionCharacter ) ,
135
+ ) ;
136
+
137
+ completionItem = CompletionItem . create ( completionField ) ;
138
+ completionItem . textEdit = lsp . InsertReplaceEdit . create (
139
+ completionField ,
140
+ range ,
141
+ range ,
142
+ ) ;
143
+ }
144
+
145
+ return completionItem ;
28
146
}
29
147
30
148
export class CSSModulesCompletionProvider {
@@ -57,17 +175,17 @@ export class CSSModulesCompletionProvider {
57
175
if ( typeof currentLine !== 'string' ) return null ;
58
176
const currentDir = getCurrentDirFromUri ( textdocument . uri ) ;
59
177
60
- if ( ! isTrigger ( currentLine , position ) ) {
178
+ const trigger = findTrigger ( currentLine , position ) ;
179
+ if ( ! trigger ) {
61
180
return [ ] ;
62
181
}
63
182
64
- const words = getWords ( currentLine , position ) ;
65
-
66
- if ( words === '' || words . indexOf ( '.' ) === - 1 ) {
183
+ const foundFields = getWords ( currentLine , position , trigger ) ;
184
+ if ( ! foundFields ) {
67
185
return [ ] ;
68
186
}
69
187
70
- const [ obj , field ] = words . split ( '.' ) ;
188
+ const [ obj , field , fieldOptions ] = foundFields ;
71
189
72
190
const importPath = findImportPath ( fileContent , obj , currentDir ) ;
73
191
if ( importPath === '' ) {
@@ -83,25 +201,13 @@ export class CSSModulesCompletionProvider {
83
201
const res = classNames . map ( _class => {
84
202
const name = this . _classTransformer ( _class ) ;
85
203
86
- let completionItem : CompletionItem ;
87
-
88
- // in case of items with dashes, we need to replace the `.` and suggest the field using the subscript expression
89
- if ( name . includes ( '-' ) ) {
90
- const arrayAccessor = `['${ name } ']` ;
91
- const range = lsp . Range . create (
92
- lsp . Position . create ( position . line , position . character - 1 ) ,
93
- position ,
94
- ) ;
95
-
96
- completionItem = CompletionItem . create ( arrayAccessor ) ;
97
- completionItem . textEdit = lsp . InsertReplaceEdit . create (
98
- arrayAccessor ,
99
- range ,
100
- range ,
101
- ) ;
102
- } else {
103
- completionItem = CompletionItem . create ( name ) ;
104
- }
204
+ const completionItem = createCompletionItem (
205
+ trigger ,
206
+ name ,
207
+ position ,
208
+ fieldOptions ,
209
+ ) ;
210
+
105
211
return completionItem ;
106
212
} ) ;
107
213
0 commit comments