Skip to content

Commit f1461bd

Browse files
authored
Merge pull request #4116 from 333fred/async-completion
Support async completion
2 parents ea20d08 + 0e83f1a commit f1461bd

File tree

10 files changed

+81
-7
lines changed

10 files changed

+81
-7
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"test:integration:slnFilterWithCsproj": "tsc -p ./ && gulp test:integration:slnFilterWithCsproj",
5555
"test:release": "mocha --config ./.mocharc.jsonc test/releaseTests/**/*.test.ts",
5656
"test:artifacts": "mocha --config ./.mocharc.jsonc test/artifactTests/**/*.test.ts",
57-
5857
"unpackage:vsix": "gulp vsix:release:unpackage",
5958
"gulp": "gulp"
6059
},
@@ -821,6 +820,11 @@
821820
"default": false,
822821
"description": "Specifies whether 'using' directives should be grouped and sorted during document formatting."
823822
},
823+
"omnisharp.enableAsyncCompletion": {
824+
"type": "boolean",
825+
"default": false,
826+
"description": "(EXPERIMENTAL) Enables support for resolving completion edits asynchronously. This can speed up time to show the completion list, particularly override and partial method completion lists, at the cost of slight delays after inserting a completion item. Most completion items will have no noticeable impact with this feature, but typing immediately after inserting an override or partial method completion, before the insert is completed, can have unpredictable results."
827+
},
824828
"razor.plugin.path": {
825829
"type": [
826830
"string",

src/features/completionProvider.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,33 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { CompletionItemProvider, TextDocument, Position, CompletionContext, CompletionList, CompletionItem, MarkdownString, TextEdit, Range, SnippetString } from "vscode";
6+
import { CompletionItemProvider, TextDocument, Position, CompletionContext, CompletionList, CompletionItem, MarkdownString, TextEdit, Range, SnippetString, window, Selection, WorkspaceEdit, workspace } from "vscode";
77
import AbstractProvider from "./abstractProvider";
88
import * as protocol from "../omnisharp/protocol";
99
import * as serverUtils from '../omnisharp/utils';
1010
import { CancellationToken, CompletionTriggerKind as LspCompletionTriggerKind, InsertTextFormat } from "vscode-languageserver-protocol";
1111
import { createRequest } from "../omnisharp/typeConversion";
12+
import { LanguageMiddlewareFeature } from "../omnisharp/LanguageMiddlewareFeature";
13+
import { OmniSharpServer } from "../omnisharp/server";
14+
15+
export const CompletionAfterInsertCommand = "csharp.completion.afterInsert";
1216

1317
export default class OmnisharpCompletionProvider extends AbstractProvider implements CompletionItemProvider {
1418

1519
#lastCompletions?: Map<CompletionItem, protocol.OmnisharpCompletionItem>;
1620

21+
constructor(server: OmniSharpServer, languageMiddlewareFeature: LanguageMiddlewareFeature) {
22+
super(server, languageMiddlewareFeature);
23+
}
24+
1725
public async provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): Promise<CompletionList> {
1826
let request = createRequest<protocol.CompletionRequest>(document, position);
1927
request.CompletionTrigger = (context.triggerKind + 1) as LspCompletionTriggerKind;
2028
request.TriggerCharacter = context.triggerCharacter;
2129

2230
try {
2331
const response = await serverUtils.getCompletion(this._server, request, token);
24-
const mappedItems = response.Items.map(this._convertToVscodeCompletionItem);
32+
const mappedItems = response.Items.map(arg => this._convertToVscodeCompletionItem(arg));
2533

2634
let lastCompletions = new Map();
2735

@@ -59,6 +67,40 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
5967
}
6068
}
6169

70+
public async afterInsert(item: protocol.OmnisharpCompletionItem) {
71+
try {
72+
const uri = window.activeTextEditor.document.uri;
73+
const response = await serverUtils.getCompletionAfterInsert(this._server, { Item: item });
74+
75+
if (!response.Changes || !response.Column || !response.Line) {
76+
return;
77+
}
78+
79+
let edit = new WorkspaceEdit();
80+
edit.set(uri, response.Changes.map(change => ({
81+
newText: change.NewText,
82+
range: new Range(new Position(change.StartLine, change.StartColumn),
83+
new Position(change.EndLine, change.EndColumn))
84+
})));
85+
86+
edit = await this._languageMiddlewareFeature.remap("remapWorkspaceEdit", edit, CancellationToken.None);
87+
88+
const applied = await workspace.applyEdit(edit);
89+
if (!applied) {
90+
return;
91+
}
92+
93+
const responseLine = response.Line;
94+
const responseColumn = response.Column;
95+
96+
const finalPosition = new Position(responseLine, responseColumn);
97+
window.activeTextEditor.selections = [new Selection(finalPosition, finalPosition)];
98+
}
99+
catch (error) {
100+
return;
101+
}
102+
}
103+
62104
private _convertToVscodeCompletionItem(omnisharpCompletion: protocol.OmnisharpCompletionItem): CompletionItem {
63105
const docs: MarkdownString | undefined = omnisharpCompletion.Documentation ? new MarkdownString(omnisharpCompletion.Documentation, false) : undefined;
64106

@@ -94,7 +136,8 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
94136
tags: omnisharpCompletion.Tags,
95137
sortText: omnisharpCompletion.SortText,
96138
additionalTextEdits: additionalTextEdits,
97-
keepWhitespace: true
139+
keepWhitespace: true,
140+
command: omnisharpCompletion.HasAfterInsertStep ? { command: CompletionAfterInsertCommand, title: "", arguments: [omnisharpCompletion] } : undefined
98141
};
99142
}
100143
}

