Merge pull request #256083 from BartolHrg/bhrg/multiple-empty-copy

fix copy with multiple cursors and empty selections
This commit is contained in:
Alexandru Dima
2025-12-18 08:45:45 +01:00
committed by GitHub
4 changed files with 64 additions and 23 deletions

View File

@@ -664,15 +664,15 @@ export class PasteOperation {
}
private static _distributePasteToCursors(config: CursorConfiguration, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): string[] | null {
if (pasteOnNewLine) {
return null;
}
if (selections.length === 1) {
return null;
}
if (multicursorText && multicursorText.length === selections.length) {
return multicursorText;
}
if (pasteOnNewLine) {
return null;
}
if (config.multiCursorPaste === 'spread') {
// Try to spread the pasted text in case the line count matches the cursor count
// Remove trailing \n if present

View File

@@ -959,33 +959,20 @@ export class ViewModel extends Disposable implements IViewModel {
}
}
if (!hasNonEmptyRange) {
if (!hasNonEmptyRange && !emptySelectionClipboard) {
// all ranges are empty
if (!emptySelectionClipboard) {
return '';
}
const modelLineNumbers = modelRanges.map((r) => r.startLineNumber);
let result = '';
for (let i = 0; i < modelLineNumbers.length; i++) {
if (i > 0 && modelLineNumbers[i - 1] === modelLineNumbers[i]) {
continue;
}
result += this.model.getLineContent(modelLineNumbers[i]) + newLineCharacter;
}
return result;
return '';
}
if (hasEmptyRange && emptySelectionClipboard) {
// mixed empty selections and non-empty selections
// some (maybe all) empty selections
const result: string[] = [];
let prevModelLineNumber = 0;
for (const modelRange of modelRanges) {
const modelLineNumber = modelRange.startLineNumber;
if (modelRange.isEmpty()) {
if (modelLineNumber !== prevModelLineNumber) {
result.push(this.model.getLineContent(modelLineNumber));
result.push(this.model.getLineContent(modelLineNumber) + newLineCharacter);
}
} else {
result.push(this.model.getValueInRange(modelRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));

View File

@@ -2239,6 +2239,37 @@ suite('Editor Controller', () => {
});
});
test('issue #256039: paste from multiple cursors with empty selections and multiCursorPaste full', () => {
usingCursor({
text: [
'line1',
'line2',
'line3'
],
editorOpts: {
multiCursorPaste: 'full'
}
}, (editor, model, viewModel) => {
// 2 cursors on lines 1 and 2
viewModel.setSelections('test', [new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
viewModel.paste(
'line1\nline2\n',
true,
['line1\n', 'line2\n']
);
// Each cursor gets its respective line
assert.strictEqual(model.getValue(), [
'line1',
'line1',
'line2',
'line2',
'line3'
].join('\n'));
});
});
test('issue #3071: Investigate why undo stack gets corrupted', () => {
const model = createTextModel(
[

View File

@@ -198,7 +198,27 @@ suite('ViewModel', () => {
new Range(3, 2, 3, 2),
],
true,
'line2\nline3\n'
[
'line2\n',
'line3\n'
]
);
});
test('issue #256039: getPlainTextToCopy with multiple cursors and empty selections should return array', () => {
// Bug: When copying with multiple cursors (empty selections) with emptySelectionClipboard enabled,
// the result should be an array so that pasting with "editor.multiCursorPaste": "full"
// correctly distributes each line to the corresponding cursor.
// Without the fix, this returns 'line2\nline3\n' (a single string).
// With the fix, this returns ['line2\n', 'line3\n'] (an array).
assertGetPlainTextToCopy(
USUAL_TEXT,
[
new Range(2, 1, 2, 1),
new Range(3, 1, 3, 1),
],
true,
['line2\n', 'line3\n']
);
});
@@ -222,7 +242,7 @@ suite('ViewModel', () => {
new Range(3, 2, 3, 2),
],
true,
['ine2', 'line3']
['ine2', 'line3\n']
);
});
@@ -259,7 +279,10 @@ suite('ViewModel', () => {
new Range(3, 2, 3, 2),
],
true,
'line2\nline3\n'
[
'line2\n',
'line3\n'
]
);
});