Skip to content

Commit 063d607

Browse files
authored
Allow rendering multi line ghost text at the start of the next line (#250008)
Allow rendering multi line ghost text when it is inserted on the start of the next line
1 parent 86585d7 commit 063d607

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,16 @@ export class InlineCompletionItem extends InlineSuggestionItemBase {
186186
textModel: ITextModel,
187187
): InlineCompletionItem {
188188
const identity = new InlineSuggestionIdentity();
189-
const textEdit = new TextReplacement(data.range, data.insertText);
190-
const edit = getPositionOffsetTransformerFromTextModel(textModel).getStringReplacement(textEdit);
189+
const transformer = getPositionOffsetTransformerFromTextModel(textModel);
190+
191+
const insertText = data.insertText.replace(/\r\n|\r|\n/g, textModel.getEOL());
192+
193+
const edit = reshapeInlineCompletion(new StringReplacement(transformer.getOffsetRange(data.range), insertText), textModel);
194+
const textEdit = transformer.getSingleTextEdit(edit);
195+
191196
const displayLocation = data.displayLocation ? InlineSuggestDisplayLocation.create(data.displayLocation, textModel) : undefined;
192197

193-
return new InlineCompletionItem(edit, textEdit, data.range, data.snippetInfo, data.additionalTextEdits, data, identity, displayLocation);
198+
return new InlineCompletionItem(edit, textEdit, textEdit.range, data.snippetInfo, data.additionalTextEdits, data, identity, displayLocation);
194199
}
195200

196201
public readonly isInlineEdit = false;
@@ -456,7 +461,7 @@ function getStringEdit(textModel: ITextModel, editRange: Range, replaceText: str
456461
const edit = new StringReplacement(originalRange, replaceText);
457462

458463
const originalText = textModel.getValueInRange(rangeInModel);
459-
return reshapeEdit(edit, originalText, innerChanges.length, textModel);
464+
return reshapeInlineEdit(edit, originalText, innerChanges.length, textModel);
460465
})
461466
);
462467

@@ -591,7 +596,18 @@ class SingleUpdatedNextEdit {
591596
}
592597
}
593598

594-
function reshapeEdit(edit: StringReplacement, originalText: string, totalInnerEdits: number, textModel: ITextModel): StringReplacement {
599+
function reshapeInlineCompletion(edit: StringReplacement, textModel: ITextModel): StringReplacement {
600+
// If the insertion is a multi line insertion starting on the next line
601+
// Move it forwards so that the multi line insertion starts on the current line
602+
const eol = textModel.getEOL();
603+
if (edit.replaceRange.isEmpty && edit.newText.includes(eol)) {
604+
edit = reshapeMultiLineInsertion(edit, textModel);
605+
}
606+
607+
return edit;
608+
}
609+
610+
function reshapeInlineEdit(edit: StringReplacement, originalText: string, totalInnerEdits: number, textModel: ITextModel): StringReplacement {
595611
// TODO: EOL are not properly trimmed by the diffAlgorithm #12680
596612
const eol = textModel.getEOL();
597613
if (edit.newText.endsWith(eol) && originalText.endsWith(eol)) {
@@ -602,7 +618,11 @@ function reshapeEdit(edit: StringReplacement, originalText: string, totalInnerEd
602618
// If the insertion ends with a new line and is inserted at the start of a line which has text,
603619
// we move the insertion to the end of the previous line if possible
604620
if (totalInnerEdits === 1 && edit.replaceRange.isEmpty && edit.newText.includes(eol)) {
605-
edit = reshapeMultiLineInsertion(edit, textModel);
621+
const startPosition = textModel.getPositionAt(edit.replaceRange.start);
622+
const hasTextOnInsertionLine = textModel.getLineLength(startPosition.lineNumber) !== 0;
623+
if (hasTextOnInsertionLine) {
624+
edit = reshapeMultiLineInsertion(edit, textModel);
625+
}
606626
}
607627

608628
// The diff algorithm extended a simple edit to the entire word
@@ -641,7 +661,7 @@ function reshapeMultiLineInsertion(edit: StringReplacement, textModel: ITextMode
641661

642662
// If the insertion ends with a new line and is inserted at the start of a line which has text,
643663
// we move the insertion to the end of the previous line if possible
644-
if (startColumn === 1 && startLineNumber > 1 && textModel.getLineLength(startLineNumber) !== 0 && edit.newText.endsWith(eol) && !edit.newText.startsWith(eol)) {
664+
if (startColumn === 1 && startLineNumber > 1 && edit.newText.endsWith(eol) && !edit.newText.startsWith(eol)) {
645665
return new StringReplacement(edit.replaceRange.delta(-1), eol + edit.newText.slice(0, -eol.length));
646666
}
647667

src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ export class InlineEditsView extends Disposable {
192192
if (this._uiState.read(reader)?.state?.kind === 'insertionMultiLine') {
193193
return this._insertion.startLineOffset.read(reader);
194194
}
195+
196+
const ghostTextIndicator = this._ghostTextIndicator.read(reader);
197+
if (ghostTextIndicator) {
198+
return getGhostTextTopOffset(ghostTextIndicator, this._editor);
199+
}
200+
195201
return 0;
196202
});
197203
this._sideBySide = this._register(this._instantiationService.createInstance(InlineEditsSideBySideView,
@@ -580,3 +586,37 @@ function _growEdits(replacements: TextReplacement[], originalText: AbstractText,
580586

581587
return result;
582588
}
589+
590+
function getGhostTextTopOffset(ghostTextIndicator: GhostTextIndicator, editor: ICodeEditor): number {
591+
const replacements = ghostTextIndicator.model.inlineEdit.edit.replacements;
592+
if (replacements.length !== 1) {
593+
return 0;
594+
}
595+
596+
const textModel = editor.getModel();
597+
if (!textModel) {
598+
return 0;
599+
}
600+
601+
const EOL = textModel.getEOL();
602+
const replacement = replacements[0];
603+
if (replacement.range.isEmpty() && replacement.text.startsWith(EOL)) {
604+
const lineHeight = editor.getLineHeightForPosition(replacement.range.getStartPosition());
605+
return countPrefixRepeats(replacement.text, EOL) * lineHeight;
606+
}
607+
608+
return 0;
609+
}
610+
611+
function countPrefixRepeats(str: string, prefix: string): number {
612+
if (!prefix.length) {
613+
return 0;
614+
}
615+
let count = 0;
616+
let i = 0;
617+
while (str.startsWith(prefix, i)) {
618+
count++;
619+
i += prefix.length;
620+
}
621+
return count;
622+
}

0 commit comments

Comments
 (0)