Skip to content

Commit

Permalink
Inline completions (#1190)
Browse files Browse the repository at this point in the history
* Add support for textdocument/inlineCompletions

* Remove unused imports

* Address PR comments

* Align client-node-tests VSC version with client

* Change insert text, bump versions, make proposed

* Add proposed annot, rename StringValue, update meta

* Adding missing tsdoc

* Undo eager version bumps

---------

Co-authored-by: Dirk Bäumer <[email protected]>
  • Loading branch information
c-claeys and dbaeumer authored May 22, 2023
1 parent 1320922 commit cc7666f
Show file tree
Hide file tree
Showing 19 changed files with 817 additions and 32 deletions.
16 changes: 8 additions & 8 deletions client-node-tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions client-node-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@
"description": "",
"version": "0.0.1",
"engines": {
"vscode": "^1.67.0"
"vscode": "^1.68.0"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"enabledApiProposals": [
"notebookContentProvider"
],
"main": "./extension.js",
"contributes": {},
"scripts": {
Expand All @@ -41,7 +38,7 @@
"@types/minimatch": "^3.0.5",
"@types/sinon": "^10.0.2",
"@types/uuid": "^8.3.1",
"@types/vscode": "1.67.0",
"@types/vscode": "1.68.0",
"find-process": "^1.4.7",
"glob": "^7.1.7",
"sinon": "^11.1.2",
Expand Down
20 changes: 20 additions & 0 deletions client-node-tests/src/converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,26 @@ suite('Protocol Converter', () => {
strictEqual((result[1] as ProtocolInlayHint).data, '2');
});

test('InlineCompletions', async () => {
const items: proto.InlineCompletionItem[] = [
proto.InlineCompletionItem.create('insert text', 'in', proto.Range.create(1, 2, 6, 7)),
proto.InlineCompletionItem.create('insert text', 'in', proto.Range.create(1, 2, 6, 7), undefined),
proto.InlineCompletionItem.create(proto.StringValue.create('insert text'), 'in', proto.Range.create(1, 2, 6, 7), undefined),
];

const result = await p2c.asInlineCompletionResult(items);
ok(result.every((r) => r instanceof vscode.InlineCompletionItem));
for (const r of result) {
rangeEqual(r.range!, proto.Range.create(1, 2, 6, 7));
}

ok(result[0] instanceof vscode.InlineCompletionItem && result[0].insertText === 'insert text');
ok(result[0] instanceof vscode.InlineCompletionItem && result[0].filterText === 'in');
ok(typeof result[0].insertText === 'string');
ok(typeof result[1].insertText === 'string');
ok(result[2].insertText instanceof vscode.SnippetString);
});

test('Bug #361', () => {
const item: proto.CompletionItem = {
'label': 'MyLabel',
Expand Down
22 changes: 22 additions & 0 deletions client-node-tests/src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ suite('Client integration', () => {
delta: true
}
},
inlineCompletionProvider: {},
workspace: {
fileOperations: {
didCreate: { filters: [{ scheme: fsProvider.scheme, pattern: { glob: '**/created-static/**{/,/*.txt}' } }] },
Expand Down Expand Up @@ -1445,6 +1446,27 @@ suite('Client integration', () => {
assert.strictEqual(edit.newText, 'number');
});

test('Inline Completions', async () => {
const providerData = client.getFeature(lsclient.InlineCompletionRequest.method).getProvider(document);
isDefined(providerData);
const results = (await providerData.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: {range, text: 'text'} }, tokenSource.token)) as vscode.InlineCompletionItem[];

isArray(results, vscode.InlineCompletionItem, 1);

rangeEqual(results[0].range!, 1, 2, 3, 4);
assert.strictEqual(results[0].filterText!, 'te');
assert.strictEqual(results[0].insertText, 'text inline');

let middlewareCalled: boolean = false;
middleware.provideInlineCompletionItems = (d, r, c, t, n) => {
middlewareCalled = true;
return n(d, r, c, t);
};
await providerData.provideInlineCompletionItems(document, position, { triggerKind: 0, selectedCompletionInfo: undefined }, tokenSource.token);
middleware.provideInlineCompletionItems = undefined;
assert.strictEqual(middlewareCalled, true);
});

test('Workspace symbols', async () => {
const providers = client.getFeature(lsclient.WorkspaceSymbolRequest.method).getProviders();
isDefined(providers);
Expand Down
11 changes: 9 additions & 2 deletions client-node-tests/src/servers/testServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
ColorInformation, Color, ColorPresentation, FoldingRange, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress,
InlineValueText, InlineValueVariableLookup, InlineValueEvaluatableExpression, WorkDoneProgressCreateRequest, WillCreateFilesRequest,
WillRenameFilesRequest, WillDeleteFilesRequest, DidDeleteFilesNotification, DidRenameFilesNotification, DidCreateFilesNotification,
ProposedFeatures, Diagnostic, DiagnosticSeverity, TypeHierarchyItem, InlayHint, InlayHintLabelPart, InlayHintKind, DocumentDiagnosticReportKind, DocumentSymbol
ProposedFeatures, Diagnostic, DiagnosticSeverity, TypeHierarchyItem, InlayHint, InlayHintLabelPart, InlayHintKind, DocumentDiagnosticReportKind, DocumentSymbol, InlineCompletionItem
} from 'vscode-languageserver/node';

import { URI } from 'vscode-uri';

const connection: ProposedFeatures.Connection = createConnection();
const connection = createConnection() as ProposedFeatures.Connection;

console.log = connection.console.log.bind(connection.console);
console.error = connection.console.error.bind(connection.console);
Expand Down Expand Up @@ -105,6 +105,7 @@ connection.onInitialize((params: InitializeParams): any => {
delta: true
}
},
inlineCompletionProvider: {},
workspace: {
fileOperations: {
// Static reg is folders + .txt files with operation kind in the path
Expand Down Expand Up @@ -513,6 +514,12 @@ connection.languages.inlayHint.resolve((hint) => {
return hint;
});

connection.languages.inlineCompletion.on((_params) => {
return [
InlineCompletionItem.create('text inline', 'te', Range.create(1,2,3,4))
];
});

connection.onRequest(
new ProtocolRequestType<null, null, never, any, any>('testing/sendSampleProgress'),
async (_, __) => {
Expand Down
14 changes: 7 additions & 7 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"devDependencies": {
"@types/minimatch": "^3.0.5",
"@types/semver": "^7.3.10",
"@types/vscode": "1.67.0",
"@types/vscode": "1.68.0",
"shx": "^0.3.4"
},
"dependencies": {
Expand Down
10 changes: 7 additions & 3 deletions client/src/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
DefinitionProvider, ReferenceProvider, DocumentHighlightProvider, CodeActionProvider, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider,
OnTypeFormattingEditProvider, RenameProvider, DocumentSymbolProvider, DocumentLinkProvider, DeclarationProvider, FoldingRangeProvider, ImplementationProvider,
DocumentColorProvider, SelectionRangeProvider, TypeDefinitionProvider, CallHierarchyProvider, LinkedEditingRangeProvider, TypeHierarchyProvider, WorkspaceSymbolProvider,
ProviderResult, TextEdit as VTextEdit
ProviderResult, TextEdit as VTextEdit, InlineCompletionItemProvider
} from 'vscode';

import {
Expand All @@ -36,7 +36,7 @@ import {
TypeHierarchyPrepareRequest, InlineValueRequest, InlayHintRequest, WorkspaceSymbolRequest, TextDocumentRegistrationOptions, FileOperationRegistrationOptions,
ConnectionOptions, PositionEncodingKind, DocumentDiagnosticRequest, NotebookDocumentSyncRegistrationType, NotebookDocumentSyncRegistrationOptions, ErrorCodes,
MessageStrategy, DidOpenTextDocumentParams, CodeLensResolveRequest, CompletionResolveRequest, CodeActionResolveRequest, InlayHintResolveRequest, DocumentLinkResolveRequest, WorkspaceSymbolResolveRequest,
CancellationToken as ProtocolCancellationToken
CancellationToken as ProtocolCancellationToken, InlineCompletionRequest, InlineCompletionRegistrationOptions
} from 'vscode-languageserver-protocol';

import * as c2p from './codeConverter';
Expand Down Expand Up @@ -90,6 +90,7 @@ import { InlineValueMiddleware, InlineValueProviderShape } from './inlineValue';
import { InlayHintsMiddleware, InlayHintsProviderShape } from './inlayHint';
import { WorkspaceFolderMiddleware } from './workspaceFolder';
import { FileOperationsMiddleware } from './fileOperations';
import { InlineCompletionMiddleware } from './inlineCompletion';
import { FileSystemWatcherFeature } from './fileSystemWatcher';
import { ColorProviderFeature } from './colorProvider';
import { ImplementationFeature } from './implementation';
Expand All @@ -106,6 +107,7 @@ import { LinkedEditingFeature } from './linkedEditingRange';
import { TypeHierarchyFeature } from './typeHierarchy';
import { InlineValueFeature } from './inlineValue';
import { InlayHintsFeature } from './inlayHint';
import { InlineCompletionItemFeature } from './inlineCompletion';

/**
* Controls when the output channel is revealed.
Expand Down Expand Up @@ -346,7 +348,7 @@ export type Middleware = _Middleware & TextDocumentSynchronizationMiddleware & C
DocumentHighlightMiddleware & DocumentSymbolMiddleware & WorkspaceSymbolMiddleware & ReferencesMiddleware & TypeDefinitionMiddleware & ImplementationMiddleware &
ColorProviderMiddleware & CodeActionMiddleware & CodeLensMiddleware & FormattingMiddleware & RenameMiddleware & DocumentLinkMiddleware & ExecuteCommandMiddleware &
FoldingRangeProviderMiddleware & DeclarationMiddleware & SelectionRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware &
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware & GeneralMiddleware;
LinkedEditingRangeMiddleware & TypeHierarchyMiddleware & InlineValueMiddleware & InlayHintsMiddleware & NotebookDocumentMiddleware & DiagnosticProviderMiddleware & InlineCompletionMiddleware & GeneralMiddleware;

export type LanguageClientOptions = {
documentSelector?: DocumentSelector | string[];
Expand Down Expand Up @@ -1734,6 +1736,7 @@ export abstract class BaseLanguageClient implements FeatureClient<Middleware, La
getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<WorkspaceSymbolProvider>;
getFeature(request: typeof DocumentDiagnosticRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticProviderShape> | undefined;
getFeature(request: typeof NotebookDocumentSyncRegistrationType.method): DynamicFeature<NotebookDocumentSyncRegistrationOptions> & NotebookDocumentProviderShape | undefined;
getFeature(request: typeof InlineCompletionRequest.method): DynamicFeature<InlineCompletionRegistrationOptions> & TextDocumentProviderFeature<InlineCompletionItemProvider>;
public getFeature(request: string): DynamicFeature<any> | undefined {
return this._dynamicFeatures.get(request);
}
Expand Down Expand Up @@ -2213,6 +2216,7 @@ function createConnection(input: MessageReader, output: MessageWriter, errorHand
export namespace ProposedFeatures {
export function createAll(_client: FeatureClient<Middleware, LanguageClientOptions>): (StaticFeature | DynamicFeature<any>)[] {
let result: (StaticFeature | DynamicFeature<any>)[] = [
new InlineCompletionItemFeature(_client)
];
return result;
}
Expand Down
10 changes: 9 additions & 1 deletion client/src/common/codeConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export interface Converter {
asWorkspaceSymbol(item: code.SymbolInformation): proto.WorkspaceSymbol;

asInlayHint(value: code.InlayHint): proto.InlayHint;

asInlineCompletionParams(document: code.TextDocument, position: code.Position, context: code.InlineCompletionContext): proto.InlineCompletionParams;
}

export interface URIConverter {
Expand Down Expand Up @@ -797,6 +799,11 @@ export function createConverter(uriConverter?: URIConverter): Converter {
return proto.InlineValueContext.create(context.frameId, asRange(context.stoppedLocation));
}

function asInlineCompletionParams(document: code.TextDocument, position: code.Position, context: code.InlineCompletionContext): proto.InlineCompletionParams {
return {context: proto.InlineCompletionContext.create(context.triggerKind, context.selectedCompletionInfo),
textDocument: asTextDocumentIdentifier(document), position: asPosition(position)};
}

function asCommand(item: code.Command): proto.Command {
let result = proto.Command.create(item.title, item.command);
if (item.arguments) { result.arguments = item.arguments; }
Expand Down Expand Up @@ -982,6 +989,7 @@ export function createConverter(uriConverter?: URIConverter): Converter {
asCallHierarchyItem,
asTypeHierarchyItem,
asInlayHint,
asWorkspaceSymbol
asWorkspaceSymbol,
asInlineCompletionParams
};
}
80 changes: 80 additions & 0 deletions client/src/common/inlineCompletion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import {
languages as Languages, TextDocument, Disposable, Position as VPosition, InlineCompletionContext as VInlineCompletionContext,
CancellationToken, ProviderResult, InlineCompletionItem as VInlineCompletionItem, InlineCompletionList as VInlineCompletionList, InlineCompletionItemProvider
} from 'vscode';

import {
ClientCapabilities, InlineCompletionOptions, InlineCompletionRegistrationOptions, InlineCompletionRequest,
DocumentSelector, ServerCapabilities
} from 'vscode-languageserver-protocol';

import {
FeatureClient, ensure, TextDocumentLanguageFeature
} from './features';

import * as UUID from './utils/uuid';

export interface ProvideInlineCompletionItemsSignature {
(this: void, document: TextDocument, position: VPosition, context: VInlineCompletionContext, token: CancellationToken): ProviderResult<VInlineCompletionItem[] | VInlineCompletionList>;
}

export interface InlineCompletionMiddleware {
provideInlineCompletionItems?: (this: void, document: TextDocument, position: VPosition, context: VInlineCompletionContext, token: CancellationToken, next: ProvideInlineCompletionItemsSignature) => ProviderResult<VInlineCompletionItem[] | VInlineCompletionList>;
}

export type InlineCompletionProviderShape = {
provider: InlineCompletionItemProvider;
};

export class InlineCompletionItemFeature extends TextDocumentLanguageFeature<boolean | InlineCompletionOptions, InlineCompletionRegistrationOptions, InlineCompletionItemProvider, InlineCompletionMiddleware> {

constructor(client: FeatureClient<InlineCompletionMiddleware>) {
super(client, InlineCompletionRequest.type);
}

public fillClientCapabilities(capabilities: ClientCapabilities): void {
let inlineCompletion = ensure(ensure(capabilities, 'textDocument')!, 'inlineCompletion')!;
inlineCompletion.dynamicRegistration = true;
}

public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {
const options = this.getRegistrationOptions(documentSelector, capabilities.inlineCompletionProvider);
if (!options) {
return;
}

this.register({
id: UUID.generateUuid(),
registerOptions: options
});
}

protected registerLanguageProvider(options: InlineCompletionRegistrationOptions): [Disposable, InlineCompletionItemProvider] {
const selector = options.documentSelector!;
const provider: InlineCompletionItemProvider = {
provideInlineCompletionItems: (document: TextDocument, position: VPosition, context: VInlineCompletionContext, token: CancellationToken): ProviderResult<VInlineCompletionList | VInlineCompletionItem[]> => {
const client = this._client;
const middleware = this._client.middleware;
const provideInlineCompletionItems: ProvideInlineCompletionItemsSignature = (document, position, context, token) => {
return client.sendRequest(InlineCompletionRequest.type, client.code2ProtocolConverter.asInlineCompletionParams(document, position, context), token).then((result) => {
if (token.isCancellationRequested) {
return null;
}
return client.protocol2CodeConverter.asInlineCompletionResult(result, token);
}, (error) => {
return client.handleFailedRequest(InlineCompletionRequest.type, token, error, null);
});
};
return middleware.provideInlineCompletionItems
? middleware.provideInlineCompletionItems(document, position, context, token, provideInlineCompletionItems)
: provideInlineCompletionItems(document, position, context, token);
}
};
return [Languages.registerInlineCompletionItemProvider(this._client.protocol2CodeConverter.asDocumentSelector(selector), provider), provider];
}
}
Loading

0 comments on commit cc7666f

Please sign in to comment.