@@ -41,9 +41,9 @@ export class CompletionHandler {
41
41
CompletionItem ,
42
42
any
43
43
> = new RequestType ( CompletionHandler . completionResolveEndpoint ) ;
44
- // TODO: do we always need empty result members defined? Can we declare type on handler function and return null?
45
- // Also, none of the empty repsonses are declared as static readonly in other handles - should they be?
46
- private static readonly emptyCompletionList : CompletionList = < CompletionList > { } ;
44
+ private static readonly emptyCompletionList : CompletionList = < CompletionList > {
45
+ items : new Array ( 0 ) ,
46
+ } ;
47
47
private static readonly emptyCompletionItem : CompletionItem = < CompletionItem > { } ;
48
48
49
49
constructor (
@@ -94,11 +94,14 @@ export class CompletionHandler {
94
94
} else if ( delegatedCompletionParams . projectedKind === LanguageKind . CSharp ) {
95
95
virtualDocument = razorDocument . csharpDocument ;
96
96
} else {
97
- // TODO: equivalent of Debug.Fail?
97
+ this . logger . logWarning ( `Unknown language kind value ${ delegatedCompletionParams . projectedKind } ` ) ;
98
98
return CompletionHandler . emptyCompletionList ;
99
99
}
100
100
101
- // TODO: Should we check for null or undefined virtual document like we do in C# code?
101
+ if ( ! virtualDocument ) {
102
+ this . logger . logWarning ( `Null or undefined virtual document: ${ virtualDocument } ` ) ;
103
+ return CompletionHandler . emptyCompletionList ;
104
+ }
102
105
103
106
const synchronized = await this . documentSynchronizer . trySynchronizeProjectedDocument (
104
107
textDocument ,
@@ -114,8 +117,6 @@ export class CompletionHandler {
114
117
115
118
delegatedCompletionParams . identifier . textDocumentIdentifier . uri = virtualDocumentUri ;
116
119
117
- // TODO: support for provisional edits
118
-
119
120
// "@" is not a valid trigger character for C# / HTML and therefore we need to translate
120
121
// it into a non-trigger invocation.
121
122
const modifiedTriggerCharacter =
@@ -139,51 +140,12 @@ export class CompletionHandler {
139
140
) ;
140
141
}
141
142
142
- // HTML completion
143
- const completions = await vscode . commands . executeCommand < vscode . CompletionList | vscode . CompletionItem [ ] > (
144
- 'vscode.executeCompletionItemProvider' ,
145
- UriConverter . deserialize ( virtualDocumentUri ) ,
143
+ // HTML completion - provided via vscode command
144
+ return this . provideVscodeCompletions (
145
+ virtualDocument . uri ,
146
146
delegatedCompletionParams . projectedPosition ,
147
147
modifiedTriggerCharacter
148
148
) ;
149
-
150
- const completionItems =
151
- completions instanceof Array
152
- ? completions // was vscode.CompletionItem[]
153
- : completions
154
- ? completions . items // was vscode.CompletionList
155
- : [ ] ;
156
-
157
- const convertedCompletionItems : CompletionItem [ ] = new Array ( completionItems . length ) ;
158
- for ( let i = 0 ; i < completionItems . length ; i ++ ) {
159
- const completionItem = completionItems [ i ] ;
160
- const convertedCompletionItem = < CompletionItem > {
161
- command : completionItem . command , // no conversion needed as fields match
162
- commitCharacters : completionItem . commitCharacters ,
163
- detail : completionItem . detail ,
164
- documentation : CompletionHandler . ToMarkupContent ( completionItem . documentation ) ,
165
- filterText : completionItem . filterText ,
166
- insertText : CompletionHandler . ToLspInsertText ( completionItem . insertText ) ,
167
- insertTextMode : CompletionHandler . ToInsertTextMode ( completionItem . keepWhitespace ) ,
168
- kind : completionItem . kind ,
169
- label : CompletionHandler . ToLspCompletionItemLabel ( completionItem . label ) ,
170
- preselect : completionItem . preselect ,
171
- sortText : completionItem . sortText ,
172
- textEdit : CompletionHandler . ToLspTextEdit (
173
- CompletionHandler . ToLspInsertText ( completionItem . insertText ) ,
174
- completionItem . range
175
- ) ,
176
- } ;
177
- convertedCompletionItems [ i ] = convertedCompletionItem ;
178
- }
179
-
180
- const isIncomplete = completions instanceof Array ? false : completions ? completions . isIncomplete : false ;
181
- const completionList = < CompletionList > {
182
- isIncomplete : isIncomplete ,
183
- items : convertedCompletionItems ,
184
- } ;
185
-
186
- return completionList ;
187
149
} catch ( error ) {
188
150
this . logger . logWarning ( `${ CompletionHandler . completionEndpoint } failed with ${ error } ` ) ;
189
151
}
@@ -215,33 +177,6 @@ export class CompletionHandler {
215
177
return CompletionHandler . emptyCompletionItem ;
216
178
}
217
179
218
- private static AdjustCSharpCompletionList ( completionList : CompletionList , triggerCharacter : string | undefined ) {
219
- const data = completionList . itemDefaults ?. data ;
220
- for ( const completionItem of completionList . items ) {
221
- // textEdit is deprecated in favor of .range. Clear out its value to avoid any unexpected behavior.
222
- completionItem . textEdit = undefined ;
223
-
224
- if ( triggerCharacter === '@' && completionItem . commitCharacters ) {
225
- // We remove `{`, '(', and '*' from the commit characters to prevent auto-completing the first
226
- // completion item with a curly brace when a user intended to type `@{}` or `@()`.
227
- completionItem . commitCharacters = completionItem . commitCharacters . filter (
228
- ( commitChar ) => commitChar !== '{' && commitChar !== '(' && commitChar !== '*'
229
- ) ;
230
- }
231
-
232
- // for intellicode items, manually set the insertText to avoid including stars in the commit
233
- if ( completionItem . label . toString ( ) . includes ( '\u2605' ) ) {
234
- if ( completionItem . textEditText ) {
235
- completionItem . insertText = completionItem . textEditText ;
236
- }
237
- }
238
-
239
- if ( ! completionItem . data ) {
240
- completionItem . data = data ;
241
- }
242
- }
243
- }
244
-
245
180
private async provideCSharpCompletions (
246
181
triggerKind : CompletionTriggerKind ,
247
182
triggerCharacter : string | undefined ,
@@ -274,10 +209,7 @@ export class CompletionHandler {
274
209
const newDocument = await vscode . workspace . openTextDocument ( virtualDocument . uri ) ;
275
210
await newDocument . save ( ) ;
276
211
277
- params . position = < Position > {
278
- line : projectedPosition . line ,
279
- character : projectedPosition . character + 1 ,
280
- } ;
212
+ return this . provideVscodeCompletions ( virtualDocument . uri , projectedPosition , triggerCharacter ) ;
281
213
}
282
214
const csharpCompletions = await vscode . commands . executeCommand < CompletionList > (
283
215
provideCompletionsCommand ,
@@ -287,7 +219,36 @@ export class CompletionHandler {
287
219
return csharpCompletions ;
288
220
}
289
221
290
- private static getIndexOfPosition ( document : IProjectedDocument , position : Position ) {
222
+ private static AdjustCSharpCompletionList ( completionList : CompletionList , triggerCharacter : string | undefined ) {
223
+ const data = completionList . itemDefaults ?. data ;
224
+ for ( const completionItem of completionList . items ) {
225
+ // textEdit is deprecated in favor of .range. Clear out its value to avoid any unexpected behavior.
226
+ completionItem . textEdit = undefined ;
227
+
228
+ if ( triggerCharacter === '@' && completionItem . commitCharacters ) {
229
+ // We remove `{`, '(', and '*' from the commit characters to prevent auto-completing the first
230
+ // completion item with a curly brace when a user intended to type `@{}` or `@()`.
231
+ completionItem . commitCharacters = completionItem . commitCharacters . filter (
232
+ ( commitChar ) => commitChar !== '{' && commitChar !== '(' && commitChar !== '*'
233
+ ) ;
234
+ }
235
+
236
+ // for intellicode items, manually set the insertText to avoid including stars in the commit
237
+ if ( completionItem . label . toString ( ) . includes ( '\u2605' ) ) {
238
+ if ( completionItem . textEditText ) {
239
+ completionItem . insertText = completionItem . textEditText ;
240
+ }
241
+ }
242
+
243
+ // copy default item data to each item or else completion item resolve will fail later
244
+ if ( ! completionItem . data ) {
245
+ completionItem . data = data ;
246
+ }
247
+ }
248
+ }
249
+
250
+ // convert (line, character) Position to absolute index number
251
+ private static getIndexOfPosition ( document : IProjectedDocument , position : Position ) : number {
291
252
const content : string = document . getContent ( ) ;
292
253
let lineNumber = 0 ;
293
254
let index = 0 ;
@@ -314,8 +275,59 @@ export class CompletionHandler {
314
275
return positionIndex ;
315
276
}
316
277
278
+ private async provideVscodeCompletions (
279
+ virtualDocumentUri : vscode . Uri ,
280
+ projectedPosition : Position ,
281
+ triggerCharacter : string | undefined
282
+ ) {
283
+ const completions = await vscode . commands . executeCommand < vscode . CompletionList | vscode . CompletionItem [ ] > (
284
+ 'vscode.executeCompletionItemProvider' ,
285
+ virtualDocumentUri ,
286
+ projectedPosition ,
287
+ triggerCharacter
288
+ ) ;
289
+
290
+ const completionItems =
291
+ completions instanceof Array
292
+ ? completions // was vscode.CompletionItem[]
293
+ : completions
294
+ ? completions . items // was vscode.CompletionList
295
+ : [ ] ;
296
+
297
+ const convertedCompletionItems : CompletionItem [ ] = new Array ( completionItems . length ) ;
298
+ for ( let i = 0 ; i < completionItems . length ; i ++ ) {
299
+ const completionItem = completionItems [ i ] ;
300
+ const convertedCompletionItem = < CompletionItem > {
301
+ command : completionItem . command , // no conversion needed as fields match
302
+ commitCharacters : completionItem . commitCharacters ,
303
+ detail : completionItem . detail ,
304
+ documentation : CompletionHandler . toMarkupContent ( completionItem . documentation ) ,
305
+ filterText : completionItem . filterText ,
306
+ insertText : CompletionHandler . toLspInsertText ( completionItem . insertText ) ,
307
+ insertTextMode : CompletionHandler . toInsertTextMode ( completionItem . keepWhitespace ) ,
308
+ kind : completionItem . kind ? completionItem . kind + 1 : completionItem . kind , // VSCode and LSP are off by one
309
+ label : CompletionHandler . toLspCompletionItemLabel ( completionItem . label ) ,
310
+ preselect : completionItem . preselect ,
311
+ sortText : completionItem . sortText ,
312
+ textEdit : CompletionHandler . toLspTextEdit (
313
+ CompletionHandler . toLspInsertText ( completionItem . insertText ) ,
314
+ completionItem . range
315
+ ) ,
316
+ } ;
317
+ convertedCompletionItems [ i ] = convertedCompletionItem ;
318
+ }
319
+
320
+ const isIncomplete = completions instanceof Array ? false : completions ? completions . isIncomplete : false ;
321
+ const completionList = < CompletionList > {
322
+ isIncomplete : isIncomplete ,
323
+ items : convertedCompletionItems ,
324
+ } ;
325
+
326
+ return completionList ;
327
+ }
328
+
317
329
// converts completion item documentation from vscode format to LSP format
318
- private static ToMarkupContent ( documentation ?: string | vscode . MarkdownString ) : string | MarkupContent | undefined {
330
+ private static toMarkupContent ( documentation ?: string | vscode . MarkdownString ) : string | MarkupContent | undefined {
319
331
const markdownString = documentation as vscode . MarkdownString ;
320
332
if ( ! markdownString ?. value ) {
321
333
return < string | undefined > documentation ;
@@ -328,17 +340,17 @@ export class CompletionHandler {
328
340
return markupContent ;
329
341
}
330
342
331
- private static ToLspCompletionItemLabel ( label : string | vscode . CompletionItemLabel ) : string {
343
+ private static toLspCompletionItemLabel ( label : string | vscode . CompletionItemLabel ) : string {
332
344
const completionItemLabel = label as vscode . CompletionItemLabel ;
333
345
return completionItemLabel ?. label ?? < string > label ;
334
346
}
335
347
336
- private static ToLspInsertText ( insertText ?: string | vscode . SnippetString ) : string | undefined {
348
+ private static toLspInsertText ( insertText ?: string | vscode . SnippetString ) : string | undefined {
337
349
const snippetString = insertText as vscode . SnippetString ;
338
350
return snippetString ?. value ?? < string | undefined > insertText ;
339
351
}
340
352
341
- private static ToLspTextEdit (
353
+ private static toLspTextEdit (
342
354
newText ?: string ,
343
355
range ?: vscode . Range | { inserting : vscode . Range ; replacing : vscode . Range }
344
356
) : TextEdit | InsertReplaceEdit | undefined {
@@ -360,7 +372,7 @@ export class CompletionHandler {
360
372
if ( ! ( insertingRange || replacingRange ) ) {
361
373
const textEdit : TextEdit = {
362
374
newText : newText ,
363
- range : this . ToLspRange ( < vscode . Range > range ) ,
375
+ range : this . toLspRange ( < vscode . Range > range ) ,
364
376
} ;
365
377
366
378
return textEdit ;
@@ -372,23 +384,23 @@ export class CompletionHandler {
372
384
}
373
385
const insertReplaceEdit : InsertReplaceEdit = {
374
386
newText : newText ,
375
- insert : CompletionHandler . ToLspRange ( insertingRange ) ,
376
- replace : CompletionHandler . ToLspRange ( replacingRange ) ,
387
+ insert : CompletionHandler . toLspRange ( insertingRange ) ,
388
+ replace : CompletionHandler . toLspRange ( replacingRange ) ,
377
389
} ;
378
390
379
391
return insertReplaceEdit ;
380
392
}
381
393
382
- private static ToLspRange ( range : vscode . Range ) : Range {
394
+ private static toLspRange ( range : vscode . Range ) : Range {
383
395
const lspRange : Range = {
384
- start : CompletionHandler . ToLspPosition ( range . start ) ,
385
- end : CompletionHandler . ToLspPosition ( range . end ) ,
396
+ start : CompletionHandler . toLspPosition ( range . start ) ,
397
+ end : CompletionHandler . toLspPosition ( range . end ) ,
386
398
} ;
387
399
388
400
return lspRange ;
389
401
}
390
402
391
- private static ToLspPosition ( position : vscode . Position ) : Position {
403
+ private static toLspPosition ( position : vscode . Position ) : Position {
392
404
const lspPosition : Position = {
393
405
line : position . line ,
394
406
character : position . character ,
@@ -397,7 +409,7 @@ export class CompletionHandler {
397
409
return lspPosition ;
398
410
}
399
411
400
- private static ToInsertTextMode ( keepWhitespace ?: boolean ) : InsertTextMode | undefined {
412
+ private static toInsertTextMode ( keepWhitespace ?: boolean ) : InsertTextMode | undefined {
401
413
if ( keepWhitespace === undefined ) {
402
414
return undefined ;
403
415
}
0 commit comments