diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 38220616526..602f8ba5f50 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -718,27 +718,32 @@ export interface InlineCompletion { * 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. */ - text: string; + readonly text: string; /** * The range to replace. * Must begin and end on the same line. */ - range?: IRange; + readonly range?: IRange; + + readonly command?: Command; } /** * @internal */ export interface InlineCompletions { - items: TItem[]; + readonly items: readonly TItem[]; } /** * @internal */ -export interface InlineCompletionsProvider { - provideInlineCompletions(model: model.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; +export interface InlineCompletionsProvider { + provideInlineCompletions(model: model.ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + + handleItemDidShow?(completions: T, item: T['items'][number]): void; + freeInlineCompletions(completions: T): void; } export interface CodeAction { diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts index f8e86f44aa3..f428d1f99c5 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -13,6 +13,7 @@ import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/ghostTextWi import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel'; import { SuggestWidgetAdapterModel } from 'vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel'; import * as nls from 'vs/nls'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -31,7 +32,7 @@ class GhostTextController extends Disposable { constructor( private readonly editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); @@ -55,7 +56,7 @@ class GhostTextController extends Disposable { this.activeController.value = undefined; this.activeController.value = this.editor.hasModel() && suggestOptions.showSuggestionPreview - ? new ActiveGhostTextController(this.editor, this.widget, this.contextKeys) + ? this.instantiationService.createInstance(ActiveGhostTextController, this.editor, this.widget, this.contextKeys) : undefined; } @@ -92,12 +93,13 @@ class GhostTextContextKeys { */ export class ActiveGhostTextController extends Disposable { private readonly suggestWidgetAdapterModel = new SuggestWidgetAdapterModel(this.editor); - private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor); + private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor, this.commandService); constructor( private readonly editor: IActiveCodeEditor, private readonly widget: GhostTextWidget, private readonly contextKeys: GhostTextContextKeys, + @ICommandService private readonly commandService: ICommandService, ) { super(); diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index 6c64a6cf71a..42edbf19b05 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -5,7 +5,7 @@ import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; @@ -13,9 +13,10 @@ import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from 'vs/editor/common/modes'; +import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineCompletionsProviderRegistry, InlineCompletionTriggerKind } from 'vs/editor/common/modes'; import { BaseGhostTextWidgetModel, GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget'; import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel { protected readonly onDidChangeEmitter = new Emitter(); @@ -25,7 +26,10 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge private active: boolean = false; - constructor(private readonly editor: IActiveCodeEditor) { + constructor( + private readonly editor: IActiveCodeEditor, + private readonly commandService: ICommandService + ) { super(); this._register(this.editor.onDidChangeModelContent((e) => { @@ -83,7 +87,7 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge if (this.completionSession.value) { return; } - this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active); + this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active, this.commandService); this.completionSession.value.takeOwnership( this.completionSession.value.onDidChange(() => { this.onDidChangeEmitter.fire(); @@ -120,7 +124,7 @@ class CachedInlineCompletion { public lastRange: Range; constructor( - public readonly inlineCompletion: NormalizedInlineCompletion, + public readonly inlineCompletion: LiveInlineCompletion, public readonly decorationId: string, ) { this.lastRange = inlineCompletion.range; @@ -130,19 +134,38 @@ class CachedInlineCompletion { class InlineCompletionsSession extends BaseGhostTextWidgetModel { public readonly minReservedLineCount = 0; - private updatePromise: CancelablePromise | undefined = undefined; + private updatePromise: CancelablePromise | undefined = undefined; private cachedCompletions: CachedInlineCompletion[] | undefined = undefined; + private cachedCompletionsSource: LiveInlineCompletions | undefined = undefined; private updateSoon = this._register(new RunOnceScheduler(() => this.update(), 50)); private readonly textModel = this.editor.getModel(); - constructor(editor: IActiveCodeEditor, private readonly triggerPosition: Position, private readonly shouldUpdate: () => boolean) { + constructor( + editor: IActiveCodeEditor, + private readonly triggerPosition: Position, + private readonly shouldUpdate: () => boolean, + private readonly commandService: ICommandService, + ) { super(editor); this._register(toDisposable(() => { this.clearGhostTextPromise(); this.clearCache(); })); + let lastCompletionItem: InlineCompletion | undefined = undefined; + this._register(this.onDidChange(() => { + const currentCompletion = this.currentCompletion; + if (currentCompletion && currentCompletion.sourceInlineCompletion !== lastCompletionItem) { + lastCompletionItem = currentCompletion.sourceInlineCompletion; + + const provider = currentCompletion.sourceProvider; + if (provider.handleItemDidShow) { + provider.handleItemDidShow(currentCompletion.sourceInlineCompletions, lastCompletionItem); + } + } + })); + this._register(this.editor.onDidChangeModelDecorations(e => { if (!this.cachedCompletions) { return; @@ -219,14 +242,18 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { return currentCompletion ? inlineCompletionToGhostText(currentCompletion, this.editor.getModel()) : undefined; } - get currentCompletion(): NormalizedInlineCompletion | undefined { + get currentCompletion(): LiveInlineCompletion | undefined { const completion = this.currentCachedCompletion; if (!completion) { return undefined; } return { text: completion.inlineCompletion.text, - range: completion.lastRange + range: completion.lastRange, + command: completion.inlineCompletion.command, + sourceProvider: completion.inlineCompletion.sourceProvider, + sourceInlineCompletions: completion.inlineCompletion.sourceInlineCompletions, + sourceInlineCompletion: completion.inlineCompletion.sourceInlineCompletion, }; } @@ -262,15 +289,24 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { })) ); + this.cachedCompletionsSource?.dispose(); + this.cachedCompletionsSource = result; this.cachedCompletions = result.items.map((item, idx) => new CachedInlineCompletion(item, decorationIds[idx])); this.onDidChangeEmitter.fire(); }, onUnexpectedError); } private clearCache(): void { - if (this.cachedCompletions) { - this.editor.deltaDecorations(this.cachedCompletions.map(c => c.decorationId), []); + const completions = this.cachedCompletions; + if (completions) { this.cachedCompletions = undefined; + this.editor.deltaDecorations(completions.map(c => c.decorationId), []); + + if (!this.cachedCompletionsSource) { + throw new Error('Unexpected state'); + } + this.cachedCompletionsSource.dispose(); + this.cachedCompletionsSource = undefined; } } @@ -292,7 +328,7 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { } } - public commit(completion: NormalizedInlineCompletion): void { + public commit(completion: LiveInlineCompletion): void { this.clearCache(); this.editor.executeEdits( 'inlineCompletions.accept', @@ -300,6 +336,10 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { EditOperation.replaceMove(completion.range, completion.text) ] ); + if (completion.command) { + this.commandService.executeCommand(completion.command.id, ...(completion.command.arguments || [])).then(undefined, onUnexpectedExternalError); + } + this.onDidChangeEmitter.fire(); } } @@ -308,12 +348,6 @@ export interface NormalizedInlineCompletion extends InlineCompletion { range: Range; } -/** - * Contains no duplicated items. -*/ -export interface NormalizedInlineCompletions extends InlineCompletions { -} - export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCompletion, textModel: ITextModel): GhostText | undefined { const valueToBeReplaced = textModel.getValueInRange(inlineCompletion.range); if (!inlineCompletion.text.startsWith(valueToBeReplaced)) { @@ -328,6 +362,20 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo }; } +export interface LiveInlineCompletion extends InlineCompletion { + range: Range; + sourceProvider: InlineCompletionsProvider; + sourceInlineCompletion: InlineCompletion; + sourceInlineCompletions: InlineCompletions; +} + +/** + * Contains no duplicated items. +*/ +export interface LiveInlineCompletions extends InlineCompletions { + dispose(): void; +} + function getDefaultRange(position: Position, model: ITextModel): Range { const word = model.getWordAtPosition(position); const maxColumn = model.getLineMaxColumn(position.lineNumber); @@ -343,25 +391,50 @@ async function provideInlineCompletions( model: ITextModel, context: InlineCompletionContext, token: CancellationToken = CancellationToken.None -): Promise { +): Promise { const defaultReplaceRange = getDefaultRange(position, model); const providers = InlineCompletionsProviderRegistry.all(model); const results = await Promise.all( - providers.map(provider => provider.provideInlineCompletions(model, position, context, token)) + providers.map( + async provider => { + const completions = await provider.provideInlineCompletions(model, position, context, token); + return ({ + completions, + provider, + dispose: () => { + if (completions) { + provider.freeInlineCompletions(completions); + } + } + }); + } + ) ); - const itemsByHash = new Map(); + const itemsByHash = new Map(); for (const result of results) { - if (result) { - for (const item of result.items.map(item => ({ + const completions = result.completions; + if (completions) { + for (const item of completions.items.map(item => ({ text: item.text, - range: item.range ? Range.lift(item.range) : defaultReplaceRange + range: item.range ? Range.lift(item.range) : defaultReplaceRange, + command: item.command, + sourceProvider: result.provider, + sourceInlineCompletions: completions, + sourceInlineCompletion: item }))) { itemsByHash.set(JSON.stringify({ text: item.text, range: item.range }), item); } } } - return { items: [...itemsByHash.values()] }; + return { + items: [...itemsByHash.values()], + dispose: () => { + for (const result of results) { + result.dispose(); + } + }, + }; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 10df4b473fa..48d01dc5301 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -3033,27 +3033,19 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima - export class InlineCompletionItem { - /** - * 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. - */ - text: string; - - /** - * The range to replace. - * Must begin and end on the same line. - */ - range?: Range; - - constructor(text: string); + export namespace languages { + export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; } - export class InlineCompletionList { - items: InlineCompletionItem[]; + export interface InlineCompletionItemProvider { + provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult>; + } - constructor(items: InlineCompletionItem[]); + export interface InlineCompletionContext { + /** + * How the completion was triggered. + */ + readonly triggerKind: InlineCompletionTriggerKind; } /** @@ -3072,19 +3064,56 @@ declare module 'vscode' { */ Explicit = 1, } - export interface InlineCompletionContext { + + export class InlineCompletionList { + items: T[]; + + constructor(items: T[]); + } + + export class InlineCompletionItem { /** - * How the completion was triggered. + * 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. + */ + text: string; + + /** + * The range to replace. + * Must begin and end on the same line. + */ + range?: Range; + + /** + * An optional {@link Command} that is executed *after* inserting this completion. */ - readonly triggerKind: InlineCompletionTriggerKind; + command?: Command; + + constructor(text: string, range?: Range, command?: Command); } - export interface InlineCompletionItemProvider { - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult; + + /** + * Be aware that this API will not ever be finalized. + */ + export namespace window { + export function getInlineCompletionItemController(provider: InlineCompletionItemProvider): InlineCompletionController; } - export namespace languages { - export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable; + /** + * Be aware that this API will not ever be finalized. + */ + export interface InlineCompletionController { + // 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; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index d24d5cd7c10..16a59acaf5a 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -500,9 +500,15 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $registerInlineCompletionsSupport(handle: number, selector: IDocumentFilterDto[]): void { - const provider: modes.InlineCompletionsProvider = { - provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise => { + const provider: modes.InlineCompletionsProvider = { + provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise => { return this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token); + }, + handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion): Promise => { + return this._proxy.$handleInlineCompletionDidShow(handle, completions.pid, item.idx); + }, + freeInlineCompletions: (completions: IdentifiableInlineCompletions): void => { + this._proxy.$freeInlineCompletionsList(handle, completions.pid); } }; this._registrations.set(handle, modes.InlineCompletionsProviderRegistry.register(selector, provider)); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 05e4c4a1d1d..9eb6164fddc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -28,7 +28,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 } from 'vs/workbench/api/common/extHostLanguageFeatures'; +import { ExtHostLanguageFeatures, InlineCompletionController } 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'; @@ -730,6 +730,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I get onDidChangeOpenEditors() { checkProposedApiEnabled(extension); return extHostEditorTabs.onDidChangeTabs; + }, + getInlineCompletionItemController(provider: vscode.InlineCompletionItemProvider): vscode.InlineCompletionController { + checkProposedApiEnabled(extension); + return InlineCompletionController.get(provider); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a9c6c7df8da..de99509e145 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -367,6 +367,14 @@ export interface ISignatureHelpProviderMetadataDto { readonly retriggerCharacters: readonly string[]; } +export interface IdentifiableInlineCompletions extends modes.InlineCompletions { + pid: number; +} + +export interface IdentifiableInlineCompletion extends modes.InlineCompletion { + idx: number; +} + export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void; @@ -1640,7 +1648,9 @@ export interface ExtHostLanguageFeaturesShape { $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; $resolveCompletionItem(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; - $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise; + $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise; + $handleInlineCompletionDidShow(handle: number, pid: number, idx: number): void; + $freeInlineCompletionsList(handle: number, pid: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; $releaseSignatureHelp(handle: number, id: number): void; $provideInlayHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 265585c0771..adaa21ddb4c 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -33,6 +33,7 @@ import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostAp import { Cache } from './cache'; import { StopWatch } from 'vs/base/common/stopwatch'; import { CancellationError } from 'vs/base/common/errors'; +import { Emitter } from 'vs/base/common/event'; // --- adapter @@ -1038,15 +1039,17 @@ class SuggestAdapter { } } -class InlineCompletionsAdapter { +class InlineCompletionAdapter { + private readonly _cache = new Cache('InlineCompletionItem'); + private readonly _disposables = new Map(); constructor( private readonly _documents: ExtHostDocuments, private readonly _provider: vscode.InlineCompletionItemProvider, + private readonly _commands: CommandsConverter, ) { } - async provideInlineCompletions(resource: URI, position: IPosition, context: vscode.InlineCompletionContext, token: CancellationToken): Promise { - + public async provideInlineCompletions(resource: URI, position: IPosition, context: vscode.InlineCompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -1063,13 +1066,69 @@ class InlineCompletionsAdapter { return undefined; } + const pid = this._cache.add(result.items); + + let disposableStore: DisposableStore | undefined = undefined; + return { - items: result.items.map(item => ({ - text: item.text, - range: item.range ? typeConvert.Range.from(item.range) : undefined, - })), + pid, + items: result.items.map((item, idx) => { + let command: modes.Command | undefined = undefined; + if (item.command) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + this._disposables.set(pid, disposableStore); + } + command = this._commands.toInternal(item.command, disposableStore); + } + + return ({ + text: item.text, + range: item.range ? typeConvert.Range.from(item.range) : undefined, + command, + idx: idx, + }); + }), }; } + + public disposeCompletions(pid: number) { + this._cache.delete(pid); + const d = this._disposables.get(pid); + if (d) { + d.clear(); + } + this._disposables.delete(pid); + } + + public handleDidShowCompletionItem(pid: number, idx: number): void { + const completionItem = this._cache.get(pid, idx); + if (completionItem) { + InlineCompletionController.get(this._provider).fireOnDidShowCompletionItem({ + completionItem + }); + } + } +} + +export class InlineCompletionController implements vscode.InlineCompletionController { + private static readonly map = new WeakMap, InlineCompletionController>(); + + public 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>(); + public readonly onDidShowCompletionItem: vscode.Event> = this._onDidShowCompletionItemEmitter.event; + + public fireOnDidShowCompletionItem(event: vscode.InlineCompletionItemDidShowEvent): void { + this._onDidShowCompletionItemEmitter.fire(event); + } } class SignatureHelpAdapter { @@ -1389,7 +1448,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter | InlineValuesAdapter - | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionsAdapter; + | LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter; class AdapterData { constructor( @@ -1846,15 +1905,24 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- ghost test registerInlineCompletionsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider): vscode.Disposable { - const handle = this._addNewAdapter(new InlineCompletionsAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new InlineCompletionAdapter(this._documents, provider, this._commands.converter), extension); this._proxy.$registerInlineCompletionsSupport(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise { - return this._withAdapter(handle, InlineCompletionsAdapter, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined); + $provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: modes.InlineCompletionContext, token: CancellationToken): Promise { + return this._withAdapter(handle, InlineCompletionAdapter, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined); } + $handleInlineCompletionDidShow(handle: number, pid: number, idx: number): void { + this._withAdapter(handle, InlineCompletionAdapter, async adapter => { + adapter.handleDidShowCompletionItem(pid, idx); + }, undefined); + } + + $freeInlineCompletionsList(handle: number, pid: number): void { + this._withAdapter(handle, InlineCompletionAdapter, async adapter => { adapter.disposeCompletions(pid); }, undefined); + } // --- parameter hints diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index fdccb8c119c..cca39a130b3 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1552,9 +1552,12 @@ export class InlineSuggestion implements vscode.InlineCompletionItem { text: string; range?: Range; + command?: vscode.Command; - constructor(text: string) { + constructor(text: string, range?: Range, command?: vscode.Command) { this.text = text; + this.range = range; + this.command = command; } }