Skip to content

Commit

Permalink
Add useMixedLinesDiff: forStableInsertions (microsoft#236090)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdima authored Dec 13, 2024
1 parent 8abb9e0 commit bd78675
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 31 deletions.
8 changes: 4 additions & 4 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4195,7 +4195,7 @@ export interface IInlineSuggestOptions {
edits?: {
experimental?: {
enabled?: boolean;
useMixedLinesDiff?: 'never' | 'whenPossible' | 'afterJumpWhenPossible';
useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible';
useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump';
onlyShowWhenCloseToCursor?: boolean;
};
Expand Down Expand Up @@ -4227,7 +4227,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
edits: {
experimental: {
enabled: true,
useMixedLinesDiff: 'never',
useMixedLinesDiff: 'forStableInsertions',
useInterleavedLinesDiff: 'never',
onlyShowWhenCloseToCursor: true,
},
Expand Down Expand Up @@ -4277,7 +4277,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
type: 'string',
default: defaults.edits.experimental.useMixedLinesDiff,
description: nls.localize('inlineSuggest.edits.experimental.useMixedLinesDiff', "Controls whether to enable experimental edits in inline suggestions."),
enum: ['never', 'whenPossible'],
enum: ['never', 'whenPossible', 'forStableInsertions', 'afterJumpWhenPossible'],
},
'editor.inlineSuggest.edits.experimental.useInterleavedLinesDiff': {
type: 'string',
Expand Down Expand Up @@ -4310,7 +4310,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
edits: {
experimental: {
enabled: boolean(input.edits?.experimental?.enabled, this.defaultValue.edits.experimental.enabled),
useMixedLinesDiff: stringSet(input.edits?.experimental?.useMixedLinesDiff, this.defaultValue.edits.experimental.useMixedLinesDiff, ['never', 'whenPossible', 'afterJumpWhenPossible']),
useMixedLinesDiff: stringSet(input.edits?.experimental?.useMixedLinesDiff, this.defaultValue.edits.experimental.useMixedLinesDiff, ['never', 'whenPossible', 'forStableInsertions', 'afterJumpWhenPossible']),
useInterleavedLinesDiff: stringSet(input.edits?.experimental?.useInterleavedLinesDiff, this.defaultValue.edits.experimental.useInterleavedLinesDiff, ['never', 'always', 'afterJump']),
onlyShowWhenCloseToCursor: boolean(input.edits?.experimental?.onlyShowWhenCloseToCursor, this.defaultValue.edits.experimental.onlyShowWhenCloseToCursor),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class InlineCompletionsModel extends Disposable {

// We use a semantic id to keep the same inline completion selected even if the provider reorders the completions.
private readonly _selectedInlineCompletionId = observableValue<string | undefined>(this, undefined);
private readonly _primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1));
public readonly primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1));

private _isAcceptingPartially = false;
public get isAcceptingPartially() { return this._isAcceptingPartially; }
Expand Down Expand Up @@ -194,7 +194,7 @@ export class InlineCompletionsModel extends Disposable {
});
}

const cursorPosition = this._primaryPosition.get();
const cursorPosition = this.primaryPosition.get();
if (changeSummary.dontRefetch) {
return Promise.resolve(true);
}
Expand Down Expand Up @@ -257,7 +257,7 @@ export class InlineCompletionsModel extends Disposable {
private readonly _inlineCompletionItems = derivedOpts({ owner: this }, reader => {
const c = this._source.inlineCompletions.read(reader);
if (!c) { return undefined; }
const cursorPosition = this._primaryPosition.read(reader);
const cursorPosition = this.primaryPosition.read(reader);
let inlineEdit: InlineCompletionWithUpdatedRange | undefined = undefined;
const visibleCompletions: InlineCompletionWithUpdatedRange[] = [];
for (const completion of c.inlineCompletions) {
Expand Down Expand Up @@ -355,10 +355,10 @@ export class InlineCompletionsModel extends Disposable {
let edit = item.inlineEdit.toSingleTextEdit(reader);
edit = singleTextRemoveCommonPrefix(edit, model);

const cursorPos = this._primaryPosition.read(reader);
const cursorPos = this.primaryPosition.read(reader);
const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber);

const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this._primaryPosition.read(reader).lineNumber);
const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this.primaryPosition.read(reader).lineNumber);

if (this._onlyShowWhenCloseToCursor.read(reader) && cursorDist > 3 && !item.inlineEdit.request.isExplicitRequest && !this._inAcceptFlow.read(reader)) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { classNames } from './utils.js';
export interface IOriginalEditorInlineDiffViewState {
diff: DetailedLineRangeMapping[];
modifiedText: AbstractText;
mode: 'mixedLines' | 'interleavedLines' | 'sideBySide';
mode: 'mixedLines' | 'ghostText' | 'interleavedLines' | 'sideBySide';

modifiedCodeEditor: ICodeEditor;
}
Expand Down Expand Up @@ -111,7 +111,7 @@ export class OriginalEditorInlineDiffView extends Disposable {
if (!diff) { return undefined; }

const modified = diff.modifiedText;
const showInline = diff.mode === 'mixedLines';
const showInline = diff.mode === 'mixedLines' || diff.mode === 'ghostText';

const showEmptyDecorations = true;

Expand Down Expand Up @@ -214,7 +214,7 @@ export class OriginalEditorInlineDiffView extends Disposable {
description: 'inserted-text',
before: {
content: insertedText,
inlineClassName: 'inlineCompletions-char-insert',
inlineClassName: diff.mode === 'ghostText' ? 'ghost-text-decoration' : 'inlineCompletions-char-insert',
},
zIndex: 2,
showIfCollapsed: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,23 @@
*--------------------------------------------------------------------------------------------*/

import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { derived, IObservable } from '../../../../../../base/common/observable.js';
import { derived, IObservable, IReader } 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 { EditorOption } from '../../../../../common/config/editorOptions.js';
import { LineRange } from '../../../../../common/core/lineRange.js';
import { Position } from '../../../../../common/core/position.js';
import { StringText } from '../../../../../common/core/textEdit.js';
import { DetailedLineRangeMapping, lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js';
import { TextModel } from '../../../../../common/model/textModel.js';
import './view.css';
import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js';
import { IInlineEditsIndicatorState, InlineEditsIndicator } from './indicatorView.js';
import { IOriginalEditorInlineDiffViewState, OriginalEditorInlineDiffView } from './inlineDiffView.js';
import { InlineEditsSideBySideDiff } from './sideBySideDiff.js';
import { applyEditToModifiedRangeMappings, createReindentEdit } from './utils.js';
import { IInlineEditsIndicatorState, InlineEditsIndicator } from './indicatorView.js';
import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js';
import './view.css';
import { InlineEditWithChanges } from './viewAndDiffProducer.js';
import { InlineEditsSideBySideDiff } from './sideBySideDiff.js';

export class InlineEditsView extends Disposable {
private readonly _editorObs = observableCodeEditor(this._editor);
Expand All @@ -37,7 +38,7 @@ export class InlineEditsView extends Disposable {
}

private readonly _uiState = derived<{
state: 'collapsed' | 'mixedLines' | 'interleavedLines' | 'sideBySide';
state: 'collapsed' | 'mixedLines' | 'ghostText' | 'interleavedLines' | 'sideBySide';
diff: DetailedLineRangeMapping[];
edit: InlineEditWithChanges;
newText: string;
Expand All @@ -55,17 +56,7 @@ export class InlineEditsView extends Disposable {
let newText = edit.edit.apply(edit.originalText);
let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText));

let state: 'collapsed' | 'mixedLines' | 'interleavedLines' | 'sideBySide';
if (edit.isCollapsed) {
state = 'collapsed';
} else if (diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) &&
(this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible'))) {
state = 'mixedLines';
} else if ((this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump'))) {
state = 'interleavedLines';
} else {
state = 'sideBySide';
}
const state = this.determinRenderState(edit, reader, diff);

if (state === 'sideBySide') {
const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange);
Expand Down Expand Up @@ -140,4 +131,57 @@ export class InlineEditsView extends Disposable {
}),
this._model,
));

private determinRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[]) {
if (edit.isCollapsed) {
return 'collapsed';
}

if (
(this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible'))
&& diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m))
) {
return 'mixedLines';
}

if (
this._useMixedLinesDiff.read(reader) === 'forStableInsertions'
&& isInsertionAfterPosition(diff, edit.cursorPosition)
) {
return 'ghostText';
}

if (this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump')) {
return 'interleavedLines';
}

return 'sideBySide';
}
}

function isInsertionAfterPosition(diff: DetailedLineRangeMapping[], position: Position | null) {
if (!position) {
return false;
}
const pos = position;

return diff.every(m => m.innerChanges!.every(r => isStableWordInsertion(r)));

function isStableWordInsertion(r: RangeMapping) {
if (!r.originalRange.isEmpty()) {
return false;
}
const isInsertionWithinLine = r.modifiedRange.startLineNumber === r.modifiedRange.endLineNumber;
if (!isInsertionWithinLine) {
return false;
}
const insertPosition = r.originalRange.getStartPosition();
if (pos.isBeforeOrEqual(insertPosition)) {
return true;
}
if (insertPosition.lineNumber < pos.lineNumber) {
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export class InlineEditsViewAndDiffProducer extends Disposable {
});

private readonly _inlineEditPromise = derived<IObservable<InlineEditWithChanges | undefined> | undefined>(this, (reader) => {
const model = this._model.read(reader);
if (!model) { return undefined; }
const inlineEdit = this._edit.read(reader);
if (!inlineEdit) { return undefined; }

Expand Down Expand Up @@ -86,7 +88,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable {
));
const diffEdits = new TextEdit(edits);

return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); //inlineEdit.showInlineIfPossible);
return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, model.primaryPosition.get(), inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); //inlineEdit.showInlineIfPossible);
});
});

