Skip to content

Commit

Permalink
Add support for multi-line insertions with Ghost Text (#238685)
Browse files Browse the repository at this point in the history
* multi line insertions with Ghost Text

* 💄
  • Loading branch information
benibenj authored Jan 24, 2025
1 parent 535fe55 commit aaa070a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4216,6 +4216,7 @@ export interface IInlineSuggestOptions {
useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible';
useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump';
useCodeOverlay?: 'never' | 'whenPossible' | 'moveCodeWhenPossible';
useMultiLineGhostText?: boolean;

useGutterIndicator?: boolean;
};
Expand Down Expand Up @@ -4251,6 +4252,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
useInterleavedLinesDiff: 'never',
useGutterIndicator: true,
useCodeOverlay: 'moveCodeWhenPossible',
useMultiLineGhostText: false
},
},
};
Expand Down Expand Up @@ -4306,6 +4308,11 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
description: nls.localize('inlineSuggest.edits.experimental.useCodeOverlay', "Controls whether suggestions may be shown above the code."),
enum: ['never', 'whenPossible', 'moveCodeWhenPossible'],
},
'editor.inlineSuggest.edits.experimental.useMultiLineGhostText': {
type: 'boolean',
default: defaults.edits.experimental.useMultiLineGhostText,
description: nls.localize('inlineSuggest.edits.experimental.useMultiLineGhostText', "Controls whether multi line insertions can be shown with Ghost text."),
},
'editor.inlineSuggest.edits.experimental.useInterleavedLinesDiff': {
type: 'string',
default: defaults.edits.experimental.useInterleavedLinesDiff,
Expand Down Expand Up @@ -4341,6 +4348,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
useCodeOverlay: stringSet(input.edits?.experimental?.useCodeOverlay, this.defaultValue.edits.experimental.useCodeOverlay, ['never', 'whenPossible', 'moveCodeWhenPossible']),
useInterleavedLinesDiff: stringSet(input.edits?.experimental?.useInterleavedLinesDiff, this.defaultValue.edits.experimental.useInterleavedLinesDiff, ['never', 'always', 'afterJump']),
useGutterIndicator: boolean(input.edits?.experimental?.useGutterIndicator, this.defaultValue.edits.experimental.useGutterIndicator),
useMultiLineGhostText: boolean(input.edits?.experimental?.useMultiLineGhostText, this.defaultValue.edits.experimental.useMultiLineGhostText),
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { autorunWithStore, derived, IObservable, IReader, ISettableObservable, mapObservableArrayCached } from '../../../../../../base/common/observable.js';
import { autorunWithStore, constObservable, derived, IObservable, IReader, ISettableObservable, mapObservableArrayCached } 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';
Expand All @@ -15,7 +15,9 @@ import { SingleTextEdit, StringText } from '../../../../../common/core/textEdit.
import { TextLength } from '../../../../../common/core/textLength.js';
import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js';
import { TextModel } from '../../../../../common/model/textModel.js';
import { GhostText, GhostTextPart } from '../../model/ghostText.js';
import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js';
import { GhostTextView } from '../ghostText/ghostTextView.js';
import { InlineEditsDeletionView } from './deletionView.js';
import { InlineEditsGutterIndicator } from './gutterIndicatorView.js';
import { IInlineEditsIndicatorState, InlineEditsIndicator } from './indicatorView.js';
Expand All @@ -32,6 +34,7 @@ export class InlineEditsView extends Disposable {
private readonly _useMixedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useMixedLinesDiff);
private readonly _useInterleavedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useInterleavedLinesDiff);
private readonly _useCodeOverlay = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useCodeOverlay);
private readonly _useMultiLineGhostText = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useMultiLineGhostText);

private _previousView: {
id: string;
Expand Down Expand Up @@ -135,10 +138,32 @@ export class InlineEditsView extends Disposable {
}) : undefined),
));

protected readonly _insertion = this._register(this._instantiationService.createInstance(GhostTextView,
this._editor,
{
ghostText: derived<GhostText | undefined>(reader => {
const state = this._uiState.read(reader)?.state;
if (!state || state.kind !== 'insertion') { return undefined; }

const textModel = this._editor.getModel()!;

// Try to not insert on the same line where there is other content
if (state.column === 1 && state.lineNumber > 1 && textModel.getLineLength(state.lineNumber) !== 0 && state.text.endsWith('\n') && !state.text.startsWith('\n')) {
const endOfLineColumn = textModel.getLineLength(state.lineNumber - 1) + 1;
return new GhostText(state.lineNumber - 1, [new GhostTextPart(endOfLineColumn, '\n' + state.text.slice(0, -1), false)]);
}

return new GhostText(state.lineNumber, [new GhostTextPart(state.column, state.text, false)]);
}),
minReservedLineCount: constObservable(0),
targetTextModel: this._model.map(v => v?.textModel),
}
));

private readonly _inlineDiffViewState = derived<IOriginalEditorInlineDiffViewState | undefined>(this, reader => {
const e = this._uiState.read(reader);
if (!e || !e.state) { return undefined; }
if (e.state.kind === 'wordReplacements' || e.state.kind === 'lineReplacement') {
if (e.state.kind === 'wordReplacements' || e.state.kind === 'lineReplacement' || e.state.kind === 'insertion') {
return undefined;
}
return {
Expand Down Expand Up @@ -238,6 +263,10 @@ export class InlineEditsView extends Disposable {
return 'deletion';
}

if (isSingleMultiLineInsertion(diff) && this._useMultiLineGhostText.read(reader)) {
return 'insertion';
}

const useCodeOverlay = this._useCodeOverlay.read(reader);
if (useCodeOverlay !== 'never') {
const numOriginalLines = edit.originalLineRange.length;
Expand Down Expand Up @@ -288,6 +317,16 @@ export class InlineEditsView extends Disposable {
};
}

if (view === 'insertion') {
const change = inner[0];
return {
kind: 'insertion' as const,
lineNumber: change.originalRange.startLineNumber,
column: change.originalRange.startColumn,
text: newText.getValueOfRange(change.modifiedRange),
};
}

const replacements = inner.map(m => new SingleTextEdit(m.originalRange, newText.getValueOfRange(m.modifiedRange)));
if (replacements.length === 0) {
return undefined;
Expand Down Expand Up @@ -341,6 +380,24 @@ function isSingleLineInsertionAfterPosition(diff: DetailedLineRangeMapping[], po
}
}

function isSingleMultiLineInsertion(diff: DetailedLineRangeMapping[]) {
const inner = diff.flatMap(d => d.innerChanges ?? []);
if (inner.length !== 1) {
return false;
}

const change = inner[0];
if (!change.originalRange.isEmpty()) {
return false;
}

if (change.modifiedRange.startLineNumber === change.modifiedRange.endLineNumber) {
return false;
}

return true;
}

function isSingleLineDeletion(diff: DetailedLineRangeMapping[]): boolean {
return diff.every(m => m.innerChanges!.every(r => isDeletion(r)));

Expand Down
1 change: 1 addition & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4614,6 +4614,7 @@ declare namespace monaco.editor {
useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible';
useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump';
useCodeOverlay?: 'never' | 'whenPossible' | 'moveCodeWhenPossible';
useMultiLineGhostText?: boolean;
useGutterIndicator?: boolean;
};
};
Expand Down

0 comments on commit aaa070a

Please sign in to comment.