diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index 7333be519ae..bac5c3c4005 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -20,7 +20,7 @@ "vs/editor/browser/view/domLineBreaksComputer.ts", "vs/editor/browser/view/viewLayer.ts", "vs/editor/browser/widget/diffEditorWidget.ts", - "vs/editor/contrib/inlineSuggestions/ghostTextWidget.ts", + "vs/editor/contrib/inlineCompletions/ghostTextWidget.ts", "vs/editor/browser/widget/diffReview.ts", "vs/editor/standalone/browser/colorizer.ts", "vs/workbench/api/worker/extHostExtensionService.ts", diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index afaa0c0b57b..38220616526 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1829,7 +1829,7 @@ export const CompletionProviderRegistry = new LanguageFeatureRegistry(); +export const InlineCompletionsProviderRegistry = new LanguageFeatureRegistry(); /** * @internal diff --git a/src/vs/editor/contrib/inlineSuggestions/ghostText.css b/src/vs/editor/contrib/inlineCompletions/ghostText.css similarity index 100% rename from src/vs/editor/contrib/inlineSuggestions/ghostText.css rename to src/vs/editor/contrib/inlineCompletions/ghostText.css diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts new file mode 100644 index 00000000000..a165019671f --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget'; +import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel'; +import { SuggestWidgetAdapterModel } from 'vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel'; +import * as nls from 'vs/nls'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +class GhostTextController extends Disposable { + public static readonly inlineCompletionsVisible = new RawContextKey('inlineCompletionsVisible ', false, nls.localize('inlineCompletionsVisible', "Whether inline suggestions are visible")); + static ID = 'editor.contrib.ghostTextController'; + + public static get(editor: ICodeEditor): GhostTextController { + return editor.getContribution(GhostTextController.ID); + } + + private readonly widget: GhostTextWidget; + private readonly activeController = this._register(new MutableDisposable()); + + private readonly contextKeys: GhostTextContextKeys; + + constructor( + private readonly editor: ICodeEditor, + @IInstantiationService instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + super(); + this.contextKeys = new GhostTextContextKeys(contextKeyService); + + this.widget = this._register(instantiationService.createInstance(GhostTextWidget, this.editor)); + + this._register(this.editor.onDidChangeModel(() => { + this.updateModelController(); + })); + this._register(this.editor.onDidChangeConfiguration((e) => { + if (e.hasChanged(EditorOption.suggest)) { + this.updateModelController(); + } + })); + this.updateModelController(); + } + + private updateModelController(): void { + const suggestOptions = this.editor.getOption(EditorOption.suggest); + + this.activeController.value = undefined; + this.activeController.value = this.editor.hasModel() && suggestOptions.showSuggestionPreview + ? new ActiveGhostTextController(this.editor, this.widget, this.contextKeys) + : undefined; + } + + public trigger(): void { + if (this.activeController.value) { + this.activeController.value.trigger(); + } + } + + public commit(): void { + if (this.activeController.value) { + this.activeController.value.commit(); + } + } + + public hide(): void { + if (this.activeController.value) { + this.activeController.value.hide(); + } + } +} + +class GhostTextContextKeys { + public readonly inlineCompletionVisible = GhostTextController.inlineCompletionsVisible.bindTo(this.contextKeyService); + + constructor(private readonly contextKeyService: IContextKeyService) { + } +} + +/** + * The controller for a text editor with an initialized text model. +*/ +export class ActiveGhostTextController extends Disposable { + private readonly suggestWidgetAdapterModel = new SuggestWidgetAdapterModel(this.editor); + private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor); + + constructor( + private readonly editor: IActiveCodeEditor, + private readonly widget: GhostTextWidget, + private readonly contextKeys: GhostTextContextKeys, + ) { + super(); + + this._register(this.suggestWidgetAdapterModel.onDidChange(() => { + this.updateModel(); + })); + + this.updateModel(); + + this._register(toDisposable(() => { + if (widget.model === this.suggestWidgetAdapterModel || widget.model === this.inlineCompletionsModel) { + widget.setModel(undefined); + } + })); + + this._register(this.inlineCompletionsModel.onDidChange(() => { + this.updateContextKeys(); + })); + } + + private updateContextKeys(): void { + this.contextKeys.inlineCompletionVisible.set( + this.widget.model === this.inlineCompletionsModel + && this.inlineCompletionsModel.ghostText !== undefined + ); + } + + public trigger(): void { + if (this.widget.model === this.inlineCompletionsModel) { + this.inlineCompletionsModel.startSession(); + } + } + + public commit(): void { + if (this.widget.model === this.inlineCompletionsModel) { + this.inlineCompletionsModel.commitCurrentSuggestion(); + } + } + + public hide(): void { + if (this.widget.model === this.inlineCompletionsModel) { + this.inlineCompletionsModel.hide(); + } + } + + private updateModel() { + this.widget.setModel(this.suggestWidgetAdapterModel.isActive ? this.suggestWidgetAdapterModel : this.inlineCompletionsModel); + this.inlineCompletionsModel.setActive(this.widget.model === this.inlineCompletionsModel); + } +} + +const GhostTextCommand = EditorCommand.bindToContribution(GhostTextController.get); + +registerEditorCommand(new GhostTextCommand({ + id: 'commitInlineCompletion', + precondition: GhostTextController.inlineCompletionsVisible, + kbOpts: { + weight: 100, + primary: KeyCode.Tab, + }, + handler(x) { + x.commit(); + } +})); + +registerEditorCommand(new GhostTextCommand({ + id: 'hideInlineCompletion', + precondition: GhostTextController.inlineCompletionsVisible, + kbOpts: { + weight: 100, + primary: KeyCode.Escape, + }, + handler(x) { + x.hide(); + } +})); + +export class TriggerInlineCompletionsAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.triggerInlineCompletions', + label: nls.localize('triggerInlineCompletionsAction', "Trigger Inline Completions"), + alias: 'Trigger Inline Completions', + precondition: EditorContextKeys.writable + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const controller = GhostTextController.get(editor); + if (controller) { + controller.trigger(); + } + } +} + +registerEditorContribution(GhostTextController.ID, GhostTextController); +registerEditorAction(TriggerInlineCompletionsAction); diff --git a/src/vs/editor/contrib/inlineSuggestions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts similarity index 88% rename from src/vs/editor/contrib/inlineSuggestions/ghostTextWidget.ts rename to src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index 0310d71c060..f228c076328 100644 --- a/src/vs/editor/contrib/inlineSuggestions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -16,53 +16,48 @@ import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Position } from 'vs/editor/common/core/position'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorSuggestPreviewBorder, editorSuggestPreviewOpacity } from 'vs/editor/common/view/editorColorRegistry'; const ttPolicy = window.trustedTypes?.createPolicy('editorGhostText', { createHTML: value => value }); +export interface GhostTextWidgetModel { + readonly onDidChange: Event; + readonly ghostText: GhostText | undefined; + + expand(): void; + readonly expanded: boolean; + + readonly minReservedLineCount: number; +} + export interface GhostText { - lines: string[]; - position: Position; - minAdditionalLineCount: number; - multiline: boolean; - expandCallback: () => void; + readonly lines: string[]; + readonly position: Position; } -// TODO: use connors interface, maybe move to common? -export interface IObservableValue { - onDidChange: Event; - readonly value: T; -} +export abstract class BaseGhostTextWidgetModel extends Disposable implements GhostTextWidgetModel { + public abstract readonly ghostText: GhostText | undefined; -export class ObservableValue implements IObservableValue { - private _value: T; - private readonly onDidChangeEmitter = new Emitter(); + private _expanded = false; + + protected readonly onDidChangeEmitter = new Emitter(); public readonly onDidChange = this.onDidChangeEmitter.event; - constructor(value: T) { - this._value = value; + public abstract readonly minReservedLineCount: number; + + public get expanded() { + return this._expanded; } - get value() { return this._value; } - - public setValue(value: T): void { - this._value = value; - this.onDidChangeEmitter.fire(this._value); + public expand(): void { + this._expanded = true; + this.onDidChangeEmitter.fire(); } } -export type GhostTextWidgetModel = IObservableValue; - -function createDisposableRef(object: T, disposable: IDisposable): IReference { - return { - object, - dispose: () => disposable.dispose(), - }; -} - export class GhostTextWidget extends Disposable { private static instanceCount = 0; @@ -112,23 +107,25 @@ export class GhostTextWidget extends Disposable { } private getRenderData() { - if (!this.editor.hasModel() || !this.model?.value) { + if (!this.editor.hasModel() || !this.model?.ghostText) { return undefined; } - let { position, lines, minAdditionalLineCount, multiline, expandCallback } = this.model?.value; + const { minReservedLineCount, expanded } = this.model; + let { position, lines } = this.model.ghostText; const textModel = this.editor.getModel(); const maxColumn = textModel.getLineMaxColumn(position.lineNumber); const { tabSize } = textModel.getOptions(); - if (position.column !== maxColumn) { + // TODO enable single line decorations that are not at the end of the line + if (/*lines.length > 1 &&*/ position.column !== maxColumn) { console.warn('Can only show multiline ghost text at the end of a line'); lines = []; position = new Position(position.lineNumber, maxColumn); } - return { tabSize, position, lines, minAdditionalLineCount, multiline, expandCallback }; + return { tabSize, position, lines, minReservedLineCount, expanded }; } private render(): void { @@ -177,9 +174,9 @@ export class GhostTextWidget extends Disposable { if (renderData) { const remainingLines = renderData.lines.slice(1); - const heightInLines = Math.max(remainingLines.length, renderData.minAdditionalLineCount); + const heightInLines = Math.max(remainingLines.length, renderData.minReservedLineCount); if (heightInLines > 0) { - if (renderData.multiline) { + if (renderData.expanded) { const domNode = document.createElement('div'); this.renderLines(domNode, renderData.tabSize, remainingLines); @@ -190,14 +187,14 @@ export class GhostTextWidget extends Disposable { domNode, }); } else if (remainingLines.length > 0) { - this.viewMoreContentWidget = this.renderViewMoreLines(renderData.position, renderData.lines[0], remainingLines.length, renderData.expandCallback); + this.viewMoreContentWidget = this.renderViewMoreLines(renderData.position, renderData.lines[0], remainingLines.length); } } } }); } - private renderViewMoreLines(position: Position, firstLineText: string, remainingLinesLength: number, expandCallback: () => void): ViewMoreLinesContentWidget { + private renderViewMoreLines(position: Position, firstLineText: string, remainingLinesLength: number): ViewMoreLinesContentWidget { const fontInfo = this.editor.getOption(EditorOption.fontInfo); const domNode = document.createElement('div'); domNode.className = 'suggest-preview-additional-widget'; @@ -220,7 +217,7 @@ export class GhostTextWidget extends Disposable { button.append(`+${remainingLinesLength} lines…`); disposableStore.add(dom.addStandardDisposableListener(button, 'click', (e) => { - expandCallback(); + this.model?.expand(); e.preventDefault(); this.editor.focus(); })); @@ -331,3 +328,10 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .suggest-preview-text { border-bottom: 2px dashed ${suggestPreviewBorder}; }`); } }); + +function createDisposableRef(object: T, disposable: IDisposable): IReference { + return { + object, + dispose: () => disposable.dispose(), + }; +} diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts new file mode 100644 index 00000000000..a65641ee2b1 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -0,0 +1,300 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { onUnexpectedError } 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'; +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 { BaseGhostTextWidgetModel, GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; + +export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel { + protected readonly onDidChangeEmitter = new Emitter(); + public readonly onDidChange = this.onDidChangeEmitter.event; + + private readonly completionSession = this._register(new MutableDisposable()); + + private readonly textModel = this.editor.getModel(); + private active: boolean = false; + + constructor(private readonly editor: IActiveCodeEditor) { + super(); + + this._register(this.editor.onDidChangeModelContent((e) => { + if (this.session && !this.session.isValid) { + this.hide(); + } + setTimeout(() => { + this.startSessionIfTriggered(); + }, 0); + })); + + this._register(this.editor.onDidChangeCursorPosition((e) => { + if (this.session && !this.session.isValid) { + this.hide(); + } + })); + } + + private get session(): InlineCompletionsSession | undefined { + return this.completionSession.value; + } + + public get ghostText(): GhostText | undefined { + return this.session?.ghostText; + } + + public get minReservedLineCount(): number { + return this.session ? this.session.minReservedLineCount : 0; + } + + public get expanded(): boolean { + return this.session ? this.session.expanded : false; + } + + public expand(): void { + this.session?.expand(); + } + + public setActive(active: boolean) { + this.active = active; + if (active) { + this.session?.scheduleUpdate(); + } + } + + private startSessionIfTriggered(): void { + if (this.session && this.session.isValid) { + return; + } + + this.startSession(); + } + + public startSession(): void { + if (this.completionSession.value) { + return; + } + this.completionSession.value = new InlineCompletionsSession(this.editor, this.editor.getPosition(), () => this.active); + this.completionSession.value.takeOwnership( + this.completionSession.value.onDidChange(() => { + this.onDidChangeEmitter.fire(); + }) + ); + } + + public hide(): void { + this.completionSession.clear(); + this.onDidChangeEmitter.fire(); + } + + public commitCurrentSuggestion(): void { + if (this.session) { + this.session.commitCurrentCompletion(); + } + } +} + +interface CachedInlineCompletion { + inlineCompletion: NormalizedInlineCompletion; + decorationId: string; + lastRange: Range; +} + +class InlineCompletionsSession extends BaseGhostTextWidgetModel { + public readonly minReservedLineCount = 0; + + private updatePromise: CancelablePromise | undefined = undefined; + private cachedCompletions: CachedInlineCompletion[] | undefined = undefined; + + private updateSoon = this._register(new RunOnceScheduler(() => this.update(), 50)); + private readonly textModel = this.editor.getModel(); + + constructor(private readonly editor: IActiveCodeEditor, private readonly triggerPosition: Position, private readonly shouldUpdate: () => boolean) { + super(); + this._register(toDisposable(() => { + this.clearGhostTextPromise(); + })); + + this._register(this.editor.onDidChangeModelDecorations(e => { + if (!this.cachedCompletions) { + return; + } + + let hasChanged = false; + for (const c of this.cachedCompletions) { + const newRange = this.textModel.getDecorationRange(c.decorationId); + if (!newRange) { + onUnexpectedError(new Error('Decoration has no range')); + continue; + } + if (!c.lastRange.equalsRange(newRange)) { + hasChanged = true; + c.lastRange = newRange; + } + } + if (hasChanged) { + this.onDidChangeEmitter.fire(); + } + })); + + this._register(this.editor.onDidChangeModelContent((e) => { + this.updateSoon.schedule(); + })); + + this.updateSoon.schedule(); + } + + public get ghostText(): GhostText | undefined { + const currentCompletion = this.currentCompletion; + return currentCompletion ? inlineCompletionToGhostText(currentCompletion, this.editor.getModel()) : undefined; + } + + get currentCompletion(): NormalizedInlineCompletion | undefined { + if (!this.cachedCompletions) { + return undefined; + } + const completion = this.cachedCompletions[0]; + if (!completion) { + return undefined; + } + return { + text: completion.inlineCompletion.text, + range: completion.lastRange + }; + } + + get isValid(): boolean { + return this.editor.getPosition().lineNumber === this.triggerPosition.lineNumber; + } + + public scheduleUpdate(): void { + this.updateSoon.schedule(); + } + + private update(): void { + if (!this.shouldUpdate()) { + return; + } + + const position = this.editor.getPosition(); + this.clearGhostTextPromise(); + this.updatePromise = createCancelablePromise(token => + provideInlineCompletions(position, + this.editor.getModel(), + { triggerKind: InlineCompletionTriggerKind.Automatic }, + token + ) + ); + this.updatePromise.then((result) => { + this.cachedCompletions = []; + const decorationIds = this.editor.deltaDecorations( + (this.cachedCompletions || []).map(c => c.decorationId), + (result.items).map(i => ({ + range: i.range, + options: {}, + })) + ); + + this.cachedCompletions = result.items.map((item, idx) => ({ + decorationId: decorationIds[idx], + inlineCompletion: item, + lastRange: item.range + })); + this.onDidChangeEmitter.fire(); + }, onUnexpectedError); + } + + private clearGhostTextPromise(): void { + if (this.updatePromise) { + this.updatePromise.cancel(); + this.updatePromise = undefined; + } + } + + public takeOwnership(disposable: IDisposable): void { + this._register(disposable); + } + + public commitCurrentCompletion(): void { + const completion = this.currentCompletion; + if (completion) { + this.commit(completion); + } + } + + public commit(completion: NormalizedInlineCompletion): void { + this.editor.executeEdits( + 'inlineCompletions.accept', + [ + EditOperation.replaceMove(completion.range, completion.text) + ] + ); + } +} + +export interface NormalizedInlineCompletion extends InlineCompletion { + range: Range; +} + +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)) { + return undefined; + } + + const lines = strings.splitLines(inlineCompletion.text.substr(valueToBeReplaced.length)); + + return { + lines, + position: inlineCompletion.range.getEndPosition() + }; +} + +function getDefaultRange(position: Position, model: ITextModel): Range { + const word = model.getWordAtPosition(position); + const maxColumn = model.getLineMaxColumn(position.lineNumber); + // By default, always replace up until the end of the current line. + // This default might be subject to change! + return word + ? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn) + : Range.fromPositions(position, position.with(undefined, maxColumn)); +} + +async function provideInlineCompletions( + position: Position, + model: ITextModel, + context: InlineCompletionContext, + token: CancellationToken = CancellationToken.None +): 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)) + ); + + const items = new Array(); + for (const result of results) { + if (result) { + items.push(...result.items.map(item => ({ + text: item.text, + range: item.range ? Range.lift(item.range) : defaultReplaceRange + }))); + } + } + + return { items }; +} diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts new file mode 100644 index 00000000000..316ffb02f6b --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { CompletionItemInsertTextRule } from 'vs/editor/common/modes'; +import { BaseGhostTextWidgetModel, GhostText } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget'; +import { inlineCompletionToGhostText, NormalizedInlineCompletion } from 'vs/editor/contrib/inlineCompletions/inlineCompletionsModel'; +import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/suggestWidget'; + +export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { + private isSuggestWidgetVisible: boolean = false; + private currentGhostText: GhostText | undefined = undefined; + + public override minReservedLineCount: number = 0; + + public get isActive() { return this.isSuggestWidgetVisible; } + + constructor( + private readonly editor: IActiveCodeEditor + ) { + super(); + + const suggestController = SuggestController.get(this.editor); + if (suggestController) { + let isBoundToSuggestWidget = false; + const bindToSuggestWidget = () => { + if (isBoundToSuggestWidget) { + return; + } + isBoundToSuggestWidget = true; + + this._register(suggestController.widget.value.onDidShow(() => { + this.isSuggestWidgetVisible = true; + this.updateFromSuggestion(); + })); + this._register(suggestController.widget.value.onDidHide(() => { + this.isSuggestWidgetVisible = false; + this.minReservedLineCount = 0; + this.updateFromSuggestion(); + })); + this._register(suggestController.widget.value.onDidFocus(() => { + this.isSuggestWidgetVisible = true; + this.updateFromSuggestion(); + })); + }; + + this._register(Event.once(suggestController.model.onDidTrigger)(e => { + bindToSuggestWidget(); + })); + } + this.updateFromSuggestion(); + + this._register(toDisposable(() => { + const suggestController = SuggestController.get(this.editor); + if (suggestController) { + suggestController.stopForceRenderingAbove(); + } + })); + } + + private updateFromSuggestion(): void { + const suggestController = SuggestController.get(this.editor); + if (!suggestController) { + this.setCurrentInlineCompletion(undefined); + return; + } + if (!this.isSuggestWidgetVisible) { + this.setCurrentInlineCompletion(undefined); + return; + } + const focusedItem = suggestController.widget.value.getFocusedItem(); + if (!focusedItem) { + this.setCurrentInlineCompletion(undefined); + return; + } + + // TODO: item.isResolved + this.setCurrentInlineCompletion(getInlineCompletion(suggestController, this.editor.getPosition(), focusedItem)); + } + + private setCurrentInlineCompletion(completion: NormalizedInlineCompletion | undefined): void { + this.currentGhostText = completion + ? ( + inlineCompletionToGhostText(completion, this.editor.getModel()) || + // Show an invisible ghost text to reserve space + { + lines: [], + position: completion.range.getEndPosition(), + } + ) : undefined; + + if (this.currentGhostText && this.expanded) { + this.minReservedLineCount = Math.max(this.minReservedLineCount, this.currentGhostText.lines.length - 1); + } + + const suggestController = SuggestController.get(this.editor); + if (suggestController) { + if (this.minReservedLineCount >= 1) { + suggestController.forceRenderingAbove(); + } else { + suggestController.stopForceRenderingAbove(); + } + } + + this.onDidChangeEmitter.fire(); + } + + public override get ghostText(): GhostText | undefined { + return this.currentGhostText; + } +} + +function getInlineCompletion(suggestController: SuggestController, position: Position, suggestion: ISelectedSuggestion): NormalizedInlineCompletion { + const item = suggestion.item; + + if (Array.isArray(item.completion.additionalTextEdits)) { + // cannot represent additional text edits + return { + text: '', + range: Range.fromPositions(position, position), + }; + } + + let { insertText } = item.completion; + if (item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) { + insertText = new SnippetParser().text(insertText); + } + + const info = suggestController.getOverwriteInfo(item, false); + return { + text: insertText, + range: Range.fromPositions(position.delta(0, -info.overwriteBefore), position.delta(0, info.overwriteAfter)), + }; +} diff --git a/src/vs/editor/contrib/inlineSuggestions/inlineSuggestionsController.ts b/src/vs/editor/contrib/inlineSuggestions/inlineSuggestionsController.ts deleted file mode 100644 index f20edab82d4..00000000000 --- a/src/vs/editor/contrib/inlineSuggestions/inlineSuggestionsController.ts +++ /dev/null @@ -1,668 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { ITextModel } from 'vs/editor/common/model'; -import { CompletionItemInsertTextRule, InlineCompletionContext, InlineCompletionTriggerKind, InlineCompletion as InlineCompletion, InlineCompletions as InlineCompletions, InlineSuggestionsProviderRegistry as InlineCompletionsProviderRegistry } from 'vs/editor/common/modes'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; -import * as errors from 'vs/base/common/errors'; -import { GhostText, GhostTextWidget, ObservableValue } from 'vs/editor/contrib/inlineSuggestions/ghostTextWidget'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { Emitter, Event } from 'vs/base/common/event'; -import * as strings from 'vs/base/common/strings'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/suggestWidget'; -import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; - -class InlineCompletionsController extends Disposable { - public static readonly inlineCompletionVisible = new RawContextKey('inlineSuggestionVisible ', false, nls.localize('inlineSuggestionVisible ', "TODO")); - static ID = 'editor.contrib.inlineSuggestionsController'; - - public static get(editor: ICodeEditor): InlineCompletionsController { - return editor.getContribution(InlineCompletionsController.ID); - } - - private readonly widget: GhostTextWidget; - private readonly modelController = this._register(new MutableDisposable()); - - private readonly contextKeys: InlineSuggestionsContextKeys; - - constructor( - private readonly editor: ICodeEditor, - @IInstantiationService instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, - ) { - super(); - this.contextKeys = new InlineSuggestionsContextKeys(contextKeyService); - - this.widget = this._register(instantiationService.createInstance(GhostTextWidget, this.editor)); - - this._register(this.editor.onDidChangeModel(() => { - this.updateModelController(); - })); - this._register(this.editor.onDidChangeConfiguration((e) => { - if (e.hasChanged(EditorOption.suggest)) { - this.updateModelController(); - } - })); - this.updateModelController(); - } - - private updateModelController(): void { - const suggestOptions = this.editor.getOption(EditorOption.suggest); - this.modelController.value = this.editor.hasModel() && suggestOptions.showSuggestionPreview - ? new InlineSuggestionsModelController(this.editor, this.widget, this.contextKeys) - : undefined; - } - - public trigger(): void { - if (this.modelController.value) { - this.modelController.value.trigger(); - } - } - - public commit(): void { - if (this.modelController.value) { - this.modelController.value.commit(); - } - } - - public hide(): void { - if (this.modelController.value) { - this.modelController.value.hide(); - } - } -} - -class InlineSuggestionsContextKeys { - public readonly inlineSuggestionVisible = InlineCompletionsController.inlineCompletionVisible.bindTo(this.contextKeyService); - - constructor(private readonly contextKeyService: IContextKeyService) { - } -} - -/** - * The model controller for a text editor with a specific text model. -*/ -export class InlineSuggestionsModelController extends Disposable { - private readonly textModel = this.editor.getModel(); - private readonly suggestWidgetModel = this._register(new SuggestWidgetInlineSuggestionsModel(this.editor)); - private readonly completionSession = this._register(new MutableDisposable()); - private readonly checkAndStartSessionSoon = this._register(new RunOnceScheduler(() => this.checkAndStartSession(), 50)); - - constructor( - private readonly editor: IActiveCodeEditor, - private readonly widget: GhostTextWidget, - private readonly contextKeys: InlineSuggestionsContextKeys, - ) { - super(); - - this._register(this.editor.onDidChangeModelContent((e) => { - if (this.completionSession.value && !this.completionSession.value.isValid()) { - this.completionSession.clear(); - } - this.checkAndStartSessionSoon.schedule(); - })); - const suggestController = SuggestController.get(this.editor); - if (suggestController) { - let isBoundToSuggestWidget = false; - const bindToSuggestWidget = () => { - if (isBoundToSuggestWidget) { - return; - } - isBoundToSuggestWidget = true; - - this._register(suggestController.widget.value.onDidShow(() => { - this.checkAndStartSession(); - })); - }; - this._register(Event.once(suggestController.model.onDidTrigger)(e => { - bindToSuggestWidget(); - })); - } - this._register(this.editor.onDidChangeCursorPosition((e) => { - if (this.completionSession.value && !this.completionSession.value.isValid()) { - this.completionSession.clear(); - } - })); - } - - private checkAndStartSession(): void { - if (this.completionSession.value && this.completionSession.value.isValid()) { - return; - } - - const pos = this.editor.getPosition(); - if (pos.column === this.textModel.getLineMaxColumn(pos.lineNumber)) { - this.triggerAt(pos); - } - } - - private triggerAt(position: Position): void { - this.completionSession.clear(); - this.completionSession.value = new InlineSuggestionsSession(this.editor, this.widget, this.contextKeys, this.suggestWidgetModel, position); - } - - public trigger(): void { - if (!this.completionSession.value) { - this.triggerAt(this.editor.getPosition()); - } - } - - public commit(): void { - if (this.completionSession.value) { - this.completionSession.value.commitCurrentSuggestion(); - } - } - - public hide(): void { - this.completionSession.clear(); - } -} - -class InlineSuggestionsSession extends Disposable { - private readonly textModel = this.editor.getModel(); - private readonly ghostTextModel = new ObservableValue(undefined); - private readonly model = this._register(new DelegatingInlineSuggestionsModel(this.editor, this.suggestWidgetModel)); - - private maxLineCount: number = 0; - private multiline: boolean = false; - - constructor( - private readonly editor: IActiveCodeEditor, - private readonly widget: GhostTextWidget, - private readonly contextKeys: InlineSuggestionsContextKeys, - private readonly suggestWidgetModel: SuggestWidgetInlineSuggestionsModel, - private readonly triggerPosition: Position, - ) { - super(); - - this._register(this.editor.onDidChangeModelContent((e) => { - this.update(); - })); - this._register(this.editor.onDidChangeCursorPosition((e) => { - this.update(); - })); - this._register(this.model.onDidChange(() => { - this.update(); - })); - // console.log(`CREATING SESSION`); - this._register(toDisposable(() => { - // console.log(`DISPOSING SESSION`); - if (this.widget.model === this.ghostTextModel) { - this.widget.setModel(undefined); - } - })); - this._register(toDisposable(() => { - const suggestController = SuggestController.get(this.editor); - if (suggestController) { - suggestController.stopForceRenderingAbove(); - } - })); - - this.update(); - this.widget.setModel(this.ghostTextModel); - } - - get currentSuggestion(): ValidatedInlineCompletion | undefined { - const cursorPos = this.editor.getPosition(); - const suggestions = this.model.getInlineSuggestions(cursorPos); - const validatedSuggestions = suggestions.items - .map(s => validateSuggestion(s, this.textModel)) - .filter(s => s !== undefined); - const first = validatedSuggestions[0]; - - return first; - } - - isValid(): boolean { - const pos = this.editor.getPosition(); - if (this.currentSuggestion) { - return this.currentSuggestion.suggestion.range.containsPosition(pos); - } - return this.triggerPosition.lineNumber === pos.lineNumber; // the cursor is still on this line - } - - private update() { - const suggestion = this.currentSuggestion; - // const cursorPos = this.editor.getPosition(); - - this.contextKeys.inlineSuggestionVisible.set(!!suggestion); - - if (suggestion) { - const text = suggestion.suggestion.text.substr(suggestion.committedSuggestionLength); - const lines = strings.splitLines(text); - this.maxLineCount = Math.max(this.maxLineCount, lines.length); - this.ghostTextModel.setValue({ - position: suggestion.suggestion.range.getStartPosition().delta(0, suggestion.committedSuggestionLength), - lines, - minAdditionalLineCount: this.maxLineCount - 1, - multiline: this.multiline, - expandCallback: () => this._onDidExpand() - }); - - if (this.maxLineCount > 1 && this.multiline) { - const suggestController = SuggestController.get(this.editor); - if (suggestController) { - suggestController.forceRenderingAbove(); - } - } - } else { - if (this.maxLineCount > 1) { - const maxColumn = this.textModel.getLineMaxColumn(this.triggerPosition.lineNumber); - this.ghostTextModel.setValue({ - position: new Position(this.triggerPosition.lineNumber, maxColumn), - lines: [], - minAdditionalLineCount: this.maxLineCount - 1, - multiline: this.multiline, - expandCallback: () => this._onDidExpand() - }); - } else { - this.ghostTextModel.setValue(undefined); - } - } - - } - - private _onDidExpand(): void { - this.multiline = true; - this.update(); - } - - public commitCurrentSuggestion(): void { - const s = this.currentSuggestion; - if (s) { - this.commit(s); - } - } - - public commit(suggestion: ValidatedInlineCompletion): void { - this.editor.executeEdits( - 'inlineSuggestions.accept', - [ - EditOperation.replaceMove(suggestion.suggestion.range, suggestion.suggestion.text) - ] - ); - } -} - -interface ValidatedInlineCompletion { - suggestion: NormalizedInlineCompletion; - lineNumber: number; - /** - * Indicates the length of the prefix of the suggestion that agrees with the text buffer. - */ - committedSuggestionLength: number; -} - -class DelegatingInlineSuggestionsModel extends Disposable { - - private readonly onDidChangeEventEmitter = this._register(new Emitter()); - public readonly onDidChange = this.onDidChangeEventEmitter.event; - - private readonly directModel = this._register(new InlineSuggestionsModel(this.editor)); - - private currentModel: SuggestWidgetInlineSuggestionsModel | InlineSuggestionsModel; - - constructor( - private readonly editor: IActiveCodeEditor, - private readonly suggestWidgetModel: SuggestWidgetInlineSuggestionsModel - ) { - super(); - - this.directModel.activate(); - this.currentModel = this.directModel; - - this._register(this.suggestWidgetModel.onDidChange(() => { - if (this.suggestWidgetModel.hasFocusedItem) { - if (this.currentModel !== this.suggestWidgetModel) { - this.directModel.deactivate(); - this.currentModel = this.suggestWidgetModel; - } - } else { - if (this.currentModel !== this.directModel) { - this.directModel.activate(); - this.currentModel = this.directModel; - } - } - this.onDidChangeEventEmitter.fire(); - })); - this._register(this.directModel.onDidChange(() => { - this.onDidChangeEventEmitter.fire(); - })); - } - - getInlineSuggestions(position: Position): NormalizedInlineCompletions { - return this.currentModel.getInlineSuggestions(position); - } -} - -class SuggestWidgetInlineSuggestion { - constructor( - public lineNumber: number, - public overwriteBefore: number, - public overwriteAfter: number, - public text: string - ) { } -} - -class SuggestWidgetInlineSuggestionsModel extends Disposable { - - private readonly onDidChangeEventEmitter = this._register(new Emitter()); - public readonly onDidChange = this.onDidChangeEventEmitter.event; - - private isSuggestWidgetVisible: boolean = false; - private _hasFocusedItem: boolean = false; - private currentSuggestion: SuggestWidgetInlineSuggestion | null = null; - - get hasFocusedItem() { return this._hasFocusedItem; } - - constructor( - private readonly editor: IActiveCodeEditor - ) { - super(); - - const suggestController = SuggestController.get(this.editor); - if (suggestController) { - let isBoundToSuggestWidget = false; - const bindToSuggestWidget = () => { - if (isBoundToSuggestWidget) { - return; - } - isBoundToSuggestWidget = true; - - this._register(suggestController.widget.value.onDidShow(() => { - this.isSuggestWidgetVisible = true; - this.updateFromSuggestion(); - })); - this._register(suggestController.widget.value.onDidHide(() => { - this.isSuggestWidgetVisible = false; - this.updateFromSuggestion(); - })); - this._register(suggestController.widget.value.onDidFocus(() => { - this.updateFromSuggestion(); - })); - }; - - this._register(Event.once(suggestController.model.onDidTrigger)(e => { - bindToSuggestWidget(); - })); - } - this.updateFromSuggestion(); - } - - private getSuggestText(suggestController: SuggestController, lineNumber: number, suggestion: ISelectedSuggestion): SuggestWidgetInlineSuggestion | null { - const item = suggestion.item; - - if (Array.isArray(item.completion.additionalTextEdits)) { - // cannot represent additional text edits - return null; - } - - let { insertText } = item.completion; - if (item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) { - insertText = new SnippetParser().text(insertText); - } - - const info = suggestController.getOverwriteInfo(item, false); - return new SuggestWidgetInlineSuggestion(lineNumber, info.overwriteBefore, info.overwriteAfter, insertText); - } - - private updateFromSuggestion(): void { - const suggestController = SuggestController.get(this.editor); - if (!suggestController) { - this.setNoFocusedItem(); - return; - } - if (!this.isSuggestWidgetVisible) { - this.setNoFocusedItem(); - return; - } - const focusedItem = suggestController.widget.value.getFocusedItem(); - if (!focusedItem) { - this.setNoFocusedItem(); - return; - } - - // TODO: item.isResolved - this.setFocusedItem(this.getSuggestText(suggestController, this.editor.getPosition().lineNumber, focusedItem)); - } - - private setNoFocusedItem(): void { - if (!this._hasFocusedItem) { - // no change - return; - } - this._hasFocusedItem = false; - this.currentSuggestion = null; - this.onDidChangeEventEmitter.fire(); - } - - private setFocusedItem(currentSuggestion: SuggestWidgetInlineSuggestion | null): void { - this._hasFocusedItem = true; - this.currentSuggestion = currentSuggestion; - this.onDidChangeEventEmitter.fire(); - } - - getInlineSuggestions(position: Position): NormalizedInlineCompletions { - if (this.currentSuggestion && this.currentSuggestion.lineNumber !== position.lineNumber) { - this.currentSuggestion = null; - } - if (this.currentSuggestion) { - return { - items: [{ - range: Range.fromPositions(position.delta(0, -this.currentSuggestion.overwriteBefore), position.delta(0, this.currentSuggestion.overwriteAfter)), - text: this.currentSuggestion.text - }] - }; - } - return { items: [] }; - } -} - -class InlineSuggestionsModel extends Disposable { - - private readonly onDidChangeEventEmitter = this._register(new Emitter()); - public readonly onDidChange = this.onDidChangeEventEmitter.event; - - private readonly textModel: ITextModel = this.editor.getModel(); - private isActive: boolean = false; - private updatePromise: CancelablePromise | undefined = undefined; - private cachedList: NormalizedInlineCompletions | undefined = undefined; - private cachedPosition: Position | undefined = undefined; - private updateSoon = this._register(new RunOnceScheduler(() => this._update(), 50)); - - constructor( - private readonly editor: IActiveCodeEditor - ) { - super(); - - this._register(toDisposable(() => { - this.clearGhostTextPromise(); - })); - this._register(this.editor.onDidChangeModelContent(() => { - if (this.isActive) { - this.updateSoon.schedule(); - } - })); - } - - activate() { - this.isActive = true; - } - - deactivate() { - this.isActive = false; - this.updateSoon.cancel(); - } - - public getInlineSuggestions(position: Position): NormalizedInlineCompletions { - if (this.cachedList && this.cachedPosition && position.lineNumber === this.cachedPosition.lineNumber) { - return this.cachedList; - } - return { - items: [] - }; - } - - private _update(): void { - const position = this.editor.getPosition(); - this.clearGhostTextPromise(); - this.updatePromise = createCancelablePromise(token => provideInlineCompletions(position, this.textModel, { triggerKind: InlineCompletionTriggerKind.Automatic }, token)); - this.updatePromise.then((result) => { - this.cachedList = result; - this.cachedPosition = position; - this.onDidChangeEventEmitter.fire(undefined); - }, errors.onUnexpectedError); - } - - private clearGhostTextPromise(): void { - if (this.updatePromise) { - this.updatePromise.cancel(); - this.updatePromise = undefined; - } - } -} - -function validateSuggestion(suggestion: NormalizedInlineCompletion, model: ITextModel): ValidatedInlineCompletion | undefined { - // Multiline replacements are not supported - if (suggestion.range.startLineNumber !== suggestion.range.endLineNumber) { - return undefined; - } - const lineNumber = suggestion.range.startLineNumber; - - const suggestedLines = strings.splitLines(suggestion.text); - const firstSuggestedLine = suggestedLines[0]; - - const modelLine = model.getLineContent(lineNumber); - - const suggestionStartIdx = suggestion.range.startColumn - 1; - let committedSuggestionLength = 0; - while ( - committedSuggestionLength < firstSuggestedLine.length - && suggestionStartIdx + committedSuggestionLength < modelLine.length - && firstSuggestedLine[committedSuggestionLength] === modelLine[suggestionStartIdx + committedSuggestionLength]) { - committedSuggestionLength++; - } - - // If a suggestion wants to replace text, the suggestion may not replace that text with different text - //if (committedSuggestionLength !== suggestion.replaceRange.endColumn - suggestion.replaceRange.startColumn) { - // return undefined; - //} - - // For now, we don't support any left over text. An entire line suffix must be replaced. - //if (suggestion.replaceRange.endColumn !== model.getLineMaxColumn(lineNumber)) { - // return undefined; - //} - - return { - lineNumber, - committedSuggestionLength, - suggestion - }; -} - -export class TriggerGhostTextAction extends EditorAction { - constructor() { - super({ - id: 'editor.action.triggerInlineCompletions', - label: nls.localize('triggerInlineCompletionsAction', "Trigger Inline Completions"), - alias: 'Trigger Inline Completions', - precondition: EditorContextKeys.writable - }); - } - - public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { - const controller = InlineCompletionsController.get(editor); - if (controller) { - controller.trigger(); - } - } -} - -export interface NormalizedInlineCompletion extends InlineCompletion { - range: Range; -} - -export interface NormalizedInlineCompletions extends InlineCompletions { -} - -function getDefaultRange(position: Position, model: ITextModel): Range { - const word = model.getWordAtPosition(position); - const maxColumn = model.getLineMaxColumn(position.lineNumber); - // By default, always replace up until the end of the current line. - // This default might be subject to change! - return word - ? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn) - : Range.fromPositions(position, position.with(undefined, maxColumn)); -} - -async function provideInlineCompletions( - position: Position, - model: ITextModel, - context: InlineCompletionContext, - token: CancellationToken = CancellationToken.None -): Promise { - - console.log(`provideInlineCompletions at ${position}`); - - const defaultReplaceRange = getDefaultRange(position, model); - - const providers = InlineCompletionsProviderRegistry.all(model); - const results = await Promise.all( - providers.map(provider => provider.provideInlineCompletions(model, position, context, token)) - ); - - const items = new Array(); - for (const result of results) { - if (result) { - items.push(...result.items.map(item => ({ - text: item.text, - range: item.range ? Range.lift(item.range) : defaultReplaceRange - }))); - } - } - - return { items }; -} - -const InlineCompletionCommand = EditorCommand.bindToContribution(InlineCompletionsController.get); - -registerEditorCommand(new InlineCompletionCommand({ - id: 'commitInlineCompletion', - precondition: InlineCompletionsController.inlineCompletionVisible, - kbOpts: { - weight: 100, - primary: KeyCode.Tab, - }, - handler(x) { - x.commit(); - } -})); -registerEditorCommand(new InlineCompletionCommand({ - id: 'hideInlineCompletion', - precondition: InlineCompletionsController.inlineCompletionVisible, - kbOpts: { - weight: 100, - primary: KeyCode.Escape, - }, - handler(x) { - x.hide(); - } -})); - -registerEditorContribution(InlineCompletionsController.ID, InlineCompletionsController); -registerEditorAction(TriggerGhostTextAction); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 12429cdf3e1..f977d9e4baa 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -24,7 +24,7 @@ import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; import 'vs/editor/contrib/documentSymbols/documentSymbols'; -import 'vs/editor/contrib/inlineSuggestions/inlineSuggestionsController'; +import 'vs/editor/contrib/inlineCompletions/ghostTextController'; import 'vs/editor/contrib/gotoSymbol/goToCommands'; import 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; import 'vs/editor/contrib/gotoError/gotoError'; diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 0515a68fa54..d24d5cd7c10 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -505,7 +505,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token); } }; - this._registrations.set(handle, modes.InlineSuggestionsProviderRegistry.register(selector, provider)); + this._registrations.set(handle, modes.InlineCompletionsProviderRegistry.register(selector, provider)); } // --- parameter hints diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 87af5a6f3ff..265585c0771 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1066,7 +1066,7 @@ class InlineCompletionsAdapter { return { items: result.items.map(item => ({ text: item.text, - replaceRange: item.range ? typeConvert.Range.from(item.range) : undefined, + range: item.range ? typeConvert.Range.from(item.range) : undefined, })), }; }