Skip to content

Commit 31de06a

Browse files
committed
Factor out provisional completion into separate method and make sure provisionally added '.' is removed after we get completions from Roslyn
1 parent 3e2dd31 commit 31de06a

File tree

1 file changed

+66
-16
lines changed

1 file changed

+66
-16
lines changed

src/razor/src/completion/completionHandler.ts

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)