Expand Down Expand Up @@ -116,6 +118,7 @@ export class InlineEditWithChanges {
public readonly originalText: AbstractText,
public readonly edit: TextEdit,
public readonly isCollapsed: boolean,
public readonly cursorPosition: Position,
public readonly userJumpedToIt: boolean,
public readonly commands: readonly Command[],
public readonly inlineCompletion: InlineCompletionItem,
Expand All @@ -126,6 +129,7 @@ export class InlineEditWithChanges {
return this.originalText.getValue() === other.originalText.getValue() &&
this.edit.equals(other.edit) &&
this.isCollapsed === other.isCollapsed &&
this.cursorPosition.equals(other.cursorPosition) &&
this.userJumpedToIt === other.userJumpedToIt &&
this.commands === other.commands &&
this.inlineCompletion === other.inlineCompletion;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4602,7 +4602,7 @@ declare namespace monaco.editor {
edits?: {
experimental?: {
enabled?: boolean;
useMixedLinesDiff?: 'never' | 'whenPossible' | 'afterJumpWhenPossible';
useMixedLinesDiff?: 'never' | 'whenPossible' | 'forStableInsertions' | 'afterJumpWhenPossible';
useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump';
onlyShowWhenCloseToCursor?: boolean;
};
Expand Down

0 comments on commit bd78675

Please sign in to comment.