From 9aaee172571ed779c5d27ad356b74a023e4ca612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 14 Feb 2019 11:33:23 +0100 Subject: [PATCH 01/13] Fixes #68694 moving multiple consequitive lines --- .../browser/linesOperations.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 0ca8dd2abfb46..7f3b0fdc3326f 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -173,16 +173,25 @@ abstract class AbstractMoveLinesAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const languageConfigurationService = accessor.get(ILanguageConfigurationService); - const commands: ICommand[] = []; const selections = editor.getSelections() || []; + let newSelections: Selection[] = []; const autoIndent = editor.getOption(EditorOption.autoIndent); - for (const selection of selections) { - commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); + editor.pushUndoStop(); + + for (const selection of this.down ? selections.reverse() : selections) { + editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); + + const currentSelection = editor.getSelection(); + if (currentSelection) { + newSelections.push(currentSelection); + } + } + + if (newSelections.length) { + editor.setSelections(newSelections); } - editor.pushUndoStop(); - editor.executeCommands(this.id, commands); editor.pushUndoStop(); } } From d4a0b63fed314d232e34e157ba01f68f8ad3f23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 14 Feb 2019 13:53:41 +0100 Subject: [PATCH 02/13] Null-check + only apply setSelections if there are multiple new selections, otherwise its already selected --- .../contrib/linesOperations/browser/linesOperations.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 7f3b0fdc3326f..63e803ee00a46 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -182,13 +182,13 @@ abstract class AbstractMoveLinesAction extends EditorAction { for (const selection of this.down ? selections.reverse() : selections) { editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); - const currentSelection = editor.getSelection(); - if (currentSelection) { - newSelections.push(currentSelection); + const editorSelection = editor.getSelection(); + if (editorSelection !== null) { + newSelections.push(editorSelection); } } - if (newSelections.length) { + if (newSelections.length > 1) { editor.setSelections(newSelections); } From 2660c000b55d909e5b0ce337c91c625ad0ea6616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 14 Feb 2019 16:03:54 +0100 Subject: [PATCH 03/13] correction, reversing selections back when moving lines down --- .../editor/contrib/linesOperations/browser/linesOperations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 63e803ee00a46..90e71cd238a16 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -189,6 +189,10 @@ abstract class AbstractMoveLinesAction extends EditorAction { } if (newSelections.length > 1) { + if (this.down) { + newSelections.reverse(); + } + editor.setSelections(newSelections); } From 1e009f7f4ca5be0c68715674b8037235223c66d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Wed, 27 Feb 2019 13:22:50 +0100 Subject: [PATCH 04/13] Fixes having multiple selections on same line and moving them Correcting multiple move-lines when having made selections in reversed order --- .../browser/linesOperations.ts | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 90e71cd238a16..4593ac31b3c3f 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -172,31 +172,62 @@ abstract class AbstractMoveLinesAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const languageConfigurationService = accessor.get(ILanguageConfigurationService); + const commands: ICommand[] = []; + const autoIndent = editor.getOption(EditorOption.autoIndent); - const selections = editor.getSelections() || []; + let selections = editor.getSelections() || []; + let movingMultipleLines = false; + let selectionDirectionDown = true; let newSelections: Selection[] = []; - const autoIndent = editor.getOption(EditorOption.autoIndent); - editor.pushUndoStop(); + if (selections.length > 1) { + movingMultipleLines = selections[0].endLineNumber !== selections[selections.length - 1].endLineNumber; - for (const selection of this.down ? selections.reverse() : selections) { - editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); + if (movingMultipleLines) { + // Work only with one selection per line + selections = selections.filter((s, idx, arr) => { + return arr.map(sel => sel['endLineNumber']).indexOf(s['endLineNumber']) === idx; + }); - const editorSelection = editor.getSelection(); - if (editorSelection !== null) { - newSelections.push(editorSelection); + selectionDirectionDown = selections[0].endLineNumber < selections[selections.length - 1].endLineNumber; } } - if (newSelections.length > 1) { - if (this.down) { - newSelections.reverse(); + if (movingMultipleLines) { + editor.pushUndoStop(); + + if (selectionDirectionDown === this.down) { + selections.reverse(); } + } + + for (const selection of selections) { + if (movingMultipleLines) { + editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); - editor.setSelections(newSelections); + let editorSelection = editor.getSelection(); + if (editorSelection !== null) { + newSelections.push(editorSelection); + } + } else { + commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); + } } - editor.pushUndoStop(); + if (movingMultipleLines) { + if (newSelections.length > 1) { + if (selectionDirectionDown === this.down) { + newSelections.reverse(); + } + editor.setSelections(newSelections); + } + + editor.pushUndoStop(); + } else { + editor.pushUndoStop(); + editor.executeCommands(this.id, commands); + editor.pushUndoStop(); + } } } From d55b443e4f70da0e050e3b203c6557280688bdb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Fri, 1 Mar 2019 15:27:42 +0100 Subject: [PATCH 05/13] Stashing all selections before proceeding with selections distinct by line. Applies stash after line movement to keep previous selections. Aborting when moving multiple lines and the line is currently at top or end of document --- .../browser/linesOperations.ts | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 4593ac31b3c3f..b746740c6a185 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -184,31 +184,43 @@ abstract class AbstractMoveLinesAction extends EditorAction { movingMultipleLines = selections[0].endLineNumber !== selections[selections.length - 1].endLineNumber; if (movingMultipleLines) { + // Stash selections while processing, set new selections +/- line-change only + for (const selection of selections) { + let startCol = selection.getStartPosition().column; + let startLine = selection.getStartPosition().lineNumber; + let newStartLine = this.down ? startLine + 1 : startLine - 1; + + let endCol = selection.getEndPosition().column; + let endLine = selection.getEndPosition().lineNumber; + let newEndLine = this.down ? endLine + 1 : endLine - 1; + + newSelections.push(new Selection(newStartLine, startCol, newEndLine, endCol)); + } + // Work only with one selection per line selections = selections.filter((s, idx, arr) => { return arr.map(sel => sel['endLineNumber']).indexOf(s['endLineNumber']) === idx; }); selectionDirectionDown = selections[0].endLineNumber < selections[selections.length - 1].endLineNumber; - } - } - if (movingMultipleLines) { - editor.pushUndoStop(); + editor.pushUndoStop(); - if (selectionDirectionDown === this.down) { - selections.reverse(); + if (selectionDirectionDown === this.down) { + selections.reverse(); + } } } for (const selection of selections) { if (movingMultipleLines) { - editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); - - let editorSelection = editor.getSelection(); - if (editorSelection !== null) { - newSelections.push(editorSelection); + // If we are at the beginning/end of document and multiple lines are moved, abort. + if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === editor.getModel()?.getLineCount() && this.down)) { + editor.pushUndoStop(); + return; } + + editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); } else { commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); } @@ -216,9 +228,6 @@ abstract class AbstractMoveLinesAction extends EditorAction { if (movingMultipleLines) { if (newSelections.length > 1) { - if (selectionDirectionDown === this.down) { - newSelections.reverse(); - } editor.setSelections(newSelections); } From cf9dceeb6dac9d4f313270499bf2f0a9b48c093d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Fri, 1 Mar 2019 16:28:14 +0100 Subject: [PATCH 06/13] fix strict-null-check --- .../contrib/linesOperations/browser/linesOperations.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index b746740c6a185..88a2361fee622 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -215,7 +215,10 @@ abstract class AbstractMoveLinesAction extends EditorAction { for (const selection of selections) { if (movingMultipleLines) { // If we are at the beginning/end of document and multiple lines are moved, abort. - if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === editor.getModel()?.getLineCount() && this.down)) { + let model = editor.getModel(); + let lineCount = model !== null ? model.getLineCount() : 0; + + if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === lineCount && this.down)) { editor.pushUndoStop(); return; } From 076ad04c79573d045184db6fe84aa18c5e26f14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Wed, 3 Apr 2019 10:27:54 +0200 Subject: [PATCH 07/13] Adding testcases --- .../browser/linesOperations.ts | 4 +- .../test/browser/linesOperations.test.ts | 131 +++++++++++++++++- 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 88a2361fee622..40ecb72023204 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -243,7 +243,7 @@ abstract class AbstractMoveLinesAction extends EditorAction { } } -class MoveLinesUpAction extends AbstractMoveLinesAction { +export class MoveLinesUpAction extends AbstractMoveLinesAction { constructor() { super(false, { id: 'editor.action.moveLinesUpAction', @@ -265,7 +265,7 @@ class MoveLinesUpAction extends AbstractMoveLinesAction { } } -class MoveLinesDownAction extends AbstractMoveLinesAction { +export class MoveLinesDownAction extends AbstractMoveLinesAction { constructor() { super(true, { id: 'editor.action.moveLinesDownAction', diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 199b22c12bac4..a04e5afdcb589 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -12,7 +12,7 @@ import { Selection } from '../../../../common/core/selection.js'; import { Handler } from '../../../../common/editorCommon.js'; import { ITextModel } from '../../../../common/model.js'; import { ViewModel } from '../../../../common/viewModel/viewModelImpl.js'; -import { CamelCaseAction, PascalCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from '../../browser/linesOperations.js'; +import { CamelCaseAction, PascalCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction, MoveLinesUpAction, MoveLinesDownAction } from '../../browser/linesOperations.js'; import { withTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; import { createTextModel } from '../../../../test/common/testTextModel.js'; @@ -1640,4 +1640,133 @@ suite('Editor Contrib - Line Operations', () => { ] ); }); + + suite('Issue #68694 Move consequitive lines with multiple selections', () => { + test('Move lines down', () => { + const TEXT = [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]; + withTestCodeEditor(TEXT, {}, (editor) => { + editor.setSelections([ + new Selection(1, 1, 1, 1), + new Selection(2, 1, 2, 1), + ]); + + const moveLinesDownAction = new MoveLinesDownAction(); + moveLinesDownAction.run(null!, editor); + + let model = editor.getModel()!; + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 3', + 'Line 1', + 'Line 2', + 'Line 4', + 'Line 5' + ]); + + assert.deepStrictEqual( + editor.getSelections()!.toString(), [ + new Selection(2, 1, 2, 1), + new Selection(3, 1, 3, 1), + ].toString()); + }); + }); + + test('Move lines up', () => { + const TEXT = [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]; + withTestCodeEditor(TEXT, {}, (editor) => { + editor.setSelections([ + new Selection(3, 1, 3, 1), + new Selection(4, 1, 4, 1), + ]); + + const moveLinesUpAction = new MoveLinesUpAction(); + moveLinesUpAction.run(null!, editor); + + let model = editor.getModel()!; + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 1', + 'Line 3', + 'Line 4', + 'Line 2', + 'Line 5' + ]); + + assert.deepStrictEqual( + editor.getSelections()!.toString(), [ + new Selection(2, 1, 2, 1), + new Selection(3, 1, 3, 1), + ].toString()); + }); + }); + + test('Combining up and down', () => { + const TEXT = [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]; + withTestCodeEditor(TEXT, {}, (editor) => { + editor.setSelections([ + new Selection(1, 1, 1, 1), + new Selection(2, 1, 2, 1), + ]); + + const moveLinesUpAction = new MoveLinesUpAction(); + const moveLinesDownAction = new MoveLinesDownAction(); + + let model = editor.getModel()!; + + moveLinesUpAction.run(null!, editor); // no change since selections are already at top + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]); + + moveLinesDownAction.run(null!, editor); + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 3', + 'Line 1', + 'Line 2', + 'Line 4', + 'Line 5' + ]); + + moveLinesDownAction.run(null!, editor); + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 3', + 'Line 4', + 'Line 1', + 'Line 2', + 'Line 5' + ]); + + assert.deepStrictEqual( + editor.getSelections()!.toString(), [ + new Selection(3, 1, 3, 1), + new Selection(4, 1, 4, 1), + ].toString()); + }); + }); + }); }); From 69b0f3335525a76bea7de987bd14a68aeed050ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 12 Jun 2025 12:31:26 +0200 Subject: [PATCH 08/13] fix: move out distinct line filter out of loop + sort by line --- .../browser/linesOperations.ts | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 40ecb72023204..314bc8d5220ae 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -178,29 +178,40 @@ abstract class AbstractMoveLinesAction extends EditorAction { let selections = editor.getSelections() || []; let movingMultipleLines = false; let selectionDirectionDown = true; - let newSelections: Selection[] = []; + const selectionsAfterLineMove: Selection[] = []; // The same selection as before the line move, but with adjusted line numbers if (selections.length > 1) { + // Selections can be made by both keyboard or clicking, so the order of selections is not guaranteed + // But it's important to process the selections in sequential line order movingMultipleLines = selections[0].endLineNumber !== selections[selections.length - 1].endLineNumber; if (movingMultipleLines) { // Stash selections while processing, set new selections +/- line-change only + const distinctSelectionsPerLine: Selection[] = []; + let lastLineNumber = 0; + for (const selection of selections) { - let startCol = selection.getStartPosition().column; - let startLine = selection.getStartPosition().lineNumber; - let newStartLine = this.down ? startLine + 1 : startLine - 1; + const startCol = selection.getStartPosition().column; + const startLine = selection.getStartPosition().lineNumber; + const newStartLine = this.down ? startLine + 1 : startLine - 1; + + const endCol = selection.getEndPosition().column; + const endLine = selection.getEndPosition().lineNumber; + const newEndLine = this.down ? endLine + 1 : endLine - 1; - let endCol = selection.getEndPosition().column; - let endLine = selection.getEndPosition().lineNumber; - let newEndLine = this.down ? endLine + 1 : endLine - 1; + selectionsAfterLineMove.push(new Selection(newStartLine, startCol, newEndLine, endCol)); - newSelections.push(new Selection(newStartLine, startCol, newEndLine, endCol)); + // Work only with one selection per line + // if there are multiple selections in one line and we move each selection, the line would be moved multiple times + if (lastLineNumber !== selection.endLineNumber) { + distinctSelectionsPerLine.push(selection); + lastLineNumber = selection.endLineNumber; + } } - // Work only with one selection per line - selections = selections.filter((s, idx, arr) => { - return arr.map(sel => sel['endLineNumber']).indexOf(s['endLineNumber']) === idx; - }); + selections = distinctSelectionsPerLine; + // Sort selections by endLineNumber, important for processing of the move command + selections.sort((a, b) => a.endLineNumber - b.endLineNumber); selectionDirectionDown = selections[0].endLineNumber < selections[selections.length - 1].endLineNumber; @@ -215,8 +226,8 @@ abstract class AbstractMoveLinesAction extends EditorAction { for (const selection of selections) { if (movingMultipleLines) { // If we are at the beginning/end of document and multiple lines are moved, abort. - let model = editor.getModel(); - let lineCount = model !== null ? model.getLineCount() : 0; + const model = editor.getModel(); + const lineCount = model !== null ? model.getLineCount() : 0; if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === lineCount && this.down)) { editor.pushUndoStop(); @@ -230,8 +241,8 @@ abstract class AbstractMoveLinesAction extends EditorAction { } if (movingMultipleLines) { - if (newSelections.length > 1) { - editor.setSelections(newSelections); + if (selectionsAfterLineMove.length > 1) { + editor.setSelections(selectionsAfterLineMove); } editor.pushUndoStop(); From 8ac7d249a7f978b038b3c6c99ffce819d9aebe29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 12 Jun 2025 16:03:13 +0200 Subject: [PATCH 09/13] fix: improve line movement logic for multiple selections and add tests, also handle order of selections --- .../browser/linesOperations.ts | 112 +++++++++--------- .../test/browser/linesOperations.test.ts | 78 +++++++++++- 2 files changed, 131 insertions(+), 59 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 314bc8d5220ae..ca32d715f8373 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -175,82 +175,82 @@ abstract class AbstractMoveLinesAction extends EditorAction { const commands: ICommand[] = []; const autoIndent = editor.getOption(EditorOption.autoIndent); - let selections = editor.getSelections() || []; - let movingMultipleLines = false; - let selectionDirectionDown = true; - const selectionsAfterLineMove: Selection[] = []; // The same selection as before the line move, but with adjusted line numbers + const selections = editor.getSelections() || []; - if (selections.length > 1) { - // Selections can be made by both keyboard or clicking, so the order of selections is not guaranteed - // But it's important to process the selections in sequential line order - movingMultipleLines = selections[0].endLineNumber !== selections[selections.length - 1].endLineNumber; + // If there are multiple selections, there is some extra logic required. + // Selections can be made by both keyboard or clicking, so the order of selections is not guaranteed. + // It's important to process the selections in sequential line order + const distinctSelectionsPerLine: Selection[] = []; // These are the selections that will actually operated on - if (movingMultipleLines) { - // Stash selections while processing, set new selections +/- line-change only - const distinctSelectionsPerLine: Selection[] = []; - let lastLineNumber = 0; - - for (const selection of selections) { - const startCol = selection.getStartPosition().column; - const startLine = selection.getStartPosition().lineNumber; - const newStartLine = this.down ? startLine + 1 : startLine - 1; - - const endCol = selection.getEndPosition().column; - const endLine = selection.getEndPosition().lineNumber; - const newEndLine = this.down ? endLine + 1 : endLine - 1; - - selectionsAfterLineMove.push(new Selection(newStartLine, startCol, newEndLine, endCol)); - - // Work only with one selection per line - // if there are multiple selections in one line and we move each selection, the line would be moved multiple times - if (lastLineNumber !== selection.endLineNumber) { - distinctSelectionsPerLine.push(selection); - lastLineNumber = selection.endLineNumber; - } - } + // Same as initial selections, but with adjusted line numbers after the move + // These will however not be applied if it's a no-op + const selectionsAfterLineMove: Selection[] = []; + + // Stash selections while processing, set new selections with +/- line-change only + // Selections should not be sorted before this step + for (const selection of selections) { + const startCol = selection.getStartPosition().column; + const startLine = selection.getStartPosition().lineNumber; + const newStartLine = this.down ? startLine + 1 : startLine - 1; - selections = distinctSelectionsPerLine; - // Sort selections by endLineNumber, important for processing of the move command - selections.sort((a, b) => a.endLineNumber - b.endLineNumber); + const endCol = selection.getEndPosition().column; + const endLine = selection.getEndPosition().lineNumber; + const newEndLine = this.down ? endLine + 1 : endLine - 1; - selectionDirectionDown = selections[0].endLineNumber < selections[selections.length - 1].endLineNumber; + selectionsAfterLineMove.push(new Selection(newStartLine, startCol, newEndLine, endCol)); + } - editor.pushUndoStop(); + // Sort selections by line to guarantee correct processing order of the move line command + selections.sort((a, b) => a.endLineNumber - b.endLineNumber); + let lastLineNumber = 0; - if (selectionDirectionDown === this.down) { - selections.reverse(); - } + for (const selection of selections) { + // Work only with one selection per line, otherwise we risk to initiate multiple moves on a line + if (lastLineNumber !== selection.endLineNumber) { + distinctSelectionsPerLine.push(selection); + lastLineNumber = selection.endLineNumber; } } - for (const selection of selections) { - if (movingMultipleLines) { - // If we are at the beginning/end of document and multiple lines are moved, abort. - const model = editor.getModel(); - const lineCount = model !== null ? model.getLineCount() : 0; + if (this.down) { + // Selections are now sorted in ascending order + // Processing needs to be done in reversed order if we are moving lines down + // I.e. starting with the last selection in the bottom - otherwise we move the same line multiple times + distinctSelectionsPerLine.reverse(); + } - if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === lineCount && this.down)) { - editor.pushUndoStop(); - return; - } + const model = editor.getModel(); + const lineCount = model !== null ? model.getLineCount() : 0; + const movingMultipleLines = distinctSelectionsPerLine.length > 1; + + if (movingMultipleLines) { + editor.pushUndoStop(); + } + + for (const selection of distinctSelectionsPerLine) { + // If we are at the beginning/end of document, abort the command + if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === lineCount && this.down)) { + editor.pushUndoStop(); + return; + } + // Unfortunately passing a commands array doesn't work currently with multiple lines moved + // Remove this branching when it is fixed along with movingMultipleLines constant + if (movingMultipleLines) { editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); } else { commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); } } - if (movingMultipleLines) { - if (selectionsAfterLineMove.length > 1) { - editor.setSelections(selectionsAfterLineMove); - } - + if (!movingMultipleLines) { editor.pushUndoStop(); + editor.executeCommands(this.id, commands); // This will also set the selection } else { - editor.pushUndoStop(); - editor.executeCommands(this.id, commands); - editor.pushUndoStop(); + editor.setSelections(selectionsAfterLineMove); } + + editor.pushUndoStop(); } } diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index a04e5afdcb589..51bd48bcd6397 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -1659,7 +1659,7 @@ suite('Editor Contrib - Line Operations', () => { const moveLinesDownAction = new MoveLinesDownAction(); moveLinesDownAction.run(null!, editor); - let model = editor.getModel()!; + const model = editor.getModel()!; assert.deepStrictEqual(model.getLinesContent(), [ 'Line 3', @@ -1694,7 +1694,7 @@ suite('Editor Contrib - Line Operations', () => { const moveLinesUpAction = new MoveLinesUpAction(); moveLinesUpAction.run(null!, editor); - let model = editor.getModel()!; + const model = editor.getModel()!; assert.deepStrictEqual(model.getLinesContent(), [ 'Line 1', @@ -1729,7 +1729,7 @@ suite('Editor Contrib - Line Operations', () => { const moveLinesUpAction = new MoveLinesUpAction(); const moveLinesDownAction = new MoveLinesDownAction(); - let model = editor.getModel()!; + const model = editor.getModel()!; moveLinesUpAction.run(null!, editor); // no change since selections are already at top @@ -1768,5 +1768,77 @@ suite('Editor Contrib - Line Operations', () => { ].toString()); }); }); + + test('Move non ascending selections down', () => { + const TEXT = [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]; + withTestCodeEditor(TEXT, {}, (editor) => { + editor.setSelections([ + new Selection(4, 1, 4, 1), + new Selection(3, 1, 3, 1), + ]); + + const moveLinesDownAction = new MoveLinesDownAction(); + moveLinesDownAction.run(null!, editor); + + const model = editor.getModel()!; + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 1', + 'Line 2', + 'Line 5', + 'Line 3', + 'Line 4' + ]); + + assert.deepStrictEqual( + editor.getSelections()!.toString(), [ + new Selection(5, 1, 5, 1), + new Selection(4, 1, 4, 1), + ].toString()); + }); + }); + + test('Move multiple selections per line', () => { + const TEXT = [ + 'Line 1', + 'Line 2', + 'Line 3', + 'Line 4', + 'Line 5' + ]; + withTestCodeEditor(TEXT, {}, (editor) => { + editor.setSelections([ + new Selection(3, 1, 3, 1), + new Selection(4, 1, 4, 1), + new Selection(3, 2, 3, 2), + ]); + + const moveLinesDownAction = new MoveLinesDownAction(); + moveLinesDownAction.run(null!, editor); + + const model = editor.getModel()!; + + assert.deepStrictEqual(model.getLinesContent(), [ + 'Line 1', + 'Line 2', + 'Line 5', + 'Line 3', + 'Line 4' + ]); + + assert.deepStrictEqual( + editor.getSelections()!.toString(), [ + new Selection(4, 1, 4, 1), + new Selection(5, 1, 5, 1), + new Selection(4, 2, 4, 2), + ].toString()); + }); + }); }); }); From d85b593b8c0801a3f2db47c4315bb90535837150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Thu, 12 Jun 2025 16:33:38 +0200 Subject: [PATCH 10/13] fix: update suite name for moving multiple lines and refactor action execution --- .../test/browser/linesOperations.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 51bd48bcd6397..44fd76e127005 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -1641,7 +1641,7 @@ suite('Editor Contrib - Line Operations', () => { ); }); - suite('Issue #68694 Move consequitive lines with multiple selections', () => { + suite('Issue #68694 support to move multiple lines through alt+arrow', () => { test('Move lines down', () => { const TEXT = [ 'Line 1', @@ -1657,7 +1657,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - moveLinesDownAction.run(null!, editor); + executeAction(moveLinesDownAction, editor); const model = editor.getModel()!; @@ -1692,7 +1692,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesUpAction = new MoveLinesUpAction(); - moveLinesUpAction.run(null!, editor); + executeAction(moveLinesUpAction, editor); const model = editor.getModel()!; @@ -1731,7 +1731,7 @@ suite('Editor Contrib - Line Operations', () => { const model = editor.getModel()!; - moveLinesUpAction.run(null!, editor); // no change since selections are already at top + executeAction(moveLinesUpAction, editor); // no change since selections are already at top assert.deepStrictEqual(model.getLinesContent(), [ 'Line 1', @@ -1741,7 +1741,7 @@ suite('Editor Contrib - Line Operations', () => { 'Line 5' ]); - moveLinesDownAction.run(null!, editor); + executeAction(moveLinesDownAction, editor); assert.deepStrictEqual(model.getLinesContent(), [ 'Line 3', @@ -1751,7 +1751,7 @@ suite('Editor Contrib - Line Operations', () => { 'Line 5' ]); - moveLinesDownAction.run(null!, editor); + executeAction(moveLinesDownAction, editor); assert.deepStrictEqual(model.getLinesContent(), [ 'Line 3', @@ -1784,7 +1784,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - moveLinesDownAction.run(null!, editor); + executeAction(moveLinesDownAction, editor); const model = editor.getModel()!; @@ -1820,7 +1820,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - moveLinesDownAction.run(null!, editor); + executeAction(moveLinesDownAction, editor); const model = editor.getModel()!; From 62675c4f0707e861eece2981470f7f538234753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Fri, 13 Jun 2025 14:37:21 +0200 Subject: [PATCH 11/13] fix test execution --- .../test/browser/linesOperations.test.ts | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index 44fd76e127005..c58cf3fa86dc2 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -6,15 +6,16 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { CoreEditingCommands } from '../../../../browser/coreCommands.js'; import type { ICodeEditor } from '../../../../browser/editorBrowser.js'; -import { EditorAction } from '../../../../browser/editorExtensions.js'; +import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js'; import { Position } from '../../../../common/core/position.js'; import { Selection } from '../../../../common/core/selection.js'; import { Handler } from '../../../../common/editorCommon.js'; import { ITextModel } from '../../../../common/model.js'; import { ViewModel } from '../../../../common/viewModel/viewModelImpl.js'; import { CamelCaseAction, PascalCaseAction, DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction, MoveLinesUpAction, MoveLinesDownAction } from '../../browser/linesOperations.js'; -import { withTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; +import { createCodeEditorServices, withTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; import { createTextModel } from '../../../../test/common/testTextModel.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; function assertSelection(editor: ICodeEditor, expected: Selection | Selection[]): void { if (!Array.isArray(expected)) { @@ -1642,7 +1643,11 @@ suite('Editor Contrib - Line Operations', () => { }); suite('Issue #68694 support to move multiple lines through alt+arrow', () => { + test('Move lines down', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const TEXT = [ 'Line 1', 'Line 2', @@ -1657,7 +1662,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - executeAction(moveLinesDownAction, editor); + moveLinesDownAction.run(instantiationService, editor); const model = editor.getModel()!; @@ -1675,9 +1680,14 @@ suite('Editor Contrib - Line Operations', () => { new Selection(3, 1, 3, 1), ].toString()); }); + + disposables.dispose(); }); test('Move lines up', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const TEXT = [ 'Line 1', 'Line 2', @@ -1692,7 +1702,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesUpAction = new MoveLinesUpAction(); - executeAction(moveLinesUpAction, editor); + moveLinesUpAction.run(instantiationService, editor); const model = editor.getModel()!; @@ -1709,10 +1719,15 @@ suite('Editor Contrib - Line Operations', () => { new Selection(2, 1, 2, 1), new Selection(3, 1, 3, 1), ].toString()); + + disposables.dispose(); }); }); test('Combining up and down', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const TEXT = [ 'Line 1', 'Line 2', @@ -1731,7 +1746,7 @@ suite('Editor Contrib - Line Operations', () => { const model = editor.getModel()!; - executeAction(moveLinesUpAction, editor); // no change since selections are already at top + moveLinesUpAction.run(instantiationService, editor); // no change since selections are already at t, instantiationServiceop assert.deepStrictEqual(model.getLinesContent(), [ 'Line 1', @@ -1741,7 +1756,7 @@ suite('Editor Contrib - Line Operations', () => { 'Line 5' ]); - executeAction(moveLinesDownAction, editor); + moveLinesDownAction.run(instantiationService, editor); assert.deepStrictEqual(model.getLinesContent(), [ 'Line 3', @@ -1751,7 +1766,7 @@ suite('Editor Contrib - Line Operations', () => { 'Line 5' ]); - executeAction(moveLinesDownAction, editor); + moveLinesDownAction.run(instantiationService, editor); assert.deepStrictEqual(model.getLinesContent(), [ 'Line 3', @@ -1766,10 +1781,15 @@ suite('Editor Contrib - Line Operations', () => { new Selection(3, 1, 3, 1), new Selection(4, 1, 4, 1), ].toString()); + + disposables.dispose(); }); }); test('Move non ascending selections down', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const TEXT = [ 'Line 1', 'Line 2', @@ -1784,7 +1804,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - executeAction(moveLinesDownAction, editor); + moveLinesDownAction.run(instantiationService, editor); const model = editor.getModel()!; @@ -1801,10 +1821,15 @@ suite('Editor Contrib - Line Operations', () => { new Selection(5, 1, 5, 1), new Selection(4, 1, 4, 1), ].toString()); + + disposables.dispose(); }); }); test('Move multiple selections per line', () => { + const disposables = new DisposableStore(); + const instantiationService = createCodeEditorServices(disposables); + const TEXT = [ 'Line 1', 'Line 2', @@ -1820,7 +1845,7 @@ suite('Editor Contrib - Line Operations', () => { ]); const moveLinesDownAction = new MoveLinesDownAction(); - executeAction(moveLinesDownAction, editor); + moveLinesDownAction.run(instantiationService, editor); const model = editor.getModel()!; @@ -1838,6 +1863,8 @@ suite('Editor Contrib - Line Operations', () => { new Selection(5, 1, 5, 1), new Selection(4, 2, 4, 2), ].toString()); + + disposables.dispose(); }); }); }); From ed4182ed8e5db5794bc77d4b24ff73db90b57c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wel=C3=A9n?= Date: Fri, 13 Jun 2025 14:46:32 +0200 Subject: [PATCH 12/13] fix: remove unnecessary import and comment test suite meaning --- .../linesOperations/test/browser/linesOperations.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index c58cf3fa86dc2..f067349e82f5d 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -6,7 +6,7 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { CoreEditingCommands } from '../../../../browser/coreCommands.js'; import type { ICodeEditor } from '../../../../browser/editorBrowser.js'; -import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js'; +import { EditorAction } from '../../../../browser/editorExtensions.js'; import { Position } from '../../../../common/core/position.js'; import { Selection } from '../../../../common/core/selection.js'; import { Handler } from '../../../../common/editorCommon.js'; @@ -1642,6 +1642,7 @@ suite('Editor Contrib - Line Operations', () => { ); }); + // Multi cursor/selections line move actions suite('Issue #68694 support to move multiple lines through alt+arrow', () => { test('Move lines down', () => { @@ -1746,7 +1747,7 @@ suite('Editor Contrib - Line Operations', () => { const model = editor.getModel()!; - moveLinesUpAction.run(instantiationService, editor); // no change since selections are already at t, instantiationServiceop + moveLinesUpAction.run(instantiationService, editor); // no change since selections are already at the beginning of the file = no-op assert.deepStrictEqual(model.getLinesContent(), [ 'Line 1', From c33517407c56d32ff93dec73b124d4814348fcc2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 17 Jun 2025 12:54:36 +0200 Subject: [PATCH 13/13] polish --- .../browser/linesOperations.ts | 112 ++++++++---------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index ca32d715f8373..9235ff3b681b9 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -171,85 +171,73 @@ abstract class AbstractMoveLinesAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const languageConfigurationService = accessor.get(ILanguageConfigurationService); - const commands: ICommand[] = []; - const autoIndent = editor.getOption(EditorOption.autoIndent); - - const selections = editor.getSelections() || []; - // If there are multiple selections, there is some extra logic required. - // Selections can be made by both keyboard or clicking, so the order of selections is not guaranteed. - // It's important to process the selections in sequential line order - const distinctSelectionsPerLine: Selection[] = []; // These are the selections that will actually operated on + const model = editor.getModel(); + if (!model) { + return; + } - // Same as initial selections, but with adjusted line numbers after the move - // These will however not be applied if it's a no-op - const selectionsAfterLineMove: Selection[] = []; + const languageConfigurationService = accessor.get(ILanguageConfigurationService); - // Stash selections while processing, set new selections with +/- line-change only - // Selections should not be sorted before this step - for (const selection of selections) { - const startCol = selection.getStartPosition().column; - const startLine = selection.getStartPosition().lineNumber; - const newStartLine = this.down ? startLine + 1 : startLine - 1; + const selections = editor.getSelections() || []; + const autoIndent = editor.getOption(EditorOption.autoIndent); - const endCol = selection.getEndPosition().column; - const endLine = selection.getEndPosition().lineNumber; - const newEndLine = this.down ? endLine + 1 : endLine - 1; + selections.sort((a, b) => { + if (this.down) { + if (a.startLineNumber === b.startLineNumber) { + return b.endLineNumber - a.endLineNumber; + } else { + return b.startLineNumber - a.startLineNumber; + } + } else { + if (a.startLineNumber === b.startLineNumber) { + return a.endLineNumber - b.endLineNumber; + } else { + return a.startLineNumber - b.startLineNumber; + } + } + }); - selectionsAfterLineMove.push(new Selection(newStartLine, startCol, newEndLine, endCol)); - } + const nonIntersectingSelections: Selection[] = []; + const newSelections: Selection[] = []; + const lineCount = model.getLineCount(); - // Sort selections by line to guarantee correct processing order of the move line command - selections.sort((a, b) => a.endLineNumber - b.endLineNumber); - let lastLineNumber = 0; + let lastStartLineNumber = lineCount; + let lastEndLineNumber = 0; for (const selection of selections) { - // Work only with one selection per line, otherwise we risk to initiate multiple moves on a line - if (lastLineNumber !== selection.endLineNumber) { - distinctSelectionsPerLine.push(selection); - lastLineNumber = selection.endLineNumber; + if (this.down && selection.endLineNumber >= lastStartLineNumber || this.down && selection.endLineNumber === lineCount) { + continue; + } + if (!this.down && selection.startLineNumber <= lastEndLineNumber || !this.down && selection.startLineNumber === 1) { + continue; } - } - if (this.down) { - // Selections are now sorted in ascending order - // Processing needs to be done in reversed order if we are moving lines down - // I.e. starting with the last selection in the bottom - otherwise we move the same line multiple times - distinctSelectionsPerLine.reverse(); - } + nonIntersectingSelections.push(selection); - const model = editor.getModel(); - const lineCount = model !== null ? model.getLineCount() : 0; - const movingMultipleLines = distinctSelectionsPerLine.length > 1; + const startPosition = selection.getStartPosition(); + const startCol = startPosition.column; + const startLine = startPosition.lineNumber; + const newStartLine = this.down ? startLine + 1 : startLine - 1; - if (movingMultipleLines) { - editor.pushUndoStop(); - } + const endPosition = selection.getEndPosition(); + const endCol = endPosition.column; + const endLine = endPosition.lineNumber; + const newEndLine = this.down ? endLine + 1 : endLine - 1; - for (const selection of distinctSelectionsPerLine) { - // If we are at the beginning/end of document, abort the command - if ((selection.startLineNumber === 1 && !this.down) || (selection.endLineNumber === lineCount && this.down)) { - editor.pushUndoStop(); - return; - } + newSelections.push(new Selection(newStartLine, startCol, newEndLine, endCol)); - // Unfortunately passing a commands array doesn't work currently with multiple lines moved - // Remove this branching when it is fixed along with movingMultipleLines constant - if (movingMultipleLines) { - editor.executeCommand(this.id, new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); - } else { - commands.push(new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)); - } + lastEndLineNumber = selection.endLineNumber; + lastStartLineNumber = selection.startLineNumber; } - if (!movingMultipleLines) { - editor.pushUndoStop(); - editor.executeCommands(this.id, commands); // This will also set the selection - } else { - editor.setSelections(selectionsAfterLineMove); + editor.pushUndoStop(); + for (const selection of nonIntersectingSelections) { + editor.executeCommands(this.id, [new MoveLinesCommand(selection, this.down, autoIndent, languageConfigurationService)]); + } + if (newSelections) { + editor.setSelections(newSelections); } - editor.pushUndoStop(); } }