Cleanup inline edits view (#242557)

This commit is contained in:
Benjamin Christopher Simmonds
2025-03-04 13:50:36 +01:00
committed by GitHub
parent d8e9c15fa3
commit 3bf4028bbd
17 changed files with 357 additions and 199 deletions

View File

@@ -4278,10 +4278,6 @@ export interface IInlineSuggestOptions {
* @internal
*/
useMultiLineGhostText?: boolean;
/**
* @internal
*/
useGutterIndicator?: boolean;
};
}
@@ -4313,7 +4309,6 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
useMixedLinesDiff: 'forStableInsertions',
useInterleavedLinesDiff: 'never',
renderSideBySide: 'auto',
useGutterIndicator: true,
codeShifting: true,
useMultiLineGhostText: true
},
@@ -4422,7 +4417,6 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
codeShifting: boolean(input.edits?.codeShifting, this.defaultValue.edits.codeShifting),
renderSideBySide: stringSet(input.edits?.renderSideBySide, this.defaultValue.edits.renderSideBySide, ['never', 'auto']),
useInterleavedLinesDiff: stringSet(input.edits?.useInterleavedLinesDiff, this.defaultValue.edits.useInterleavedLinesDiff, ['never', 'always', 'afterJump']),
useGutterIndicator: boolean(input.edits?.useGutterIndicator, this.defaultValue.edits.useGutterIndicator),
useMultiLineGhostText: boolean(input.edits?.useMultiLineGhostText, this.defaultValue.edits.useMultiLineGhostText),
},
};

View File

@@ -771,7 +771,7 @@ export class InlineCompletionsModel extends Disposable {
// TODO: clean this up if we keep it
private readonly _inAcceptPartialFlow = observableValue(this, false);
public readonly inAcceptPartialFlow: IObservable<boolean> = this._inAcceptPartialFlow;
public readonly inPartialAcceptFlow: IObservable<boolean> = this._inAcceptPartialFlow;
public async acceptNextInlineEditPart(editor: ICodeEditor): Promise<void> {
if (editor.getModel() !== this.textModel) {
throw new BugIndicatingError();

View File

@@ -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) => {

View File

@@ -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),

View File

@@ -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<LineRange | undefined>,
private readonly _verticalOffset: IObservable<number>,
private readonly _host: IInlineEditsViewHost,
private readonly _model: IObservable<IInlineEditModel | undefined>,
private readonly _isHoveringOverInlineEdit: IObservable<boolean>,
private readonly _focusIsInMenu: ISettableObservable<boolean>,
@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);

View File

@@ -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<InlineEditTabAction>,
) { }
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<InlineEditTabAction>(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,
);
}
}

View File

@@ -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<InlineEditTabAction>(reader => this._model.read(reader)?.tabAction.read(reader) ?? InlineEditTabAction.Inactive);
private _previousView: {
id: string;
view: ReturnType<typeof InlineEditsView.prototype.determineView>;
@@ -54,14 +55,19 @@ export class InlineEditsView extends Disposable {
constructor(
private readonly _editor: ICodeEditor,
private readonly _edit: IObservable<InlineEditWithChanges | undefined>,
private readonly _model: IObservable<InlineCompletionsModel | undefined>,
private readonly _model: IObservable<InlineEditModel | undefined>,
private readonly _ghostTextIndicator: IObservable<GhostTextIndicator | undefined>,
private readonly _focusIsInMenu: ISettableObservable<boolean>,
@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<string>(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<InlineEditTabAction>(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<InlineEditsGutterIndicator | InlineEditsIndicator | undefined>(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<IInlineEditsIndicatorState | undefined>(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<InlineEditModel | undefined>(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<IOriginalEditorInlineDiffViewState | undefined>(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');

View File

@@ -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<boolean>;
onDidClick: Event<IMouseEvent>;
}
export interface IInlineEditsViewHost {
displayName: IObservable<string>;
action: IObservable<Command | undefined>;
export interface IInlineEditModel {
displayName: string;
action: Command | undefined;
extensionCommands: Command[];
inlineEdit: InlineEditWithChanges;
tabAction: IObservable<InlineEditTabAction>;
extensionCommands: IObservable<readonly Command[] | undefined>;
inAcceptFlow: IObservable<boolean>;
inPartialAcceptFlow: IObservable<boolean>;
handleInlineEditShown(): void;
accept(): void;
jump(): void;
abort(reason: string): void;
}

View File

@@ -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<InlineEditWithChanges | undefined>(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<InlineEditModel | undefined>(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<InlineEditTabAction>(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<GhostTextIndicator | undefined>(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<InlineEdit | undefined>,
@@ -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));
}
}

View File

@@ -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<InlineEditTabAction>,
) {
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', {

View File

@@ -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<InlineEditTabAction>,
@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', {

View File

@@ -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<InlineEditTabAction>,
@ILanguageService private readonly _languageService: ILanguageService
) {
super();

View File

@@ -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<InlineEditTabAction>,
@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<HTMLDivElement>();
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', {

View File

@@ -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 {

View File

@@ -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<InlineEditTabAction>,
@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({

View File

@@ -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',

View File

@@ -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);