src/observers/OptionChangeObserver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const omniSharpOptions: ReadonlyArray<OptionsKey> = [
2222
"enableDecompilationSupport",
2323
"enableImportCompletion",
2424
"organizeImportsOnFormat",
25+
"enableAsyncCompletion",
2526
];
2627

2728
function OmniSharpOptionChangeObservable(optionObservable: Observable<Options>): Observable<Options> {

src/omnisharp/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { safeLength, sum } from '../common';
1111
import { CSharpConfigurationProvider } from '../configurationProvider';
1212
import CodeActionProvider from '../features/codeActionProvider';
1313
import CodeLensProvider from '../features/codeLensProvider';
14-
import CompletionProvider from '../features/completionProvider';
14+
import CompletionProvider, { CompletionAfterInsertCommand } from '../features/completionProvider';
1515
import DefinitionMetadataDocumentProvider from '../features/definitionMetadataDocumentProvider';
1616
import DefinitionProvider from '../features/definitionProvider';
1717
import DocumentHighlightProvider from '../features/documentHighlightProvider';
@@ -69,6 +69,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
6969
disposables.add(languageMiddlewareFeature);
7070
let localDisposables: CompositeDisposable;
7171
const testManager = new TestManager(server, eventStream, languageMiddlewareFeature);
72+
const completionProvider = new CompletionProvider(server, languageMiddlewareFeature);
7273

7374
disposables.add(server.onServerStart(() => {
7475
// register language feature provider on start
@@ -90,7 +91,8 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
9091
localDisposables.add(vscode.languages.registerDocumentRangeFormattingEditProvider(documentSelector, new FormatProvider(server, languageMiddlewareFeature)));
9192
localDisposables.add(vscode.languages.registerOnTypeFormattingEditProvider(documentSelector, new FormatProvider(server, languageMiddlewareFeature), '}', '/', '\n', ';'));
9293
}
93-
localDisposables.add(vscode.languages.registerCompletionItemProvider(documentSelector, new CompletionProvider(server, languageMiddlewareFeature), '.', ' '));
94+
localDisposables.add(vscode.languages.registerCompletionItemProvider(documentSelector, completionProvider, '.', ' '));
95+
localDisposables.add(vscode.commands.registerCommand(CompletionAfterInsertCommand, async (item) => completionProvider.afterInsert(item)));
9496
localDisposables.add(vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(server, optionProvider, languageMiddlewareFeature)));
9597
localDisposables.add(vscode.languages.registerSignatureHelpProvider(documentSelector, new SignatureHelpProvider(server, languageMiddlewareFeature), '(', ','));
9698
// Since the CodeActionProvider registers its own commands, we must instantiate it and add it to the localDisposables

src/omnisharp/options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class Options {
3232
public enableEditorConfigSupport: boolean,
3333
public enableDecompilationSupport: boolean,
3434
public enableImportCompletion: boolean,
35+
public enableAsyncCompletion: boolean,
3536
public useSemanticHighlighting: boolean,
3637
public razorPluginPath?: string,
3738
public defaultLaunchSolution?: string,
@@ -75,6 +76,7 @@ export class Options {
7576
const enableEditorConfigSupport = omnisharpConfig.get<boolean>('enableEditorConfigSupport', false);
7677
const enableDecompilationSupport = omnisharpConfig.get<boolean>('enableDecompilationSupport', false);
7778
const enableImportCompletion = omnisharpConfig.get<boolean>('enableImportCompletion', false);
79+
const enableAsyncCompletion = omnisharpConfig.get<boolean>('enableAsyncCompletion', false);
7880

7981
const useFormatting = csharpConfig.get<boolean>('format.enable', true);
8082
const organizeImportsOnFormat = omnisharpConfig.get<boolean>('organizeImportsOnFormat', false);
@@ -130,6 +132,7 @@ export class Options {
130132
enableEditorConfigSupport,
131133
enableDecompilationSupport,
132134
enableImportCompletion,
135+
enableAsyncCompletion,
133136
useSemanticHighlighting,
134137
razorPluginPath,
135138
defaultLaunchSolution,

src/omnisharp/protocol.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export module Requests {
3434
export const QuickInfo = '/quickinfo';
3535
export const Completion = '/completion';
3636
export const CompletionResolve = '/completion/resolve';
37+
export const CompletionAfterInsert = '/completion/afterInsert';
3738
}
3839

3940
export namespace WireProtocol {
@@ -524,6 +525,16 @@ export interface CompletionResolveResponse {
524525
Item: OmnisharpCompletionItem;
525526
}
526527

528+
export interface CompletionAfterInsertionRequest {
529+
Item: OmnisharpCompletionItem;
530+
}
531+
532+
export interface CompletionAfterInsertResponse {
533+
Changes?: LinePositionSpanTextChange[];
534+
Line?: number;
535+
Column?: number;
536+
}
537+
527538
export interface OmnisharpCompletionItem {
528539
Label: string;
529540
Kind: CompletionItemKind;
@@ -539,6 +550,7 @@ export interface OmnisharpCompletionItem {
539550
CommitCharacters?: string[];
540551
AdditionalTextEdits?: LinePositionSpanTextChange[];
541552
Data: any;
553+
HasAfterInsertStep: boolean;
542554
}
543555

544556
export namespace V2 {

src/omnisharp/server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ export class OmniSharpServer {
372372
args.push('RoslynExtensionsOptions:EnableImportCompletion=true');
373373
}
374374

375+
if (options.enableAsyncCompletion === true) {
376+
args.push('RoslynExtensionsOptions:EnableAsyncCompletion=true');
377+
}
378+
375379
let launchInfo: LaunchInfo;
376380
try {
377381
launchInfo = await this._omnisharpManager.GetOmniSharpLaunchInfo(this.packageJSON.defaults.omniSharp, options.path, serverUrl, latestVersionFileServerPath, installPath, this.extensionPath);

src/omnisharp/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ export async function getCompletionResolve(server: OmniSharpServer, request: pro
179179
return server.makeRequest<protocol.CompletionResolveResponse>(protocol.Requests.CompletionResolve, request, context);
180180
}
181181

182+
export async function getCompletionAfterInsert(server: OmniSharpServer, request: protocol.CompletionAfterInsertionRequest) {
183+
return server.makeRequest<protocol.CompletionAfterInsertResponse>(protocol.Requests.CompletionAfterInsert, request);
184+
}
185+
182186
export async function isNetCoreProject(project: protocol.MSBuildProject) {
183187
return project.TargetFrameworks.find(tf => tf.ShortName.startsWith('netcoreapp') || tf.ShortName.startsWith('netstandard')) !== undefined;
184188
}

test/unitTests/Fakes/FakeOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
import { Options } from "../../../src/omnisharp/options";
77

88
export function getEmptyOptions(): Options {
9-
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, [], false, false, false, 0, 0, false, false, false, false, false, false, false, undefined, "", "");
9+
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, [], false, false, false, 0, 0, false, false, false, false, false, false, false, false, undefined, "", "");
1010
}

test/unitTests/optionStream.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ suite('OptionStream', () => {
5555
options.enableEditorConfigSupport.should.equal(false);
5656
options.enableDecompilationSupport.should.equal(false);
5757
options.enableImportCompletion.should.equal(false);
58+
options.enableAsyncCompletion.should.equal(false);
5859
expect(options.defaultLaunchSolution).to.be.undefined;
5960
});
6061

0 commit comments

Comments
 (0)