Skip to content

Commit

Permalink
vscode: add support for inline completions
Browse files Browse the repository at this point in the history
The commit adds support for the `inline completions` VS Code API.

Signed-off-by: vince-fugnitto <vincent.fugnitto@ericsson.com>
  • Loading branch information
vince-fugnitto committed Nov 21, 2022
1 parent 3ac912f commit b8a8e0d
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 6 deletions.
95 changes: 95 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -764,3 +764,98 @@ export interface InlayHintsProvider {
resolveInlayHint?(hint: InlayHint, token: monaco.CancellationToken): InlayHint[] | undefined | Thenable<InlayHint[] | undefined>;
}

/**
* How an {@link InlineCompletionsProvider inline completion provider} was triggered.
*/
export enum InlineCompletionTriggerKind {
/**
* Completion was triggered automatically while editing.
* It is sufficient to return a single completion item in this case.
*/
Automatic = 0,

/**
* Completion was triggered explicitly by a user gesture.
* Return multiple completion items to enable cycling through them.
*/
Explicit = 1,
}

export interface InlineCompletionContext {
/**
* How the completion was triggered.
*/
readonly triggerKind: InlineCompletionTriggerKind;

readonly selectedSuggestionInfo: SelectedSuggestionInfo | undefined;
}

export interface SelectedSuggestionInfo {
range: Range;
text: string;
isSnippetText: boolean;
completionKind: CompletionItemKind;
}

export interface InlineCompletion {
/**
* The text to insert.
* If the text contains a line break, the range must end at the end of a line.
* If existing text should be replaced, the existing text must be a prefix of the text to insert.
*
* The text can also be a snippet. In that case, a preview with default parameters is shown.
* When accepting the suggestion, the full snippet is inserted.
*/
readonly insertText: string | { snippet: string };

/**
* A text that is used to decide if this inline completion should be shown.
* An inline completion is shown if the text to replace is a subword of the filter text.
*/
readonly filterText?: string;

/**
* An optional array of additional text edits that are applied when
* selecting this completion. Edits must not overlap with the main edit
* nor with themselves.
*/
readonly additionalTextEdits?: SingleEditOperation[];

/**
* The range to replace.
* Must begin and end on the same line.
*/
readonly range?: Range;

readonly command?: Command;

/**
* If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed.
* Defaults to `false`.
*/
readonly completeBracketPairs?: boolean;
}

export interface InlineCompletions<TItem extends InlineCompletion = InlineCompletion> {
readonly items: readonly TItem[];
}

export interface InlineCompletionsProvider<T extends InlineCompletions = InlineCompletions> {
provideInlineCompletions(
model: monaco.editor.ITextModel,
position: monaco.Position,
context: InlineCompletionContext,
token: monaco.CancellationToken
): T[] | undefined | Thenable<T[] | undefined>;

/**
* Will be called when an item is shown.
*/
handleItemDidShow?(completions: T, item: T['items'][number]): void;

/**
* Will be called when a completions list is no longer in use and can be garbage-collected.
*/
freeInlineCompletions(completions: T): void;
}

