Skip to content

Commit

Permalink
Support async completion
Browse files Browse the repository at this point in the history
  • Loading branch information
333fred committed Oct 16, 2020
1 parent dafa51b commit bd53e95
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 9 deletions.
7 changes: 6 additions & 1 deletion package.json
Expand Up @@ -819,6 +819,11 @@
"default": false,
"description": "Enables support for showing unimported types and unimported extension methods in completion lists. When committed, the appropriate using directive will be added at the top of the current file. This option can have a negative impact on initial completion responsiveness, particularly for the first few completion sessions after opening a solution."
},
"omnisharp.enableAsyncCompletion": {
"type": "boolean",
"default": false,
"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."
},
"razor.plugin.path": {
"type": [
"string",
Expand Down Expand Up @@ -3571,4 +3576,4 @@
]
}
}
}
}
52 changes: 47 additions & 5 deletions src/features/completionProvider.ts
Expand Up @@ -3,25 +3,36 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CompletionItemProvider, TextDocument, Position, CompletionContext, CompletionList, CompletionItem, MarkdownString, TextEdit, Range, SnippetString } from "vscode";
import { CompletionItemProvider, TextDocument, Position, CompletionContext, CompletionList, CompletionItem, MarkdownString, TextEdit, Range, SnippetString, window, Selection } from "vscode";
import AbstractProvider from "./abstractProvider";
import * as protocol from "../omnisharp/protocol";
import * as serverUtils from '../omnisharp/utils';
import { CancellationToken, CompletionTriggerKind as LspCompletionTriggerKind, InsertTextFormat } from "vscode-languageserver-protocol";
import { createRequest } from "../omnisharp/typeConversion";
import OptionProvider from "../observers/OptionProvider";
import { LanguageMiddlewareFeature } from "../omnisharp/LanguageMiddlewareFeature";
import { OmniSharpServer } from "../omnisharp/server";

export const CompletionAfterInsertCommand = "csharp.completion.afterInsert";

