Skip to content

Commit 6f47507

Browse files
committed
Support async completion
vscode side of OmniSharp/omnisharp-roslyn#1986.
1 parent e0b8959 commit 6f47507

File tree

10 files changed

+79
-8
lines changed

10 files changed

+79
-8
lines changed

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
"test:integration:slnFilterWithCsproj": "tsc -p ./ && gulp test:integration:slnFilterWithCsproj",
4949
"test:release": "mocha --config ./.mocharc.jsonc test/releaseTests/**/*.test.ts",
5050
"test:artifacts": "mocha --config ./.mocharc.jsonc test/artifactTests/**/*.test.ts",
51-
5251
"unpackage:vsix": "gulp vsix:release:unpackage",
5352
"gulp": "gulp"
5453
},
@@ -807,6 +806,11 @@
807806
"default": false,
808807
"description": "Specifies whether 'using' directives should be grouped and sorted during document formatting."
809808
},
809+
"omnisharp.enableAsyncCompletion": {
810+
"type": "boolean",
811+
"default": false,
812+
"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."
813+
},
810814
"razor.plugin.path": {
811815
"type": [
812816
"string",
@@ -3688,4 +3692,4 @@
36883692
]
36893693
}
36903694
}
3691-
}
3695+
}

src/features/completionProvider.ts

Lines changed: 42 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 } 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,36 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
5967
}
6068
}
6169

70+
public async afterInsert(item: protocol.OmnisharpCompletionItem) {
71+
try {
72+
const response = await serverUtils.getCompletionAfterInsert(this._server, { Item: item });
73+
74+
if (!response.Changes || !response.Column || !response.Line) {
75+
return;
76+
}
77+
78+
const applied = await window.activeTextEditor.edit(editBuilder => {
79+
for (const change of response.Changes) {
80+
const replaceRange = new Range(change.StartLine, change.StartColumn, change.EndLine, change.EndColumn);
81+
editBuilder.replace(replaceRange, change.NewText);
82+
}
83+
});
84+
85+
if (!applied) {
86+
return;
87+
}
88+
89+
const responseLine = response.Line;
90+
const responseColumn = response.Column;
91+
92+
const finalPosition = new Position(responseLine, responseColumn);
93+
window.activeTextEditor.selections = [new Selection(finalPosition, finalPosition)];
94+
}
95+
catch (error) {
96+
return;
97+
}
98+
}
99+
62100
private _convertToVscodeCompletionItem(omnisharpCompletion: protocol.OmnisharpCompletionItem): CompletionItem {
63101
const docs: MarkdownString | undefined = omnisharpCompletion.Documentation ? new MarkdownString(omnisharpCompletion.Documentation, false) : undefined;
64102

@@ -94,7 +132,8 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
94132
tags: omnisharpCompletion.Tags,
95133
sortText: omnisharpCompletion.SortText,
96134
additionalTextEdits: additionalTextEdits,
97-
keepWhitespace: true
135+
keepWhitespace: true,
136+
command: omnisharpCompletion.HasAfterInsertStep ? { command: CompletionAfterInsertCommand, title: "", arguments: [omnisharpCompletion] } : undefined
98137
};
99138
}
100139
}

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
@@ -31,6 +31,7 @@ export class Options {
3131
public enableEditorConfigSupport: boolean,
3232
public enableDecompilationSupport: boolean,
3333
public enableImportCompletion: boolean,
34+
public enableAsyncCompletion: boolean,
3435
public useSemanticHighlighting: boolean,
3536
public razorPluginPath?: string,
3637
public defaultLaunchSolution?: string,
@@ -74,6 +75,7 @@ export class Options {
7475
const enableEditorConfigSupport = omnisharpConfig.get<boolean>('enableEditorConfigSupport', false);
7576
const enableDecompilationSupport = omnisharpConfig.get<boolean>('enableDecompilationSupport', false);
7677
const enableImportCompletion = omnisharpConfig.get<boolean>('enableImportCompletion', false);
78+
const enableAsyncCompletion = omnisharpConfig.get<boolean>('enableAsyncCompletion', false);
7779

7880
const useFormatting = csharpConfig.get<boolean>('format.enable', true);
7981
const organizeImportsOnFormat = omnisharpConfig.get<boolean>('organizeImportsOnFormat', false);
@@ -127,6 +129,7 @@ export class Options {
127129
enableEditorConfigSupport,
128130
enableDecompilationSupport,
129131
enableImportCompletion,
132+
enableAsyncCompletion,
130133
useSemanticHighlighting,
131134
razorPluginPath,
132135
defaultLaunchSolution,

src/omnisharp/protocol.ts

Lines changed: 13 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 {
@@ -509,6 +510,7 @@ export interface QuickInfoResponse {
509510
export interface CompletionRequest extends Request {
510511
CompletionTrigger: CompletionTriggerKind;
511512
TriggerCharacter?: string;
513+
UseAsyncCompletion: boolean;
512514
}
513515

514516
export interface CompletionResponse {
@@ -524,6 +526,16 @@ export interface CompletionResolveResponse {
524526
Item: OmnisharpCompletionItem;
525527
}
526528

529+
export interface CompletionAfterInsertionRequest {
530+
Item: OmnisharpCompletionItem;
531+
}
532+
533+
export interface CompletionAfterInsertResponse {
534+
Changes?: LinePositionSpanTextChange[];
535+
Line?: number;
536+
Column?: number;
537+
}
538+
527539
export interface OmnisharpCompletionItem {
528540
Label: string;
529541
Kind: CompletionItemKind;
@@ -539,6 +551,7 @@ export interface OmnisharpCompletionItem {
539551
CommitCharacters?: string[];
540552
AdditionalTextEdits?: LinePositionSpanTextChange[];
541553
Data: any;
554+
HasAfterInsertStep: boolean;
542555
}
543556

544557
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)