From 3bf4028bbde5d717e33bc9daf441388cbfbfc580 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:50:36 +0100 Subject: [PATCH] Cleanup inline edits view (#242557) --- src/vs/editor/common/config/editorOptions.ts | 6 - .../browser/model/inlineCompletionsModel.ts | 2 +- .../browser/view/inlineCompletionsView.ts | 2 +- .../components/gutterIndicatorMenu.ts | 102 ++++++--- .../components/gutterIndicatorView.ts | 32 ++- .../view/inlineEdits/inlineEditsModel.ts | 87 ++++++++ .../view/inlineEdits/inlineEditsView.ts | 209 ++++++++---------- .../inlineEdits/inlineEditsViewInterface.ts | 23 +- ...Producer.ts => inlineEditsViewProducer.ts} | 45 +++- .../inlineEditsDeletionView.ts | 6 +- .../inlineEditsInsertionView.ts | 6 +- .../inlineEditsLineReplacementView.ts | 6 +- .../inlineEditsSideBySideView.ts | 10 +- .../inlineEditsWordInsertView.ts | 4 +- .../inlineEditsWordReplacementView.ts | 8 +- .../browser/view/inlineEdits/theme.ts | 2 +- .../browser/view/inlineEdits/utils/utils.ts | 6 - 17 files changed, 357 insertions(+), 199 deletions(-) create mode 100644 src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts rename src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/{viewAndDiffProducer.ts => inlineEditsViewProducer.ts} (61%) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3fdecc8717e..e49033996e0 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4278,10 +4278,6 @@ export interface IInlineSuggestOptions { * @internal */ useMultiLineGhostText?: boolean; - /** - * @internal - */ - useGutterIndicator?: boolean; }; } @@ -4313,7 +4309,6 @@ class InlineEditorSuggest extends BaseEditorOption = this._inAcceptPartialFlow; + public readonly inPartialAcceptFlow: IObservable = this._inAcceptPartialFlow; public async acceptNextInlineEditPart(editor: ICodeEditor): Promise { if (editor.getModel() !== this.textModel) { throw new BugIndicatingError(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts index 04afc6a88f3..bcf120f4996 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts @@ -14,7 +14,7 @@ import { InlineCompletionsHintsWidget } from '../hintsWidget/inlineCompletionsHi import { InlineCompletionsModel } from '../model/inlineCompletionsModel.js'; import { convertItemsToStableObservables } from '../utils.js'; import { GhostTextView } from './ghostText/ghostTextView.js'; -import { InlineEditsViewAndDiffProducer } from './inlineEdits/viewAndDiffProducer.js'; +import { InlineEditsViewAndDiffProducer } from './inlineEdits/inlineEditsViewProducer.js'; export class InlineCompletionsView extends Disposable { private readonly _ghostTexts = derived(this, (reader) => { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts index 6624abea5fa..5a5b7aaa1d3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorMenu.ts @@ -22,15 +22,15 @@ import { asCssVariable, descriptionForeground, editorActionListForeground, edito import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { hideInlineCompletionId, inlineSuggestCommitId, jumpToNextInlineEditId, toggleShowCollapsedId } from '../../../controller/commandIds.js'; -import { IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; -import { FirstFnArg, InlineEditTabAction } from '../utils/utils.js'; +import { IInlineEditModel, InlineEditTabAction } from '../inlineEditsViewInterface.js'; +import { FirstFnArg, } from '../utils/utils.js'; export class GutterIndicatorMenuContent { private readonly _inlineEditsShowCollapsed = this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.showCollapsed); constructor( - private readonly _host: IInlineEditsViewHost, + private readonly _model: IInlineEditModel, private readonly _close: (focusEditor: boolean) => void, private readonly _editorObs: ObservableCodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -60,39 +60,74 @@ export class GutterIndicatorMenuContent { }; }; - // TODO make this menu contributable! - return hoverContent([ - header(this._host.displayName), + const title = header(this._model.displayName); + + const gotoAndAccept = option(createOptionArgs({ + id: 'gotoAndAccept', + title: `${localize('goto', "Go To")} / ${localize('accept', "Accept")}`, + icon: this._model.tabAction.map(action => action === InlineEditTabAction.Accept ? Codicon.check : Codicon.arrowRight), + commandId: this._model.tabAction.map(action => action === InlineEditTabAction.Accept ? inlineSuggestCommitId : jumpToNextInlineEditId) + })); + + const reject = option(createOptionArgs({ + id: 'reject', + title: localize('reject', "Reject"), + icon: Codicon.close, + commandId: hideInlineCompletionId + })); + + const extensionCommands = this._model.extensionCommands.map((c, idx) => option(createOptionArgs({ id: c.id + '_' + idx, title: c.title, icon: Codicon.symbolEvent, commandId: c.id, commandArgs: c.arguments }))); + + const toggleCollapsedMode = this._inlineEditsShowCollapsed.map(showCollapsed => showCollapsed ? option(createOptionArgs({ - id: 'gotoAndAccept', title: `${localize('goto', "Go To")} / ${localize('accept', "Accept")}`, - icon: this._host.tabAction.map(action => action === InlineEditTabAction.Accept ? Codicon.check : Codicon.arrowRight), - commandId: this._host.tabAction.map(action => action === InlineEditTabAction.Accept ? inlineSuggestCommitId : jumpToNextInlineEditId) + id: 'showExpanded', + title: localize('showExpanded', "Show Expanded"), + icon: Codicon.expandAll, + commandId: toggleShowCollapsedId + })) + : option(createOptionArgs({ + id: 'showCollapsed', + title: localize('showCollapsed', "Show Collapsed"), + icon: Codicon.collapseAll, + commandId: toggleShowCollapsedId + })) + ); + + const settings = option(createOptionArgs({ + id: 'settings', + title: localize('settings', "Settings"), + icon: Codicon.gear, + commandId: 'workbench.action.openSettings', + commandArgs: ['@tag:nextEditSuggestions'] + })); + + const actions = this._model.action ? [this._model.action] : []; + const actionBarFooter = actions.length > 0 ? actionBar( + actions.map(action => ({ + id: action.id, + label: action.title, + enabled: true, + run: () => this._commandService.executeCommand(action.id, ...(action.arguments ?? [])), + class: undefined, + tooltip: action.tooltip ?? action.title })), - option(createOptionArgs({ id: 'reject', title: localize('reject', "Reject"), icon: Codicon.close, commandId: hideInlineCompletionId })), + { hoverDelegate: nativeHoverDelegate /* unable to show hover inside another hover */ } + ) : undefined; + + return hoverContent([ + title, + gotoAndAccept, + reject, separator(), - this._host.extensionCommands?.map(c => c && c.length > 0 ? [ - ...c.map((c, idx) => option(createOptionArgs({ id: c.id + '_' + idx, title: c.title, icon: Codicon.symbolEvent, commandId: c.id, commandArgs: c.arguments }))), - separator() - ] : []), - this._inlineEditsShowCollapsed.map(showCollapsed => showCollapsed ? - option(createOptionArgs({ id: 'showExpanded', title: localize('showExpanded', "Show Expanded"), icon: Codicon.expandAll, commandId: toggleShowCollapsedId })) : - option(createOptionArgs({ id: 'showCollapsed', title: localize('showCollapsed', "Show Collapsed"), icon: Codicon.collapseAll, commandId: toggleShowCollapsedId })) - ), - option(createOptionArgs({ id: 'settings', title: localize('settings', "Settings"), icon: Codicon.gear, commandId: 'workbench.action.openSettings', commandArgs: ['@tag:nextEditSuggestions'] })), - this._host.action.map(action => action ? [ - separator(), - actionBar( - [{ - id: action.id, - label: action.title, - enabled: true, - run: () => this._commandService.executeCommand(action.id, ...(action.arguments ?? [])), - class: undefined, - tooltip: action.tooltip ?? action.title - }], - { hoverDelegate: nativeHoverDelegate /* unable to show hover inside another hover */ } - ) - ] : []) + + ...extensionCommands, + extensionCommands.length ? separator() : undefined, + + toggleCollapsedMode, + settings, + + actionBarFooter ? separator() : undefined, + actionBarFooter ]); } @@ -188,6 +223,7 @@ function actionBar(actions: IAction[], options: IActionBarOptions) { function separator() { return n.div({ + id: 'inline-edit-gutter-indicator-menu-separator', class: 'menu-separator', style: { color: asCssVariable(editorActionListForeground), diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts index e737ff7aa25..8ec842d9fc5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts @@ -6,6 +6,7 @@ import { n, trackFocus } from '../../../../../../../base/browser/dom.js'; import { renderIcon } from '../../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { Codicon } from '../../../../../../../base/common/codicons.js'; +import { BugIndicatingError } from '../../../../../../../base/common/errors.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../../../../base/common/lifecycle.js'; import { IObservable, ISettableObservable, constObservable, derived, observableFromEvent, observableValue, runOnChange } from '../../../../../../../base/common/observable.js'; import { debouncedObservable } from '../../../../../../../base/common/observableInternal/utils.js'; @@ -21,17 +22,24 @@ import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { LineRange } from '../../../../../../common/core/lineRange.js'; import { OffsetRange } from '../../../../../../common/core/offsetRange.js'; import { StickyScrollController } from '../../../../../stickyScroll/browser/stickyScrollController.js'; -import { IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditModel, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { inlineEditIndicatorBackground, inlineEditIndicatorPrimaryBackground, inlineEditIndicatorPrimaryForeground, inlineEditIndicatorSecondaryBackground, inlineEditIndicatorSecondaryForeground, inlineEditIndicatorsuccessfulBackground, inlineEditIndicatorsuccessfulForeground } from '../theme.js'; -import { InlineEditTabAction, mapOutFalsy, rectToProps } from '../utils/utils.js'; +import { mapOutFalsy, rectToProps } from '../utils/utils.js'; import { GutterIndicatorMenuContent } from './gutterIndicatorMenu.js'; export class InlineEditsGutterIndicator extends Disposable { + + private get model() { + const model = this._model.get(); + if (!model) { throw new BugIndicatingError('Inline Edit Model not available'); } + return model; + } + constructor( private readonly _editorObs: ObservableCodeEditor, private readonly _originalRange: IObservable, private readonly _verticalOffset: IObservable, - private readonly _host: IInlineEditsViewHost, + private readonly _model: IObservable, private readonly _isHoveringOverInlineEdit: IObservable, private readonly _focusIsInMenu: ISettableObservable, @IHoverService private readonly _hoverService: HoverService, @@ -131,7 +139,7 @@ export class InlineEditsGutterIndicator extends Disposable { const disposableStore = new DisposableStore(); const content = disposableStore.add(this._instantiationService.createInstance( GutterIndicatorMenuContent, - this._host, + this.model, (focusEditor) => { if (focusEditor) { this._editorObs.editor.focus(); @@ -161,15 +169,21 @@ export class InlineEditsGutterIndicator extends Disposable { } } + private readonly _tabAction = derived(this, reader => { + const model = this._model.read(reader); + if (!model) { return InlineEditTabAction.Inactive; } + return model.tabAction.read(reader); + }); + private readonly _indicator = n.div({ class: 'inline-edits-view-gutter-indicator', onclick: () => { const docked = this._layout.map(l => l && l.docked).get(); this._editorObs.editor.focus(); if (docked) { - this._host.accept(); + this.model.accept(); } else { - this._host.jump(); + this.model.jump(); } }, tabIndex: 0, @@ -199,14 +213,14 @@ export class InlineEditsGutterIndicator extends Disposable { cursor: 'pointer', zIndex: '1000', position: 'absolute', - backgroundColor: this._host.tabAction.map(v => { + backgroundColor: this._tabAction.map(v => { switch (v) { case InlineEditTabAction.Inactive: return asCssVariable(inlineEditIndicatorSecondaryBackground); case InlineEditTabAction.Jump: return asCssVariable(inlineEditIndicatorPrimaryBackground); case InlineEditTabAction.Accept: return asCssVariable(inlineEditIndicatorsuccessfulBackground); } }), - ['--vscodeIconForeground' as any]: this._host.tabAction.map(v => { + ['--vscodeIconForeground' as any]: this._tabAction.map(v => { switch (v) { case InlineEditTabAction.Inactive: return asCssVariable(inlineEditIndicatorSecondaryForeground); case InlineEditTabAction.Jump: return asCssVariable(inlineEditIndicatorPrimaryForeground); @@ -235,7 +249,7 @@ export class InlineEditsGutterIndicator extends Disposable { justifyContent: 'center', } }, [ - this._host.tabAction.map(v => v === InlineEditTabAction.Accept ? renderIcon(Codicon.keyboardTab) : renderIcon(Codicon.arrowRight)) + this._tabAction.map(v => v === InlineEditTabAction.Accept ? renderIcon(Codicon.keyboardTab) : renderIcon(Codicon.arrowRight)) ]) ]), ])).keepUpdated(this._store); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts new file mode 100644 index 00000000000..56db7d9f1b3 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsModel.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { derived, IObservable } from '../../../../../../base/common/observable.js'; +import { localize } from '../../../../../../nls.js'; +import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; +import { LineRange } from '../../../../../common/core/lineRange.js'; +import { StringText, TextEdit } from '../../../../../common/core/textEdit.js'; +import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { InlineCompletionWithUpdatedRange } from '../../model/inlineCompletionsSource.js'; +import { IInlineEditModel, InlineEditTabAction } from './inlineEditsViewInterface.js'; +import { InlineEditWithChanges } from './inlineEditWithChanges.js'; + +export class InlineEditModel implements IInlineEditModel { + + readonly action = this.inlineEdit.inlineCompletion.action; + readonly displayName = this.inlineEdit.inlineCompletion.source.provider.displayName ?? localize('inlineEdit', "Inline Edit"); + readonly extensionCommands = this.inlineEdit.inlineCompletion.source.inlineCompletions.commands ?? []; + + readonly inAcceptFlow = this._model.inAcceptFlow; + readonly inPartialAcceptFlow = this._model.inPartialAcceptFlow; + + constructor( + private readonly _model: InlineCompletionsModel, + readonly inlineEdit: InlineEditWithChanges, + readonly tabAction: IObservable, + ) { } + + accept() { + this._model.accept(); + } + + jump() { + this._model.jump(); + } + + abort(reason: string) { + console.error(reason); // TODO: add logs/telemetry + this._model.stop(); + } + + handleInlineEditShown() { + this._model.handleInlineEditShown(this.inlineEdit.inlineCompletion); + } +} + + +export class GhostTextIndicator { + + readonly model: InlineEditModel; + + private readonly _editorObs = observableCodeEditor(this._editor); + + constructor( + private _editor: ICodeEditor, + model: InlineCompletionsModel, + readonly lineRange: LineRange, + inlineCompletion: InlineCompletionWithUpdatedRange, + renderExplicitly: boolean, + ) { + + const tabAction = derived(this, reader => { + if (this._editorObs.isFocused.read(reader)) { + if (model.inlineCompletionState.read(reader)?.inlineCompletion?.sourceInlineCompletion.showInlineEditMenu) { + return InlineEditTabAction.Accept; + } + } + return InlineEditTabAction.Inactive; + }); + + this.model = new InlineEditModel( + model, + new InlineEditWithChanges( + new StringText(''), + new TextEdit([]), + model.primaryPosition.get(), + renderExplicitly, + inlineCompletion.source.inlineCompletions.commands ?? [], + inlineCompletion.inlineCompletion + ), + tabAction, + ); + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index f9479e66980..7f16b9652fb 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -7,8 +7,7 @@ import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equal import { BugIndicatingError } from '../../../../../../base/common/errors.js'; import { Event } from '../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorunWithStore, derived, derivedObservableWithCache, derivedOpts, derivedWithStore, IObservable, IReader, ISettableObservable, mapObservableArrayCached, observableValue } from '../../../../../../base/common/observable.js'; -import { localize } from '../../../../../../nls.js'; +import { autorunWithStore, derived, derivedOpts, derivedWithStore, IObservable, IReader, ISettableObservable, mapObservableArrayCached, observableValue } from '../../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; @@ -20,18 +19,18 @@ import { AbstractText, SingleTextEdit, StringText } from '../../../../../common/ import { TextLength } from '../../../../../common/core/textLength.js'; import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; import { TextModel } from '../../../../../common/model/textModel.js'; -import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; import { InlineEditsGutterIndicator } from './components/gutterIndicatorView.js'; -import { IInlineEditsIndicatorState, InlineEditsIndicator } from './components/indicatorView.js'; +import { InlineEditsIndicator } from './components/indicatorView.js'; import { InlineEditWithChanges } from './inlineEditWithChanges.js'; -import { IInlineEditsViewHost } from './inlineEditsViewInterface.js'; +import { GhostTextIndicator, InlineEditModel } from './inlineEditsModel.js'; +import { IInlineEditModel, InlineEditTabAction } from './inlineEditsViewInterface.js'; import { InlineEditsDeletionView } from './inlineEditsViews/inlineEditsDeletionView.js'; import { InlineEditsInsertionView } from './inlineEditsViews/inlineEditsInsertionView.js'; import { InlineEditsLineReplacementView } from './inlineEditsViews/inlineEditsLineReplacementView.js'; import { InlineEditsSideBySideView } from './inlineEditsViews/inlineEditsSideBySideView.js'; import { InlineEditsWordReplacementView } from './inlineEditsViews/inlineEditsWordReplacementView.js'; import { IOriginalEditorInlineDiffViewState, OriginalEditorInlineDiffView } from './inlineEditsViews/originalEditorInlineDiffView.js'; -import { applyEditToModifiedRangeMappings, createReindentEdit, InlineEditTabAction } from './utils/utils.js'; +import { applyEditToModifiedRangeMappings, createReindentEdit } from './utils/utils.js'; import './view.css'; export class InlineEditsView extends Disposable { @@ -44,6 +43,8 @@ export class InlineEditsView extends Disposable { private readonly _showCollapsed = this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.showCollapsed); private readonly _useMultiLineGhostText = this._editorObs.getOption(EditorOption.inlineSuggest).map(s => s.edits.useMultiLineGhostText); + private readonly _tabAction = derived(reader => this._model.read(reader)?.tabAction.read(reader) ?? InlineEditTabAction.Inactive); + private _previousView: { id: string; view: ReturnType; @@ -54,14 +55,19 @@ export class InlineEditsView extends Disposable { constructor( private readonly _editor: ICodeEditor, - private readonly _edit: IObservable, - private readonly _model: IObservable, + private readonly _model: IObservable, + private readonly _ghostTextIndicator: IObservable, private readonly _focusIsInMenu: ISettableObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); this._register(autorunWithStore((reader, store) => { + const model = this._model.read(reader); + if (!model) { + return; + } + store.add( Event.any( this._sideBySide.onDidClick, @@ -71,9 +77,9 @@ export class InlineEditsView extends Disposable { ...this._wordReplacementViews.read(reader).map(w => w.onDidClick), this._inlineDiffView.onDidClick, )(e => { - if (this._viewHasBeenShownLongThan(350)) { + if (this._viewHasBeenShownLongerThan(350)) { e.preventDefault(); - this._host.accept(); + model.accept(); } }) ); @@ -93,35 +99,36 @@ export class InlineEditsView extends Disposable { newTextLineCount: number; originalDisplayRange: LineRange; } | undefined>(this, reader => { - const edit = this._edit.read(reader); - if (!edit) { + const model = this._model.read(reader); + if (!model) { return undefined; } - this._model.get()?.handleInlineEditShown(edit.inlineCompletion); + model.handleInlineEditShown(); - let mappings = RangeMapping.fromEdit(edit.edit); - let newText = edit.edit.apply(edit.originalText); - let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); + const inlineEdit = model.inlineEdit; + let mappings = RangeMapping.fromEdit(inlineEdit.edit); + let newText = inlineEdit.edit.apply(inlineEdit.originalText); + let diff = lineRangeMappingFromRangeMappings(mappings, inlineEdit.originalText, new StringText(newText)); - const originalDisplayRange = edit.originalText.lineRange.intersect( - edit.originalLineRange.join( - LineRange.ofLength(edit.originalLineRange.startLineNumber, edit.lineEdit.newLines.length) + const originalDisplayRange = inlineEdit.originalText.lineRange.intersect( + inlineEdit.originalLineRange.join( + LineRange.ofLength(inlineEdit.originalLineRange.startLineNumber, inlineEdit.lineEdit.newLines.length) ) )!; - let state = this.determineRenderState(edit, reader, diff, new StringText(newText), originalDisplayRange); + let state = this.determineRenderState(model, reader, diff, new StringText(newText), originalDisplayRange); if (!state) { - this._model.get()?.stop(); + model.abort(`unable to determine view: tried to render ${this._previousView?.view}`); return undefined; } if (state.kind === 'sideBySide') { - const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange); + const indentationAdjustmentEdit = createReindentEdit(newText, inlineEdit.modifiedLineRange); newText = indentationAdjustmentEdit.applyToString(newText); mappings = applyEditToModifiedRangeMappings(mappings, indentationAdjustmentEdit); - diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); + diff = lineRangeMappingFromRangeMappings(mappings, inlineEdit.originalText, new StringText(newText)); } this._previewTextModel.setLanguage(this._editor.getModel()!.getLanguageId()); @@ -132,16 +139,16 @@ export class InlineEditsView extends Disposable { this._previewTextModel.setValue(newText); } - if (this._showCollapsed.read(reader) && this._host.tabAction.read(reader) !== InlineEditTabAction.Accept && !this._indicator.read(reader)?.isHoverVisible.read(reader) && !this._model.get()!.inAcceptFlow.read(reader)) { + if (this._showCollapsed.read(reader) && model.tabAction.read(reader) !== InlineEditTabAction.Accept && !this._indicator.read(reader)?.isHoverVisible.read(reader) && !model.inAcceptFlow.read(reader)) { state = { kind: 'hidden' }; } return { state, diff, - edit, + edit: inlineEdit, newText, - newTextLineCount: edit.modifiedLineRange.length, + newTextLineCount: inlineEdit.modifiedLineRange.length, originalDisplayRange: originalDisplayRange, }; }); @@ -154,37 +161,6 @@ export class InlineEditsView extends Disposable { null )); - // TODO: This has become messy, should it be passed in to the InlineEditsView? Maybe include in accept flow? - private readonly _host: IInlineEditsViewHost = { - displayName: derivedObservableWithCache(this, (reader, previousDisplayName) => { - const state = this._model.read(reader)?.inlineEditState; - const item = state?.read(reader); - const completionSource = item?.inlineCompletion?.source; - // TODO: expose the provider (typed) and expose the provider the edit belongs to typing and get correct edit - return (completionSource?.inlineCompletions as any)?.edits?.[0]?.provider?.displayName ?? previousDisplayName - ?? completionSource?.provider.displayName ?? localize('inlineEdit', "Inline Edit"); - }), - tabAction: derived(this, reader => { - const m = this._model.read(reader); - if (this._editorObs.isFocused.read(reader)) { - if (m && m.tabShouldJumpToInlineEdit.read(reader)) { return InlineEditTabAction.Jump; } - if (m && m.tabShouldAcceptInlineEdit.read(reader)) { return InlineEditTabAction.Accept; } - if (m && m.inlineCompletionState.read(reader)?.inlineCompletion?.sourceInlineCompletion.showInlineEditMenu) { return InlineEditTabAction.Accept; } - } - return InlineEditTabAction.Inactive; - }), - action: this._model.map((m, r) => m?.state.read(r)?.inlineCompletion?.inlineCompletion.action), - extensionCommands: this._model.map((m, r) => m?.state.read(r)?.inlineCompletion?.source.inlineCompletions.commands ?? []), - accept: () => { - this._model.get()?.accept(); - }, - jump: () => { - this._model.get()?.jump(); - } - }; - - private readonly _useGutterIndicator = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.useGutterIndicator); - private readonly _indicatorCyclicDependencyCircuitBreaker = observableValue(this, false); protected readonly _indicator = derivedWithStore(this, (reader, store) => { @@ -193,9 +169,9 @@ export class InlineEditsView extends Disposable { } const indicatorDisplayRange = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => { - const s = this._model.read(reader)?.inlineCompletionState.read(reader); - if (s && s.inlineCompletion?.sourceInlineCompletion.showInlineEditMenu) { - return LineRange.ofLength(s.primaryGhostText.lineNumber, 1); + const ghostTextIndicator = this._ghostTextIndicator.read(reader); + if (ghostTextIndicator) { + return ghostTextIndicator.lineRange; } const state = this._uiState.read(reader); @@ -205,29 +181,29 @@ export class InlineEditsView extends Disposable { return state?.originalDisplayRange; }); - if (this._useGutterIndicator.read(reader)) { - return store.add(this._instantiationService.createInstance( - InlineEditsGutterIndicator, - this._editorObs, - indicatorDisplayRange, - this._gutterIndicatorOffset, - this._host, - this._inlineEditsIsHovered, - this._focusIsInMenu, - )); - } else { - return store.add(new InlineEditsIndicator( - this._editorObs, - derived(reader => { - const state = this._uiState.read(reader); - const range = indicatorDisplayRange.read(reader); - if (!state || !state.state || !range) { return undefined; } - const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader) + this._gutterIndicatorOffset.read(reader); - return { editTop: top, showAlways: state.state.kind !== 'sideBySide' }; - }), - this._model, - )); - } + const modelWithGhostTextSupport = derived(this, reader => { + const model = this._model.read(reader); + if (model) { + return model; + } + + const ghostTextIndicator = this._ghostTextIndicator.read(reader); + if (ghostTextIndicator) { + return ghostTextIndicator.model; + } + + return model; + }); + + return store.add(this._instantiationService.createInstance( + InlineEditsGutterIndicator, + this._editorObs, + indicatorDisplayRange, + this._gutterIndicatorOffset, + modelWithGhostTextSupport, + this._inlineEditsIsHovered, + this._focusIsInMenu, + )); }); private readonly _inlineEditsIsHovered = derived(this, reader => { @@ -249,24 +225,24 @@ export class InlineEditsView extends Disposable { private readonly _sideBySide = this._register(this._instantiationService.createInstance(InlineEditsSideBySideView, this._editor, - this._edit, + this._model.map(m => m?.inlineEdit), this._previewTextModel, this._uiState.map(s => s && s.state?.kind === 'sideBySide' ? ({ edit: s.edit, newTextLineCount: s.newTextLineCount, originalDisplayRange: s.originalDisplayRange, }) : undefined), - this._host, + this._tabAction, )); protected readonly _deletion = this._register(this._instantiationService.createInstance(InlineEditsDeletionView, this._editor, - this._edit, + this._model.map(m => m?.inlineEdit), this._uiState.map(s => s && s.state?.kind === 'deletion' ? ({ originalRange: s.state.originalRange, deletions: s.state.deletions, }) : undefined), - this._host, + this._tabAction, )); protected readonly _insertion = this._register(this._instantiationService.createInstance(InlineEditsInsertionView, @@ -276,7 +252,7 @@ export class InlineEditsView extends Disposable { startColumn: s.state.column, text: s.state.text, }) : undefined), - this._host, + this._tabAction, )); private readonly _inlineDiffViewState = derived(this, reader => { @@ -296,7 +272,7 @@ export class InlineEditsView extends Disposable { protected readonly _inlineDiffView = this._register(new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState, this._previewTextModel)); protected readonly _wordReplacementViews = mapObservableArrayCached(this, this._uiState.map(s => s?.state?.kind === 'wordReplacements' ? s.state.replacements : []), (e, store) => { - return store.add(this._instantiationService.createInstance(InlineEditsWordReplacementView, this._editorObs, e, [e], this._host)); + return store.add(this._instantiationService.createInstance(InlineEditsWordReplacementView, this._editorObs, e, [e], this._tabAction)); }); protected readonly _lineReplacementView = this._register(this._instantiationService.createInstance(InlineEditsLineReplacementView, @@ -307,21 +283,23 @@ export class InlineEditsView extends Disposable { modifiedLines: s.state.modifiedLines, replacements: s.state.replacements, }) : undefined), - this._host + this._tabAction, )); - private getCacheId(edit: InlineEditWithChanges) { - if (this._model.get()?.inAcceptPartialFlow.get()) { - return `${edit.inlineCompletion.id}_${edit.edit.edits.map(edit => edit.range.toString() + edit.text).join(',')}`; + private getCacheId(model: IInlineEditModel) { + const inlineEdit = model.inlineEdit; + if (model.inPartialAcceptFlow.get()) { + return `${inlineEdit.inlineCompletion.id}_${inlineEdit.edit.edits.map(innerEdit => innerEdit.range.toString() + innerEdit.text).join(',')}`; } - return edit.inlineCompletion.id; + return inlineEdit.inlineCompletion.id; } - private determineView(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange): string { + private determineView(model: IInlineEditModel, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange): string { // Check if we can use the previous view if it is the same InlineCompletion as previously shown - const canUseCache = this._previousView?.id === this.getCacheId(edit); - const reconsiderViewAfterJump = edit.userJumpedToIt !== this._previousView?.userJumpedToIt && + const inlineEdit = model.inlineEdit; + const canUseCache = this._previousView?.id === this.getCacheId(model); + const reconsiderViewAfterJump = inlineEdit.userJumpedToIt !== this._previousView?.userJumpedToIt && ( (this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible' && this._previousView?.view !== 'mixedLines') || (this._useInterleavedLinesDiff.read(reader) === 'afterJump' && this._previousView?.view !== 'interleavedLines') @@ -344,13 +322,13 @@ export class InlineEditsView extends Disposable { isSingleInnerEdit && ( this._useMixedLinesDiff.read(reader) === 'forStableInsertions' && this._useCodeShifting.read(reader) - && isSingleLineInsertionAfterPosition(diff, edit.cursorPosition) + && isSingleLineInsertionAfterPosition(diff, inlineEdit.cursorPosition) ) ) { return 'insertionInline'; } - const innerValues = inner.map(m => ({ original: edit.originalText.getValueOfRange(m.originalRange), modified: newText.getValueOfRange(m.modifiedRange) })); + const innerValues = inner.map(m => ({ original: inlineEdit.originalText.getValueOfRange(m.originalRange), modified: newText.getValueOfRange(m.modifiedRange) })); if (innerValues.every(({ original, modified }) => modified.trim() === '' && original.length > 0 && (original.length > modified.length || original.trim() !== ''))) { return 'deletion'; } @@ -359,20 +337,20 @@ export class InlineEditsView extends Disposable { return 'insertionMultiLine'; } - const numOriginalLines = edit.originalLineRange.length; - const numModifiedLines = edit.modifiedLineRange.length; + const numOriginalLines = inlineEdit.originalLineRange.length; + const numModifiedLines = inlineEdit.modifiedLineRange.length; const allInnerChangesNotTooLong = inner.every(m => TextLength.ofRange(m.originalRange).columnCount < InlineEditsWordReplacementView.MAX_LENGTH && TextLength.ofRange(m.modifiedRange).columnCount < InlineEditsWordReplacementView.MAX_LENGTH); if (allInnerChangesNotTooLong && isSingleInnerEdit && numOriginalLines === 1 && numModifiedLines === 1) { // Make sure there is no insertion, even if we grow them if ( !inner.some(m => m.originalRange.isEmpty()) || - !growEditsUntilWhitespace(inner.map(m => new SingleTextEdit(m.originalRange, '')), edit.originalText).some(e => e.range.isEmpty() && TextLength.ofRange(e.range).columnCount < InlineEditsWordReplacementView.MAX_LENGTH) + !growEditsUntilWhitespace(inner.map(m => new SingleTextEdit(m.originalRange, '')), inlineEdit.originalText).some(e => e.range.isEmpty() && TextLength.ofRange(e.range).columnCount < InlineEditsWordReplacementView.MAX_LENGTH) ) { return 'wordReplacements'; } } if (numOriginalLines > 0 && numModifiedLines > 0) { - if (this._renderSideBySide.read(reader) !== 'never' && InlineEditsSideBySideView.fitsInsideViewport(this._editor, edit, originalDisplayRange, reader)) { + if (this._renderSideBySide.read(reader) !== 'never' && InlineEditsSideBySideView.fitsInsideViewport(this._editor, inlineEdit, originalDisplayRange, reader)) { return 'sideBySide'; } @@ -380,24 +358,25 @@ export class InlineEditsView extends Disposable { } if ( - (this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible')) + (this._useMixedLinesDiff.read(reader) === 'whenPossible' || (inlineEdit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible')) && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) ) { return 'mixedLines'; } - if (this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump')) { + if (this._useInterleavedLinesDiff.read(reader) === 'always' || (inlineEdit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump')) { return 'interleavedLines'; } return 'sideBySide'; } - private determineRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange) { + private determineRenderState(model: IInlineEditModel, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange) { + const inlineEdit = model.inlineEdit; - const view = this.determineView(edit, reader, diff, newText, originalDisplayRange); + const view = this.determineView(model, reader, diff, newText, originalDisplayRange); - this._previousView = { id: this.getCacheId(edit), view, userJumpedToIt: edit.userJumpedToIt, editorWidth: this._editor.getLayoutInfo().width, timestamp: Date.now() }; + this._previousView = { id: this.getCacheId(model), view, userJumpedToIt: inlineEdit.userJumpedToIt, editorWidth: this._editor.getLayoutInfo().width, timestamp: Date.now() }; switch (view) { case 'insertionInline': return { kind: 'insertionInline' as const }; @@ -412,7 +391,7 @@ export class InlineEditsView extends Disposable { if (view === 'deletion') { return { kind: 'deletion' as const, - originalRange: edit.originalLineRange, + originalRange: inlineEdit.originalLineRange, deletions: inner.map(m => m.originalRange), }; } @@ -433,10 +412,10 @@ export class InlineEditsView extends Disposable { } if (view === 'wordReplacements') { - let grownEdits = growEditsToEntireWord(replacements, edit.originalText); + let grownEdits = growEditsToEntireWord(replacements, inlineEdit.originalText); if (grownEdits.some(e => e.range.isEmpty())) { - grownEdits = growEditsUntilWhitespace(replacements, edit.originalText); + grownEdits = growEditsUntilWhitespace(replacements, inlineEdit.originalText); } return { @@ -448,9 +427,9 @@ export class InlineEditsView extends Disposable { if (view === 'lineReplacement') { return { kind: 'lineReplacement' as const, - originalRange: edit.originalLineRange, - modifiedRange: edit.modifiedLineRange, - modifiedLines: edit.modifiedLineRange.mapToLineArray(line => newText.getLineAt(line)), + originalRange: inlineEdit.originalLineRange, + modifiedRange: inlineEdit.modifiedLineRange, + modifiedLines: inlineEdit.modifiedLineRange.mapToLineArray(line => newText.getLineAt(line)), replacements: inner.map(m => ({ originalRange: m.originalRange, modifiedRange: m.modifiedRange })), }; } @@ -458,7 +437,7 @@ export class InlineEditsView extends Disposable { return undefined; } - private _viewHasBeenShownLongThan(durationMs: number): boolean { + private _viewHasBeenShownLongerThan(durationMs: number): boolean { const viewCreationTime = this._previousView?.timestamp; if (!viewCreationTime) { throw new BugIndicatingError('viewHasBeenShownLongThan called before a view has been shown'); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts index ffa4cdbafff..a00fc94d390 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewInterface.ts @@ -7,18 +7,31 @@ import { IMouseEvent } from '../../../../../../base/browser/mouseEvent.js'; import { Event } from '../../../../../../base/common/event.js'; import { IObservable } from '../../../../../../base/common/observable.js'; import { Command } from '../../../../../common/languages.js'; -import { InlineEditTabAction } from './utils/utils.js'; +import { InlineEditWithChanges } from './inlineEditWithChanges.js'; + +export enum InlineEditTabAction { + Jump = 'jump', + Accept = 'accept', + Inactive = 'inactive' +} export interface IInlineEditsView { isHovered: IObservable; onDidClick: Event; } -export interface IInlineEditsViewHost { - displayName: IObservable; - action: IObservable; +export interface IInlineEditModel { + displayName: string; + action: Command | undefined; + extensionCommands: Command[]; + inlineEdit: InlineEditWithChanges; + tabAction: IObservable; - extensionCommands: IObservable; + inAcceptFlow: IObservable; + inPartialAcceptFlow: IObservable; + + handleInlineEditShown(): void; accept(): void; jump(): void; + abort(reason: string): void; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts similarity index 61% rename from src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts rename to src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts index 9b5fcbb5159..cd93ce8a60a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewProducer.ts @@ -8,17 +8,23 @@ import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { derived, IObservable, ISettableObservable } from '../../../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; +import { LineRange } from '../../../../../common/core/lineRange.js'; import { Range } from '../../../../../common/core/range.js'; import { SingleTextEdit, TextEdit } from '../../../../../common/core/textEdit.js'; import { TextModelText } from '../../../../../common/model/textModelText.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; import { InlineEdit } from '../../model/inlineEdit.js'; import { InlineEditWithChanges } from './inlineEditWithChanges.js'; +import { GhostTextIndicator, InlineEditModel } from './inlineEditsModel.js'; import { InlineEditsView } from './inlineEditsView.js'; +import { InlineEditTabAction } from './inlineEditsViewInterface.js'; export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This class is no longer a diff producer. Rename it or get rid of it public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); + private readonly _editorObs = observableCodeEditor(this._editor); + private readonly _inlineEdit = derived(this, (reader) => { const model = this._model.read(reader); if (!model) { return undefined; } @@ -30,7 +36,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c const editOffset = model.inlineEditState.get()?.inlineCompletion.updatedEdit.read(reader); if (!editOffset) { return undefined; } - const offsetEdits = model.inAcceptPartialFlow.read(reader) ? [editOffset.edits[0]] : editOffset.edits; + const offsetEdits = model.inPartialAcceptFlow.read(reader) ? [editOffset.edits[0]] : editOffset.edits; const edits = offsetEdits.map(e => { const innerEditRange = Range.fromPositions( textModel.getPositionAt(e.replaceRange.start), @@ -45,6 +51,41 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c return new InlineEditWithChanges(text, diffEdits, model.primaryPosition.get(), inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); }); + private readonly _inlineEditModel = derived(this, reader => { + const model = this._model.read(reader); + if (!model) { return undefined; } + const edit = this._inlineEdit.read(reader); + if (!edit) { return undefined; } + + const tabAction = derived(this, reader => { + if (this._editorObs.isFocused.read(reader)) { + if (model.tabShouldJumpToInlineEdit.read(reader)) { return InlineEditTabAction.Jump; } + if (model.tabShouldAcceptInlineEdit.read(reader)) { return InlineEditTabAction.Accept; } + } + return InlineEditTabAction.Inactive; + }); + + return new InlineEditModel(model, edit, tabAction); + }); + + private readonly _ghostTextIndicator = derived(this, reader => { + const model = this._model.read(reader); + if (!model) { return undefined; } + const state = model.inlineCompletionState.read(reader); + if (!state) { return undefined; } + const inlineCompletion = state.inlineCompletion; + if (!inlineCompletion) { return undefined; } + + if (!inlineCompletion.sourceInlineCompletion.showInlineEditMenu) { + return undefined; + } + + const lineRange = LineRange.ofLength(state.primaryGhostText.lineNumber, 1); + const renderExplicitly = false; + + return new GhostTextIndicator(this._editor, model, lineRange, inlineCompletion, renderExplicitly); + }); + constructor( private readonly _editor: ICodeEditor, private readonly _edit: IObservable, @@ -54,6 +95,6 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c ) { super(); - this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEdit, this._model, this._focusIsInMenu)); + this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEditModel, this._ghostTextIndicator, this._focusIsInMenu)); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsDeletionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsDeletionView.ts index f8c6bcf3934..2881fb21196 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsDeletionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsDeletionView.ts @@ -14,7 +14,7 @@ import { Point } from '../../../../../../browser/point.js'; import { LineRange } from '../../../../../../common/core/lineRange.js'; import { Position } from '../../../../../../common/core/position.js'; import { Range } from '../../../../../../common/core/range.js'; -import { IInlineEditsView, IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { InlineEditWithChanges } from '../inlineEditWithChanges.js'; import { getOriginalBorderColor, originalBackgroundColor } from '../theme.js'; import { createRectangle, getPrefixTrim, mapOutFalsy, maxContentWidthInRange } from '../utils/utils.js'; @@ -32,7 +32,7 @@ export class InlineEditsDeletionView extends Disposable implements IInlineEditsV originalRange: LineRange; deletions: Range[]; } | undefined>, - private readonly _host: IInlineEditsViewHost, + private readonly _tabAction: IObservable, ) { super(); @@ -151,7 +151,7 @@ export class InlineEditsDeletionView extends Disposable implements IInlineEditsV { hideLeft: layoutInfo.horizontalScrollOffset !== 0 } ); - const originalBorderColor = getOriginalBorderColor(this._host.tabAction).read(reader); + const originalBorderColor = getOriginalBorderColor(this._tabAction).read(reader); return [ n.svgElem('path', { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts index 38fe0c00ee0..2f631455e49 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsInsertionView.ts @@ -23,7 +23,7 @@ import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; import { GhostText, GhostTextPart } from '../../../model/ghostText.js'; import { GhostTextView } from '../../ghostText/ghostTextView.js'; -import { IInlineEditsView, IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor, modifiedChangedLineBackgroundColor } from '../theme.js'; import { createRectangle, getPrefixTrim, mapOutFalsy } from '../utils/utils.js'; @@ -106,7 +106,7 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits startColumn: number; text: string; } | undefined>, - private readonly _host: IInlineEditsViewHost, + private readonly _tabAction: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILanguageService private readonly _languageService: ILanguageService, ) { @@ -255,7 +255,7 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits { hideLeft: croppedOverlay.left !== overlay.left } ); - const modifiedBorderColor = getModifiedBorderColor(this._host.tabAction).read(reader); + const modifiedBorderColor = getModifiedBorderColor(this._tabAction).read(reader); return [ n.svgElem('path', { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts index 123fb77cd78..c2fb53db63b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsLineReplacementView.ts @@ -25,7 +25,7 @@ import { IModelDecorationOptions, TrackedRangeStickiness } from '../../../../../ import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../../common/viewModel.js'; -import { IInlineEditsView, IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor, modifiedChangedLineBackgroundColor } from '../theme.js'; import { getPrefixTrim, mapOutFalsy, rectToProps } from '../utils/utils.js'; import { rangesToBubbleRanges, Replacement } from './inlineEditsWordReplacementView.js'; @@ -197,7 +197,7 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin l.style.position = 'relative'; }); - const modifiedBorderColor = getModifiedBorderColor(this._host.tabAction).read(reader); + const modifiedBorderColor = getModifiedBorderColor(this._tabAction).read(reader); return [ n.div({ @@ -287,7 +287,7 @@ export class InlineEditsLineReplacementView extends Disposable implements IInlin modifiedLines: string[]; replacements: Replacement[]; } | undefined>, - private readonly _host: IInlineEditsViewHost, + private readonly _tabAction: IObservable, @ILanguageService private readonly _languageService: ILanguageService ) { super(); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts index a2e6f5fa233..41ff1f50b23 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsSideBySideView.ts @@ -23,7 +23,7 @@ import { Range } from '../../../../../../common/core/range.js'; import { ITextModel } from '../../../../../../common/model.js'; import { StickyScrollController } from '../../../../../stickyScroll/browser/stickyScrollController.js'; import { InlineCompletionContextKeys } from '../../../controller/inlineCompletionContextKeys.js'; -import { IInlineEditsView, IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { InlineEditWithChanges } from '../inlineEditWithChanges.js'; import { getModifiedBorderColor, getOriginalBorderColor, modifiedBackgroundColor, originalBackgroundColor } from '../theme.js'; import { PathBuilder, createRectangle, getOffsetForPos, mapOutFalsy, maxContentWidthInRange } from '../utils/utils.js'; @@ -64,7 +64,7 @@ export class InlineEditsSideBySideView extends Disposable implements IInlineEdit newTextLineCount: number; originalDisplayRange: LineRange; } | undefined>, - private readonly _host: IInlineEditsViewHost, + private readonly _tabAction: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, ) { @@ -123,7 +123,7 @@ export class InlineEditsSideBySideView extends Disposable implements IInlineEdit private readonly previewRef = n.ref(); private readonly _editorContainer = n.div({ - class: ['editorContainer', this._editorObs.getOption(EditorOption.inlineSuggest).map(v => !v.edits.useGutterIndicator && 'showHover')], + class: ['editorContainer'], style: { position: 'absolute', overflow: 'hidden', cursor: 'pointer' }, onmousedown: e => { e.preventDefault(); // This prevents that the editor loses focus @@ -508,8 +508,8 @@ export class InlineEditsSideBySideView extends Disposable implements IInlineEdit const layoutInfoObs = mapOutFalsy(this._previewEditorLayoutInfo).read(reader); if (!layoutInfoObs) { return undefined; } - const modifiedBorderColor = getModifiedBorderColor(this._host.tabAction).read(reader); - const originalBorderColor = getOriginalBorderColor(this._host.tabAction).read(reader); + const modifiedBorderColor = getModifiedBorderColor(this._tabAction).read(reader); + const originalBorderColor = getOriginalBorderColor(this._tabAction).read(reader); return [ n.svgElem('path', { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts index d3ee68c60cc..d18dad373c3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordInsertView.ts @@ -14,9 +14,9 @@ import { Rect } from '../../../../../../browser/rect.js'; import { EditorOption } from '../../../../../../common/config/editorOptions.js'; import { OffsetRange } from '../../../../../../common/core/offsetRange.js'; import { SingleTextEdit } from '../../../../../../common/core/textEdit.js'; -import { IInlineEditsView } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor } from '../theme.js'; -import { InlineEditTabAction, mapOutFalsy, rectToProps } from '../utils/utils.js'; +import { mapOutFalsy, rectToProps } from '../utils/utils.js'; export class InlineEditsWordInsertView extends Disposable implements IInlineEditsView { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts index 86d42cb4de3..f7272f15052 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/inlineEditsWordReplacementView.ts @@ -7,7 +7,7 @@ import { getWindow, n, ObserverNodeWithElement } from '../../../../../../../base import { IMouseEvent, StandardMouseEvent } from '../../../../../../../base/browser/mouseEvent.js'; import { Emitter } from '../../../../../../../base/common/event.js'; import { Disposable } from '../../../../../../../base/common/lifecycle.js'; -import { constObservable, derived, observableValue } from '../../../../../../../base/common/observable.js'; +import { constObservable, derived, IObservable, observableValue } from '../../../../../../../base/common/observable.js'; import { editorBackground, editorHoverForeground, scrollbarShadow } from '../../../../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../../browser/observableCodeEditor.js'; @@ -22,7 +22,7 @@ import { SingleTextEdit } from '../../../../../../common/core/textEdit.js'; import { ILanguageService } from '../../../../../../common/languages/language.js'; import { LineTokens } from '../../../../../../common/tokens/lineTokens.js'; import { TokenArray } from '../../../../../../common/tokens/tokenArray.js'; -import { IInlineEditsView, IInlineEditsViewHost } from '../inlineEditsViewInterface.js'; +import { IInlineEditsView, InlineEditTabAction } from '../inlineEditsViewInterface.js'; import { getModifiedBorderColor, modifiedChangedTextOverlayColor, originalChangedTextOverlayColor, replacementViewBackground } from '../theme.js'; import { mapOutFalsy, rectToProps } from '../utils/utils.js'; @@ -47,7 +47,7 @@ export class InlineEditsWordReplacementView extends Disposable implements IInlin /** Must be single-line in both sides */ private readonly _edit: SingleTextEdit, private readonly _innerEdits: SingleTextEdit[], - private readonly _host: IInlineEditsViewHost, + private readonly _tabAction: IObservable, @ILanguageService private readonly _languageService: ILanguageService, ) { super(); @@ -162,7 +162,7 @@ export class InlineEditsWordReplacementView extends Disposable implements IInlin const edits = layoutProps.innerEdits.map(edit => ({ modified: edit.modified.translateX(-contentLeft), original: edit.original.translateX(-contentLeft) })); - const modifiedBorderColor = getModifiedBorderColor(this._host.tabAction).read(reader); + const modifiedBorderColor = getModifiedBorderColor(this._tabAction).read(reader); return [ n.div({ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts index 85cc06a8a27..056fbf695d1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/theme.ts @@ -8,7 +8,7 @@ import { IObservable } from '../../../../../../base/common/observable.js'; import { localize } from '../../../../../../nls.js'; import { diffRemoved, diffInsertedLine, diffInserted, editorHoverBorder, editorHoverStatusBarBackground, buttonBackground, buttonForeground, buttonSecondaryBackground, buttonSecondaryForeground } from '../../../../../../platform/theme/common/colorRegistry.js'; import { registerColor, transparent, asCssVariable, lighten, darken } from '../../../../../../platform/theme/common/colorUtils.js'; -import { InlineEditTabAction } from './utils/utils.js'; +import { InlineEditTabAction } from './inlineEditsViewInterface.js'; export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts index 9427e0b23a0..a393e04e241 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts @@ -26,12 +26,6 @@ import { SingleTextEdit, TextEdit } from '../../../../../../common/core/textEdit import { RangeMapping } from '../../../../../../common/diff/rangeMapping.js'; import { indentOfLine } from '../../../../../../common/model/textModel.js'; -export enum InlineEditTabAction { - Jump = 'jump', - Accept = 'accept', - Inactive = 'inactive' -} - export function maxContentWidthInRange(editor: ObservableCodeEditor, range: LineRange, reader: IReader | undefined): number { editor.layoutInfo.read(reader); editor.value.read(reader);