From 3b08d52d0b20df1de3675989befdb39e8dcdd4bd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 5 May 2022 07:40:58 +0200 Subject: [PATCH] Updates inlineCompletions proposal from inlineCompletionsNew proposal. Also moves InlineCompletionList.commands to inlineCompletionsAdditions proposal. --- .../workbench/api/common/extHost.api.impl.ts | 11 +- .../api/common/extHostLanguageFeatures.ts | 86 ++++---- src/vs/workbench/api/common/extHostTypes.ts | 27 +-- .../vscode.proposed.inlineCompletions.d.ts | 186 ++++++++---------- ...e.proposed.inlineCompletionsAdditions.d.ts | 21 ++ 5 files changed, 162 insertions(+), 169 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 864b227de8e..1e5d381833e 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -29,7 +29,7 @@ import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocu import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; -import { ExtHostLanguageFeatures, InlineCompletionController } from 'vs/workbench/api/common/extHostLanguageFeatures'; +import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; @@ -526,6 +526,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, registerInlineCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable { checkProposedApiEnabled(extension, 'inlineCompletions'); + if (provider.handleDidShowCompletionItem && !isProposedApiEnabled(extension, 'inlineCompletionsAdditions')) { + throw new Error(`When the method "handleDidShowCompletionItem" is implemented on a provider, the usage of the proposed api 'inlineCompletionsAdditions' must be declared!`); + } return extHostLanguageFeatures.registerInlineCompletionsProvider(extension, checkSelector(selector), provider); }, registerInlineCompletionItemProviderNew(selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProviderNew): vscode.Disposable { @@ -792,10 +795,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get tabGroups(): vscode.TabGroups { return extHostEditorTabs.tabGroups; }, - getInlineCompletionItemController(provider: vscode.InlineCompletionItemProvider): vscode.InlineCompletionController { - checkProposedApiEnabled(extension, 'inlineCompletions'); - return InlineCompletionController.get(provider); - }, }; // namespace: workspace @@ -1244,7 +1243,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I FunctionBreakpoint: extHostTypes.FunctionBreakpoint, InlineCompletionItem: extHostTypes.InlineSuggestion, InlineCompletionItemNew: extHostTypes.InlineSuggestionNew, - InlineCompletionList: extHostTypes.InlineSuggestions, + InlineCompletionList: extHostTypes.InlineSuggestionList, InlineCompletionListNew: extHostTypes.InlineSuggestionsNew, Hover: extHostTypes.Hover, IndentAction: languageConfiguration.IndentAction, diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index ef72e012647..bad4af0fba1 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import * as languages from 'vs/editor/common/languages'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -32,7 +32,6 @@ import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostAp import { Cache } from './cache'; import { StopWatch } from 'vs/base/common/stopwatch'; import { isCancellationError } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DataTransferConverter, DataTransferDTO } from 'vs/workbench/api/common/shared/dataTransfer'; @@ -1033,10 +1032,15 @@ class InlineCompletionAdapterBase { } class InlineCompletionAdapter extends InlineCompletionAdapterBase { - private readonly _cache = new Cache('InlineCompletionItem'); - private readonly _disposables = new Map(); + private readonly _references = new ReferenceMap<{ + dispose(): void; + items: readonly vscode.InlineCompletionItem[]; + }>(); + + private readonly isAdditionProposedApiEnabled = isProposedApiEnabled(this.extension, 'inlineCompletionsAdditions'); constructor( + private readonly extension: IExtensionDescription, private readonly _documents: ExtHostDocuments, private readonly _provider: vscode.InlineCompletionItemProvider, private readonly _commands: CommandsConverter, @@ -1044,6 +1048,11 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { super(); } + private readonly languageTriggerKindToVSCodeTriggerKind: Record = { + [languages.InlineCompletionTriggerKind.Automatic]: InlineCompletionTriggerKind.Automatic, + [languages.InlineCompletionTriggerKind.Explicit]: InlineCompletionTriggerKind.Invoke, + }; + override async provideInlineCompletions(resource: URI, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -1053,12 +1062,10 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { context.selectedSuggestionInfo ? { range: typeConvert.Range.to(context.selectedSuggestionInfo.range), - text: context.selectedSuggestionInfo.text, - isSnippetText: context.selectedSuggestionInfo.isSnippetText, - completionKind: typeConvert.CompletionItemKind.to(context.selectedSuggestionInfo.completionKind), + text: context.selectedSuggestionInfo.text } : undefined, - triggerKind: context.triggerKind + triggerKind: this.languageTriggerKindToVSCodeTriggerKind[context.triggerKind] }, token); if (!result) { @@ -1073,9 +1080,17 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { } const normalizedResult = isArray(result) ? result : result.items; + const commands = isArray(result) ? [] : result.commands || []; - const pid = this._cache.add(normalizedResult); let disposableStore: DisposableStore | undefined = undefined; + const pid = this._references.createReferenceId({ + dispose() { + if (disposableStore) { + disposableStore.dispose(); + } + }, + items: normalizedResult + }); return { pid, @@ -1084,41 +1099,40 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase { if (item.command) { if (!disposableStore) { disposableStore = new DisposableStore(); - this._disposables.set(pid, disposableStore); } command = this._commands.toInternal(item.command, disposableStore); } - const insertText = item.insertText ?? item.text; - if (insertText === undefined) { - throw new Error('text or insertText must be defined'); - } + const insertText = item.insertText; return ({ insertText: typeof insertText === 'string' ? insertText : { snippet: insertText.value }, + filterText: item.filterText, range: item.range ? typeConvert.Range.from(item.range) : undefined, command, idx: idx, - completeBracketPairs: item.completeBracketPairs + completeBracketPairs: this.isAdditionProposedApiEnabled ? item.completeBracketPairs : false }); }), + commands: commands.map(c => { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + return this._commands.toInternal(c, disposableStore); + }) }; } override disposeCompletions(pid: number) { - this._cache.delete(pid); - const d = this._disposables.get(pid); - if (d) { - d.clear(); - } - this._disposables.delete(pid); + const data = this._references.disposeReferenceId(pid); + data?.dispose(); } override handleDidShowCompletionItem(pid: number, idx: number): void { - const completionItem = this._cache.get(pid, idx); + const completionItem = this._references.get(pid)?.items[idx]; if (completionItem) { - InlineCompletionController.get(this._provider).fireOnDidShowCompletionItem({ - completionItem - }); + if (this._provider.handleDidShowCompletionItem && this.isAdditionProposedApiEnabled) { + this._provider.handleDidShowCompletionItem(completionItem); + } } } } @@ -1250,26 +1264,6 @@ class ReferenceMap { } } -export class InlineCompletionController implements vscode.InlineCompletionController { - private static readonly map = new WeakMap, InlineCompletionController>(); - - static get(provider: vscode.InlineCompletionItemProvider): InlineCompletionController { - let existing = InlineCompletionController.map.get(provider); - if (!existing) { - existing = new InlineCompletionController(); - InlineCompletionController.map.set(provider, existing); - } - return existing; - } - - private readonly _onDidShowCompletionItemEmitter = new Emitter>(); - readonly onDidShowCompletionItem: vscode.Event> = this._onDidShowCompletionItemEmitter.event; - - fireOnDidShowCompletionItem(event: vscode.InlineCompletionItemDidShowEvent): void { - this._onDidShowCompletionItemEmitter.fire(event); - } -} - class SignatureHelpAdapter { private readonly _cache = new Cache('SignatureHelp'); @@ -2210,7 +2204,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- ghost test registerInlineCompletionsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable { - const handle = this._addNewAdapter(new InlineCompletionAdapter(this._documents, provider, this._commands.converter), extension); + const handle = this._addNewAdapter(new InlineCompletionAdapter(extension, this._documents, provider, this._commands.converter), extension); this._proxy.$registerInlineCompletionsSupport(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a69e00f05ec..e369c9b192e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1595,33 +1595,24 @@ export class CompletionList { @es5ClassCompat export class InlineSuggestion implements vscode.InlineCompletionItem { - insertText?: string | SnippetString; - - filterText?: string; - - /** - * @deprecated Use `insertText` instead. Will be removed eventually. - */ - text?: string; - + insertText: string; range?: Range; command?: vscode.Command; - constructor(insertText: string | SnippetString, range?: Range, command?: vscode.Command) { + constructor(insertText: string, range?: Range, command?: vscode.Command) { this.insertText = insertText; this.range = range; this.command = command; } } -/** - * @deprecated Return an array of inline completion items directly. Will be removed eventually. -*/ @es5ClassCompat -export class InlineSuggestions implements vscode.InlineCompletionList { - items: vscode.InlineCompletionItem[]; +export class InlineSuggestionList implements vscode.InlineCompletionList { + items: vscode.InlineCompletionItemNew[]; - constructor(items: vscode.InlineCompletionItem[]) { + commands: vscode.Command[] | undefined = undefined; + + constructor(items: vscode.InlineCompletionItemNew[]) { this.items = items; } } @@ -2622,8 +2613,8 @@ export class EvaluatableExpression implements vscode.EvaluatableExpression { } export enum InlineCompletionTriggerKind { - Automatic = 0, - Explicit = 1, + Invoke = 0, + Automatic = 1, } export enum InlineCompletionTriggerKindNew { diff --git a/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts index bf01c4dca4b..c070547c967 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts @@ -8,28 +8,67 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima export namespace languages { + /** * Registers an inline completion provider. * - * @return A {@link Disposable} that unregisters this provider when being disposed. + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An inline completion provider. + * @return A {@link Disposable} that unregisters this provider when being disposed. */ - // TODO@API what are the rules when multiple providers apply export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; } - export interface InlineCompletionItemProvider { + /** + * The inline completion item provider interface defines the contract between extensions and + * the inline completion feature. + * + * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. + */ + export interface InlineCompletionItemProvider { + /** * Provides inline completion items for the given position and document. * If inline completions are enabled, this method will be called whenever the user stopped typing. - * It will also be called when the user explicitly triggers inline completions or asks for the next or previous inline completion. - * Use `context.triggerKind` to distinguish between these scenarios. - */ - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult | T[]>; + * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. + * In that case, all available inline completions should be returned. + * `context.triggerKind` can be used to distinguish between these scenarios. + * + * @param document The document inline completions are requested for. + * @param position The position inline completions are requested for. + * @param context A context object with additional information. + * @param token A cancellation token. + * @return An array of completion items or a thenable that resolves to an array of completion items. + */ + provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; } + /** + * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented + * in the editor. + */ + export class InlineCompletionList { + /** + * The inline completion items. + */ + items: InlineCompletionItem[]; + + /** + * Creates a new list of inline completion items. + */ + constructor(items: InlineCompletionItem[]); + } + + /** + * Provides information about the context in which an inline completion was requested. + */ export interface InlineCompletionContext { /** - * How the completion was triggered. + * Describes how the inline completion was triggered. */ readonly triggerKind: InlineCompletionTriggerKind; @@ -42,89 +81,68 @@ declare module 'vscode' { * the inline completion must also replace `.` and start with `.log`, for example `.log()`. * * Inline completion providers are requested again whenever the selected item changes. - * - * The user must configure `"editor.suggest.preview": true` for this feature. - */ + */ readonly selectedCompletionInfo: SelectedCompletionInfo | undefined; } - // TODO@API remove kind, snippet properties - // TODO@API find a better name, xyzFilter, xyzConstraint + /** + * Describes the currently selected completion item. + */ export interface SelectedCompletionInfo { - range: Range; - text: string; + /** + * The range that will be replaced if this completion item is accepted. + */ + readonly range: Range; - - completionKind: CompletionItemKind; - isSnippetText: boolean; + /** + * The text the range will be replaced with if this completion is accepted. + */ + readonly text: string; } /** - * How an {@link InlineCompletionItemProvider inline completion provider} was triggered. + * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. */ - // TODO@API align with CodeActionTriggerKind - // (1) rename Explicit to Invoke - // (2) swap order of Invoke and Automatic 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, + Invoke = 0, + + /** + * Completion was triggered automatically while editing. + * It is sufficient to return a single completion item in this case. + */ + Automatic = 1, } /** - * @deprecated Return an array of Inline Completion items directly. Will be removed eventually. - */ - // TODO@API We could keep this and allow for `vscode.Command` instances that explain - // the result. That would replace the existing proposed menu-identifier and be more LSP friendly - // TODO@API maybe use MarkdownString - export class InlineCompletionList { - items: T[]; - - // command: Command; "Show More..." - - // description: MarkdownString - - /** - * @deprecated Return an array of Inline Completion items directly. Will be removed eventually. - */ - constructor(items: T[]); - } - + * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. + * + * @see {@link InlineCompletionItemProvider.provideInlineCompletionItems} + */ export class InlineCompletionItem { /** * The text to replace the range with. Must be set. * Is used both for the preview and the accept operation. - * - * The text the range refers to must be a subword of this value (`AB` and `BEF` are subwords of `ABCDEF`, but `Ab` is not). - * Additionally, if possible, it should be a prefix of this value for a better user-experience. - * - * However, any indentation of the text to replace does not matter for the subword constraint. - * Thus, ` B` can be replaced with ` ABC`, effectively removing a whitespace and inserting `A` and `C`. - */ - insertText?: string | SnippetString; + */ + insertText: string | SnippetString; /** - * @deprecated Use `insertText` instead. Will be removed eventually. - */ - text?: string; + * A text that is used to decide if this inline completion should be shown. When `falsy` + * the {@link InlineCompletionItem.insertText} is used. + * + * An inline completion is shown if the text to replace is a prefix of the filter text. + */ + filterText?: string; /** * The range to replace. * Must begin and end on the same line. * - * Prefer replacements over insertions to avoid cache invalidation: - * Instead of reporting a completion that inserts an extension at the end of a word, - * the whole word (or even the whole line) should be replaced with the extended word (or extended line) to improve the UX. - * That way, when the user presses backspace, the cache can be reused and there is no flickering. - */ + * Prefer replacements over insertions to provide a better experience when the user deletes typed text. + */ range?: Range; /** @@ -132,43 +150,13 @@ declare module 'vscode' { */ command?: Command; - constructor(insertText: string, range?: Range, command?: Command); - } - - - // TODO@API move "never" API into new proposal - - export interface InlineCompletionItem { /** - * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. - * Defaults to `false`. - */ - completeBracketPairs?: boolean; - } - - /** - * Be aware that this API will not ever be finalized. - */ - export namespace window { - // TODO@API move into provider (just like internal API). Only read property if proposal is enabled! - export function getInlineCompletionItemController(provider: InlineCompletionItemProvider): InlineCompletionController; - } - - /** - * Be aware that this API will not ever be finalized. - */ - export interface InlineCompletionController { - /** - * Is fired when an inline completion item is shown to the user. + * Creates a new inline completion item. + * + * @param insertText The text to replace the range with. + * @param range The range to replace. If not set, the word at the requested position will be used. + * @param command An optional {@link Command} that is executed *after* inserting this completion. */ - // eslint-disable-next-line vscode-dts-event-naming - readonly onDidShowCompletionItem: Event>; - } - - /** - * Be aware that this API will not ever be finalized. - */ - export interface InlineCompletionItemDidShowEvent { - completionItem: T; + constructor(insertText: string | SnippetString, range?: Range, command?: Command); } } diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index c79cf048df9..f1fbb770d8e 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -19,4 +19,25 @@ declare module 'vscode' { // eslint-disable-next-line vscode-dts-provider-naming handleDidShowCompletionItem?(completionItem: InlineCompletionItemNew): void; } + + export interface InlineCompletionItem { + /** + * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. + * Defaults to `false`. + */ + completeBracketPairs?: boolean; + } + + export interface InlineCompletionItemProvider { + // eslint-disable-next-line vscode-dts-provider-naming + handleDidShowCompletionItem?(completionItem: InlineCompletionItem): void; + } + + // When finalizing `commands`, make sure to add a corresponding constructor parameter. + export interface InlineCompletionList { + /** + * A list of commands associated with the inline completions of this list. + */ + commands?: Command[]; + } }