@@ -197,29 +197,75 @@ export class CompletionHandler {
197
197
} ;
198
198
199
199
if ( provisionalTextEdit ) {
200
- const absoluteIndex = CompletionHandler . getIndexOfPosition ( virtualDocument , projectedPosition ) ;
201
- if ( absoluteIndex === - 1 ) {
202
- return CompletionHandler . emptyCompletionList ;
203
- }
204
- virtualDocument . addProvisionalDotAt ( absoluteIndex ) ;
205
- this . projectedCSharpProvider . ensureDocumentContent ( virtualDocument . uri ) ;
206
-
207
- // We open and then re-save because we're adding content to the text document within an event.
208
- // We need to allow the system to propogate this text document change.
209
- const newDocument = await vscode . workspace . openTextDocument ( virtualDocument . uri ) ;
210
- await newDocument . save ( ) ;
211
-
212
- return this . provideVscodeCompletions ( virtualDocument . uri , projectedPosition , triggerCharacter ) ;
200
+ // provisional C# completion
201
+ return this . provideCSharpProvisionalCompletions (
202
+ triggerKind ,
203
+ triggerCharacter ,
204
+ virtualDocument ,
205
+ virtualDocumentUri ,
206
+ projectedPosition
207
+ ) ;
213
208
}
209
+
210
+ // non-provisional C# completion
214
211
const csharpCompletions = await vscode . commands . executeCommand < CompletionList > (
215
212
provideCompletionsCommand ,
216
213
params
217
214
) ;
218
- CompletionHandler . AdjustCSharpCompletionList ( csharpCompletions , triggerCharacter ) ;
215
+ CompletionHandler . adjustCSharpCompletionList ( csharpCompletions , triggerCharacter ) ;
219
216
return csharpCompletions ;
220
217
}
221
218
222
- private static AdjustCSharpCompletionList ( completionList : CompletionList , triggerCharacter : string | undefined ) {
219
+ // Provides 'provisional' C# completions.
220
+ // This happens when a user types '.' after an object. In that case '.' is initially in
221
+ // html document and not generated C# document. To get correct completions as soon as the user
222
+ // types '.' we need to
223
+ // 1. Temporarily add '.' to projected C# document at the correct position (projected position)
224
+ // 2. Make sure projected document is updated on the Roslyn server so Roslyn provides correct completions
225
+ // 3. Invoke Roslyn/C# completion and return that to the Razor LSP server.
226
+ // NOTE: currently there is an issue (see comments in code below) causing us to invoke vscode command
227
+ // rather then the Roslyn command
228
+ // 4. Remove temporarily (provisionally) added '.' from the projected C# buffer.
229
+ // 5. Make sure the projected C# document is updated since the user will likely continue interacting with this document.
230
+ private async provideCSharpProvisionalCompletions (
231
+ triggerKind : CompletionTriggerKind ,
232
+ triggerCharacter : string | undefined ,
233
+ virtualDocument : CSharpProjectedDocument ,
234
+ virtualDocumentUri : string ,
235
+ projectedPosition : Position
236
+ ) {
237
+ const absoluteIndex = CompletionHandler . getIndexOfPosition ( virtualDocument , projectedPosition ) ;
238
+ if ( absoluteIndex === - 1 ) {
239
+ return CompletionHandler . emptyCompletionList ;
240
+ }
241
+
242
+ try {
243
+ // temporarily add '.' to projected C# document to ensure correct completions are provided
244
+ virtualDocument . addProvisionalDotAt ( absoluteIndex ) ;
245
+ await this . ensureProjectedCSharpDocumentUpdated ( virtualDocument . uri ) ;
246
+
247
+ // Current code has to execute vscode command vscode.executeCompletionItemProvider for provisional completion
248
+ // Calling roslyn command vscode.executeCompletionItemProvider returns null
249
+ // Tracked by https://github.com/dotnet/vscode-csharp/issues/7250
250
+ return this . provideVscodeCompletions ( virtualDocument . uri , projectedPosition , triggerCharacter ) ;
251
+ } finally {
252
+ if ( virtualDocument . removeProvisionalDot ( ) ) {
253
+ await this . ensureProjectedCSharpDocumentUpdated ( virtualDocument . uri ) ;
254
+ }
255
+ }
256
+ }
257
+
258
+ private async ensureProjectedCSharpDocumentUpdated ( virtualDocumentUri : vscode . Uri ) {
259
+ this . projectedCSharpProvider . ensureDocumentContent ( virtualDocumentUri ) ;
260
+
261
+ // We open and then re-save because we're adding content to the text document within an event.
262
+ // We need to allow the system to propogate this text document change.
263
+ const newDocument = await vscode . workspace . openTextDocument ( virtualDocumentUri ) ;
264
+ await newDocument . save ( ) ;
265
+ }
266
+
267
+ // Adjust Roslyn completion command results to make it more palatable to VSCode
268
+ private static adjustCSharpCompletionList ( completionList : CompletionList , triggerCharacter : string | undefined ) {
223
269
const data = completionList . itemDefaults ?. data ;
224
270
for ( const completionItem of completionList . items ) {
225
271
// textEdit is deprecated in favor of .range. Clear out its value to avoid any unexpected behavior.
@@ -247,7 +293,7 @@ export class CompletionHandler {
247
293
}
248
294
}
249
295
250
- // convert (line, character) Position to absolute index number
296
+ // Convert (line, character) Position to absolute index number
251
297
private static getIndexOfPosition ( document : IProjectedDocument , position : Position ) : number {
252
298
const content : string = document . getContent ( ) ;
253
299
let lineNumber = 0 ;
@@ -275,6 +321,10 @@ export class CompletionHandler {
275
321
return positionIndex ;
276
322
}
277
323
324
+ // Provide completions using standard vscode executeCompletionItemProvider command
325
+ // Used in HTML context and (temporarily) C# provisional completion context (calling Roslyn
326
+ // directly during provisional completion session returns null, root cause TBD, tracked by
327
+ // https://github.com/dotnet/vscode-csharp/issues/7250)
278
328
private async provideVscodeCompletions (
279
329
virtualDocumentUri : vscode . Uri ,
280
330
projectedPosition : Position ,
0 commit comments