@@ -197,29 +197,75 @@ export class CompletionHandler {
197197 } ;
198198
199199 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+ ) ;
213208 }
209+
210+ // non-provisional C# completion
214211 const csharpCompletions = await vscode . commands . executeCommand < CompletionList > (
215212 provideCompletionsCommand ,
216213 params
217214 ) ;
218- CompletionHandler . AdjustCSharpCompletionList ( csharpCompletions , triggerCharacter ) ;
215+ CompletionHandler . adjustCSharpCompletionList ( csharpCompletions , triggerCharacter ) ;
219216 return csharpCompletions ;
220217 }
221218
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 ) {
223269 const data = completionList . itemDefaults ?. data ;
224270 for ( const completionItem of completionList . items ) {
225271 // textEdit is deprecated in favor of .range. Clear out its value to avoid any unexpected behavior.
@@ -247,7 +293,7 @@ export class CompletionHandler {
247293 }
248294 }
249295
250- // convert (line, character) Position to absolute index number
296+ // Convert (line, character) Position to absolute index number
251297 private static getIndexOfPosition ( document : IProjectedDocument , position : Position ) : number {
252298 const content : string = document . getContent ( ) ;
253299 let lineNumber = 0 ;
@@ -275,6 +321,10 @@ export class CompletionHandler {
275321 return positionIndex ;
276322 }
277323
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)
278328 private async provideVscodeCompletions (
279329 virtualDocumentUri : vscode . Uri ,
280330 projectedPosition : Position ,
0 commit comments