export default class OmnisharpCompletionProvider extends AbstractProvider implements CompletionItemProvider {

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

constructor(server: OmniSharpServer, private optionProvider: OptionProvider, languageMiddlewareFeature: LanguageMiddlewareFeature) {
super(server, languageMiddlewareFeature);
}

public async provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): Promise<CompletionList> {
let request = createRequest<protocol.CompletionRequest>(document, position);
request.CompletionTrigger = (context.triggerKind + 1) as LspCompletionTriggerKind;
request.TriggerCharacter = context.triggerCharacter;
const options = this.optionProvider.GetLatestOptions();
request.UseAsyncCompletion = options.enableAsyncCompletion;

try {
const response = await serverUtils.getCompletion(this._server, request, token);
const mappedItems = response.Items.map(this._convertToVscodeCompletionItem);
const mappedItems = response.Items.map(arg => this._convertToVscodeCompletionItem(arg, options.enableAsyncCompletion));

let lastCompletions = new Map();

Expand Down Expand Up @@ -52,14 +63,44 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
const request: protocol.CompletionResolveRequest = { Item: lspItem };
try {
const response = await serverUtils.getCompletionResolve(this._server, request, token);
return this._convertToVscodeCompletionItem(response.Item);
const wasCreatedWithAsyncCompletion: boolean = !!item.command;
return this._convertToVscodeCompletionItem(response.Item, wasCreatedWithAsyncCompletion);
}
catch (error) {
return;
}
}

public async afterInsert(item: protocol.OmnisharpCompletionItem) {
try {
const { document: { fileName }, selection: { active: { line, character } } } = window.activeTextEditor;
const response = await serverUtils.getCompletionAfterInsert(this._server, { Item: item, FileName: fileName, Line: line + 1, Column: character + 1 });

if (!response.Change || !response.Column || !response.Line) {
return;
}

const applied = await window.activeTextEditor.edit(editBuilder => {
const replaceRange = new Range(response.Change.StartLine - 1, response.Change.StartColumn - 1, response.Change.EndLine - 1, response.Change.EndColumn - 1);
editBuilder.replace(replaceRange, response.Change.NewText);
});

if (!applied) {
return;
}

const responseLine = response.Line - 1;
const responseColumn = response.Column - 1;

const finalPosition = new Position(responseLine, responseColumn);
window.activeTextEditor.selections = [new Selection(finalPosition, finalPosition)];
}
catch (error) {
return;
}
}

private _convertToVscodeCompletionItem(omnisharpCompletion: protocol.OmnisharpCompletionItem): CompletionItem {
private _convertToVscodeCompletionItem(omnisharpCompletion: protocol.OmnisharpCompletionItem, enableAsyncCompletion: boolean): CompletionItem {
const docs: MarkdownString | undefined = omnisharpCompletion.Documentation ? new MarkdownString(omnisharpCompletion.Documentation, false) : undefined;

const mapRange = function (edit: protocol.LinePositionSpanTextChange): Range {
Expand Down Expand Up @@ -94,7 +135,8 @@ export default class OmnisharpCompletionProvider extends AbstractProvider implem
tags: omnisharpCompletion.Tags,
sortText: omnisharpCompletion.SortText,
additionalTextEdits: additionalTextEdits,
keepWhitespace: true
keepWhitespace: true,
command: enableAsyncCompletion ? { command: CompletionAfterInsertCommand, title: "", arguments: [omnisharpCompletion] } : undefined
};
}
}
6 changes: 4 additions & 2 deletions src/omnisharp/extension.ts
Expand Up @@ -11,7 +11,7 @@ import { safeLength, sum } from '../common';
import { CSharpConfigurationProvider } from '../configurationProvider';
import CodeActionProvider from '../features/codeActionProvider';
import CodeLensProvider from '../features/codeLensProvider';
import CompletionProvider from '../features/completionProvider';
import CompletionProvider, { CompletionAfterInsertCommand } from '../features/completionProvider';
import DefinitionMetadataDocumentProvider from '../features/definitionMetadataDocumentProvider';
import DefinitionProvider from '../features/definitionProvider';
import DocumentHighlightProvider from '../features/documentHighlightProvider';
Expand Down Expand Up @@ -69,6 +69,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
disposables.add(languageMiddlewareFeature);
let localDisposables: CompositeDisposable;
const testManager = new TestManager(server, eventStream, languageMiddlewareFeature);
const completionProvider = new CompletionProvider(server, optionProvider, languageMiddlewareFeature);

disposables.add(server.onServerStart(() => {
// register language feature provider on start
Expand All @@ -90,7 +91,8 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
localDisposables.add(vscode.languages.registerDocumentRangeFormattingEditProvider(documentSelector, new FormatProvider(server, languageMiddlewareFeature)));
localDisposables.add(vscode.languages.registerOnTypeFormattingEditProvider(documentSelector, new FormatProvider(server, languageMiddlewareFeature), '}', ';'));
}
localDisposables.add(vscode.languages.registerCompletionItemProvider(documentSelector, new CompletionProvider(server, languageMiddlewareFeature), '.', ' '));
localDisposables.add(vscode.languages.registerCompletionItemProvider(documentSelector, completionProvider, '.', ' '));
localDisposables.add(vscode.commands.registerCommand(CompletionAfterInsertCommand, async (item) => completionProvider.afterInsert(item)));
localDisposables.add(vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(server, optionProvider, languageMiddlewareFeature)));
localDisposables.add(vscode.languages.registerSignatureHelpProvider(documentSelector, new SignatureHelpProvider(server, languageMiddlewareFeature), '(', ','));
// Since the CodeActionProvider registers its own commands, we must instantiate it and add it to the localDisposables
Expand Down
3 changes: 3 additions & 0 deletions src/omnisharp/options.ts
Expand Up @@ -29,6 +29,7 @@ export class Options {
public enableEditorConfigSupport: boolean,
public enableDecompilationSupport: boolean,
public enableImportCompletion: boolean,
public enableAsyncCompletion: boolean,
public useSemanticHighlighting: boolean,
public razorPluginPath?: string,
public defaultLaunchSolution?: string,
Expand Down Expand Up @@ -72,6 +73,7 @@ export class Options {
const enableEditorConfigSupport = omnisharpConfig.get<boolean>('enableEditorConfigSupport', false);
const enableDecompilationSupport = omnisharpConfig.get<boolean>('enableDecompilationSupport', false);
const enableImportCompletion = omnisharpConfig.get<boolean>('enableImportCompletion', false);
const enableAsyncCompletion = omnisharpConfig.get<boolean>('enableAsyncCompletion', false);

const useFormatting = csharpConfig.get<boolean>('format.enable', true);

Expand Down Expand Up @@ -120,6 +122,7 @@ export class Options {
enableEditorConfigSupport,
enableDecompilationSupport,
enableImportCompletion,
enableAsyncCompletion,
useSemanticHighlighting,
razorPluginPath,
defaultLaunchSolution,
Expand Down
12 changes: 12 additions & 0 deletions src/omnisharp/protocol.ts
Expand Up @@ -35,6 +35,7 @@ export module Requests {
export const QuickInfo = '/quickinfo';
export const Completion = '/completion';
export const CompletionResolve = '/completion/resolve';
export const CompletionAfterInsert = '/completion/afterInsert';
}

export namespace WireProtocol {
Expand Down Expand Up @@ -499,6 +500,7 @@ export interface QuickInfoResponse {
export interface CompletionRequest extends Request {
CompletionTrigger: CompletionTriggerKind;
TriggerCharacter?: string;
UseAsyncCompletion: boolean;
}

export interface CompletionResponse {
Expand All @@ -514,6 +516,16 @@ export interface CompletionResolveResponse {
Item: OmnisharpCompletionItem;
}

export interface CompletionAfterInsertionRequest extends Request {
Item: OmnisharpCompletionItem;
}

export interface CompletionAfterInsertResponse {
Change?: LinePositionSpanTextChange;
Line?: number;
Column?: number;
}

export interface OmnisharpCompletionItem {
Label: string;
Kind: CompletionItemKind;
Expand Down
4 changes: 4 additions & 0 deletions src/omnisharp/utils.ts
Expand Up @@ -191,6 +191,10 @@ export async function getCompletionResolve(server: OmniSharpServer, request: pro
return server.makeRequest<protocol.CompletionResolveResponse>(protocol.Requests.CompletionResolve, request, context);
}

export async function getCompletionAfterInsert(server: OmniSharpServer, request: protocol.CompletionAfterInsertionRequest) {
return server.makeRequest<protocol.CompletionAfterInsertResponse>(protocol.Requests.CompletionAfterInsert, request);
}

export async function isNetCoreProject(project: protocol.MSBuildProject) {
return project.TargetFrameworks.find(tf => tf.ShortName.startsWith('netcoreapp') || tf.ShortName.startsWith('netstandard')) !== undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion test/unitTests/Fakes/FakeOptions.ts
Expand Up @@ -6,5 +6,5 @@
import { Options } from "../../../src/omnisharp/options";

export function getEmptyOptions(): Options {
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, false, 0, 0, false, false, false, false, false, false, false, undefined, "", "");
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, false, 0, 0, false, false, false, false, false, false, false, false, undefined, "", "");
}

0 comments on commit bd53e95

Please sign in to comment.