16 changes: 15 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ import {
InlayHint,
CachedSession,
CachedSessionItem,
TypeHierarchyItem
TypeHierarchyItem,
InlineCompletion,
InlineCompletions,
InlineCompletionContext
} from './plugin-api-rpc-model';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
Expand Down Expand Up @@ -1576,6 +1579,8 @@ export interface LanguagesExt {
$provideSuperTypes(handle: number, sessionId: string, itemId: string, token: theia.CancellationToken): Promise<TypeHierarchyItem[] | undefined>
$provideSubTypes(handle: number, sessionId: string, itemId: string, token: theia.CancellationToken): Promise<TypeHierarchyItem[] | undefined>;
$releaseTypeHierarchy(handle: number, session?: string): Promise<boolean>;
$provideInlineCompletions(handle: number, resource: UriComponents, position: Position, context: InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined>;
$freeInlineCompletionsList(handle: number, pid: number): void;
}

export const LanguagesMainFactory = Symbol('LanguagesMainFactory');
Expand Down Expand Up @@ -1633,6 +1638,7 @@ export interface LanguagesMain {
$registerTypeHierarchyProvider(handle: number, selector: SerializedDocumentFilter[]): void;
$setLanguageStatus(handle: number, status: LanguageStatus): void;
$removeLanguageStatus(handle: number): void;
$registerInlineCompletionsSupport(handle: number, selector: SerializedDocumentFilter[]): void;
}

export interface WebviewInitData {
Expand Down Expand Up @@ -2040,3 +2046,11 @@ export interface SecretsMain {

export type InlayHintDto = CachedSessionItem<InlayHint>;
export type InlayHintsDto = CachedSession<{ hints: InlayHint[] }>;

export interface IdentifiableInlineCompletions extends InlineCompletions<IdentifiableInlineCompletion> {
pid: number;
}

export interface IdentifiableInlineCompletion extends InlineCompletion {
idx: number;
}
38 changes: 38 additions & 0 deletions packages/plugin-ext/src/common/reference-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// *****************************************************************************
// Copyright (C) 2022 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************

// copied from hhttps://github.com/microsoft/vscode/blob/6261075646f055b99068d3688932416f2346dd3b/src/vs/workbench/api/common/extHostLanguageFeatures.ts#L1291-L1310.

export class ReferenceMap<T> {
private readonly _references = new Map<number, T>();
private _idPool = 1;

createReferenceId(value: T): number {
const id = this._idPool++;
this._references.set(id, value);
return id;
}

disposeReferenceId(referenceId: number): T | undefined {
const value = this._references.get(referenceId);
this._references.delete(referenceId);
return value;
}

get(referenceId: number): T | undefined {
return this._references.get(referenceId);
}
}
19 changes: 18 additions & 1 deletion packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
WorkspaceTextEditDto,
PluginInfo,
LanguageStatus as LanguageStatusDTO,
InlayHintDto
InlayHintDto,
IdentifiableInlineCompletions
} from '../../common/plugin-api-rpc';
import { injectable, inject } from '@theia/core/shared/inversify';
import {
Expand Down Expand Up @@ -868,6 +869,22 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
}
}

$registerInlineCompletionsSupport(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = this.toLanguageSelector(selector);
const provider: monaco.languages.InlineCompletionsProvider<IdentifiableInlineCompletions> = {
provideInlineCompletions: async (
model: monaco.editor.ITextModel,
position: monaco.Position,
context: monaco.languages.InlineCompletionContext,
token: CancellationToken
): Promise<IdentifiableInlineCompletions | undefined> => this.proxy.$provideInlineCompletions(handle, model.uri, position, context, token),
freeInlineCompletions: (completions: IdentifiableInlineCompletions): void => {
this.proxy.$freeInlineCompletionsList(handle, completions.pid);
}
};
this.register(handle, (monaco.languages.registerInlineCompletionsProvider as RegistrationFunction<monaco.languages.InlineCompletionsProvider>)(languageSelector, provider));
}

$registerQuickFixProvider(
handle: number,
pluginInfo: PluginInfo,
Expand Down
30 changes: 28 additions & 2 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
Plugin,
InlayHintsDto,
InlayHintDto,
IdentifiableInlineCompletions,
} from '../common/plugin-api-rpc';
import { RPCProtocol } from '../common/rpc-protocol';
import * as theia from '@theia/plugin';
Expand Down Expand Up @@ -67,7 +68,8 @@ import {
EvaluatableExpression,
InlineValue,
InlineValueContext,
TypeHierarchyItem
TypeHierarchyItem,
InlineCompletionContext
} from '../common/plugin-api-rpc-model';
import { CompletionAdapter } from './languages/completion';
import { Diagnostics } from './languages/diagnostics';
Expand Down Expand Up @@ -106,6 +108,7 @@ import { Severity } from '@theia/core/lib/common/severity';
import { LinkedEditingRangeAdapter } from './languages/linked-editing-range';
import { serializeEnterRules, serializeIndentation, serializeRegExp } from './languages-utils';
import { InlayHintsAdapter } from './languages/inlay-hints';
import { InlineCompletionAdapter, InlineCompletionAdapterBase } from './languages/inline-completion';

type Adapter = CompletionAdapter |
SignatureHelpAdapter |
Expand Down Expand Up @@ -135,7 +138,8 @@ type Adapter = CompletionAdapter |
DocumentRangeSemanticTokensAdapter |
DocumentSemanticTokensAdapter |
LinkedEditingRangeAdapter |
TypeHierarchyAdapter;
TypeHierarchyAdapter |
InlineCompletionAdapter;

export class LanguagesExtImpl implements LanguagesExt {

Expand Down Expand Up @@ -275,6 +279,28 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Completion end

// ### Inline completion provider begin
registerInlineCompletionsProvider(selector: theia.DocumentSelector, provider: theia.InlineCompletionItemProvider): theia.Disposable {
const callId = this.addNewAdapter(new InlineCompletionAdapter(this.documents, provider, this.commands));
this.proxy.$registerInlineCompletionsSupport(callId, this.transformDocumentSelector(selector));
return this.createDisposable(callId);
}

$provideInlineCompletions(
handle: number,
resource: UriComponents,
position: Position,
context: InlineCompletionContext,
token: theia.CancellationToken
): Promise<IdentifiableInlineCompletions | undefined> {
return this.withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined);
}

$freeInlineCompletionsList(handle: number, pid: number): void {
this.withAdapter(handle, InlineCompletionAdapterBase, async adapter => { adapter.disposeCompletions(pid); }, undefined);
}
// ### Inline completion provider end

// ### Definition provider begin
$provideDefinition(handle: number, resource: UriComponents, position: Position, token: theia.CancellationToken): Promise<Definition | undefined> {
return this.withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position, token), undefined);
Expand Down
Loading

0 comments on commit b8a8e0d

Please sign in to comment.