From 4f37dc12735991c8f60dc813de2baab90feb7881 Mon Sep 17 00:00:00 2001 From: rebornix Date: Wed, 7 Dec 2016 13:36:06 -0800 Subject: [PATCH] fix #16587. Enrich Join lines with multi cursor support --- .../linesOperations/common/linesOperations.ts | 165 +++++++++++------- .../test/common/linesOperations.test.ts | 46 ++++- 2 files changed, 148 insertions(+), 63 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts index f097915357a6d..e7ce60d2b99b0 100644 --- a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts @@ -10,7 +10,7 @@ import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/common/sortL import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import { EditorContextKeys, Handler, ICommand, ICommonCodeEditor, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon'; -import { ReplaceCommand, ReplaceCommandThatPreservesSelection, ReplaceCommandWithOffsetCursorState } from 'vs/editor/common/commands/replaceCommand'; +import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { editorAction, ServicesAccessor, IActionOptions, EditorAction, HandlerEditorAction } from 'vs/editor/common/editorCommonExtensions'; @@ -420,86 +420,127 @@ export class JoinLinesAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { - let selection = editor.getSelection(); + let selections = editor.getSelections(); + let primarySelection = editor.getSelection(); + + selections.sort(Range.compareRangesUsingStarts); + let reducedSelections: Selection[] = []; + + let lastSelection = selections.reduce((previousValue, currentValue) => { + if (previousValue.isEmpty()) { + if (currentValue.startLineNumber > previousValue.endLineNumber + 1) { + reducedSelections.push(previousValue); + return currentValue; + } else { + return new Selection(previousValue.startLineNumber, previousValue.startColumn, currentValue.endLineNumber, currentValue.endColumn); + } + } else { + if (currentValue.startLineNumber > previousValue.endLineNumber) { + reducedSelections.push(previousValue); + return currentValue; + } else { + return new Selection(previousValue.startLineNumber, previousValue.startColumn, currentValue.endLineNumber, currentValue.endColumn); + } + } + }); + + reducedSelections.push(lastSelection); + let model = editor.getModel(); - let startLineNumber = selection.startLineNumber; - let startColumn = 1; - let endLineNumber: number, - endColumn: number, - columnDeltaOffset: number; - - let selectionEndPositionOffset = model.getLineContent(selection.endLineNumber).length - selection.endColumn; - - if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) { - let position = selection.getStartPosition(); - if (position.lineNumber < model.getLineCount()) { - endLineNumber = startLineNumber + 1; - endColumn = model.getLineMaxColumn(endLineNumber); + let edits = []; + let resultSelections = []; + let resultPrimarySelection = primarySelection; + let lineOffset = 0; + + for (let i = 0, len = reducedSelections.length; i < len; i++) { + let selection = reducedSelections[i]; + let startLineNumber = selection.startLineNumber; + let startColumn = 1; + let endLineNumber: number, + endColumn: number, + columnDeltaOffset: number; + + let selectionEndPositionOffset = model.getLineContent(selection.endLineNumber).length - selection.endColumn; + + if (selection.isEmpty() || selection.startLineNumber === selection.endLineNumber) { + let position = selection.getStartPosition(); + if (position.lineNumber < model.getLineCount()) { + endLineNumber = startLineNumber + 1; + endColumn = model.getLineMaxColumn(endLineNumber); + } else { + endLineNumber = position.lineNumber; + endColumn = model.getLineMaxColumn(position.lineNumber); + } } else { - endLineNumber = position.lineNumber; - endColumn = model.getLineMaxColumn(position.lineNumber); + endLineNumber = selection.endLineNumber; + endColumn = model.getLineMaxColumn(endLineNumber); } - } else { - endLineNumber = selection.endLineNumber; - endColumn = model.getLineMaxColumn(endLineNumber); - } - let trimmedLinesContent = model.getLineContent(startLineNumber); + let trimmedLinesContent = model.getLineContent(startLineNumber); - for (let i = startLineNumber + 1; i <= endLineNumber; i++) { - let lineText = model.getLineContent(i); - let firstNonWhitespaceIdx = model.getLineFirstNonWhitespaceColumn(i); + for (let i = startLineNumber + 1; i <= endLineNumber; i++) { + let lineText = model.getLineContent(i); + let firstNonWhitespaceIdx = model.getLineFirstNonWhitespaceColumn(i); - if (firstNonWhitespaceIdx >= 1) { - let insertSpace = true; - if (trimmedLinesContent === '') { - insertSpace = false; - } + if (firstNonWhitespaceIdx >= 1) { + let insertSpace = true; + if (trimmedLinesContent === '') { + insertSpace = false; + } - if (insertSpace && (trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === ' ' || - trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === '\t')) { - insertSpace = false; - trimmedLinesContent = trimmedLinesContent.replace(/[\s\uFEFF\xA0]+$/g, ' '); - } + if (insertSpace && (trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === ' ' || + trimmedLinesContent.charAt(trimmedLinesContent.length - 1) === '\t')) { + insertSpace = false; + trimmedLinesContent = trimmedLinesContent.replace(/[\s\uFEFF\xA0]+$/g, ' '); + } - let lineTextWithoutIndent = lineText.substr(firstNonWhitespaceIdx - 1); + let lineTextWithoutIndent = lineText.substr(firstNonWhitespaceIdx - 1); - trimmedLinesContent += (insertSpace ? ' ' : '') + lineTextWithoutIndent; + trimmedLinesContent += (insertSpace ? ' ' : '') + lineTextWithoutIndent; - if (insertSpace) { - columnDeltaOffset = lineTextWithoutIndent.length + 1; + if (insertSpace) { + columnDeltaOffset = lineTextWithoutIndent.length + 1; + } else { + columnDeltaOffset = lineTextWithoutIndent.length; + } } else { - columnDeltaOffset = lineTextWithoutIndent.length; + columnDeltaOffset = 0; } - } else { - columnDeltaOffset = 0; } - } - let deleteSelection = new Range( - startLineNumber, - startColumn, - endLineNumber, - endColumn - ); + let deleteSelection = new Range(startLineNumber, startColumn, endLineNumber, endColumn); - if (!deleteSelection.isEmpty()) { - if (selection.isEmpty()) { - editor.executeCommand(this.id, - new ReplaceCommandWithOffsetCursorState(deleteSelection, trimmedLinesContent, 0, -columnDeltaOffset) - ); - } else { - if (selection.startLineNumber === selection.endLineNumber) { - editor.executeCommand(this.id, - new ReplaceCommandThatPreservesSelection(deleteSelection, trimmedLinesContent, selection) - ); + if (!deleteSelection.isEmpty()) { + let resultSelection: Selection; + + if (selection.isEmpty()) { + edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent)); + resultSelection = new Selection(deleteSelection.startLineNumber - lineOffset, trimmedLinesContent.length - columnDeltaOffset + 1, startLineNumber - lineOffset, trimmedLinesContent.length - columnDeltaOffset + 1); + } else { + if (selection.startLineNumber === selection.endLineNumber) { + edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent)); + resultSelection = new Selection(selection.startLineNumber - lineOffset, selection.startColumn, + selection.endLineNumber - lineOffset, selection.endColumn); + } else { + edits.push(EditOperation.replace(deleteSelection, trimmedLinesContent)); + resultSelection = new Selection(selection.startLineNumber - lineOffset, selection.startColumn, + selection.startLineNumber - lineOffset, trimmedLinesContent.length - selectionEndPositionOffset); + } + } + + if (Range.intersectRanges(deleteSelection, primarySelection) !== null) { + resultPrimarySelection = resultSelection; } else { - editor.executeCommand(this.id, new ReplaceCommand(deleteSelection, trimmedLinesContent)); - editor.setSelection(new Selection(selection.startLineNumber, selection.startColumn, - selection.startLineNumber, trimmedLinesContent.length - selectionEndPositionOffset)); + resultSelections.push(resultSelection); } } + + lineOffset += deleteSelection.endLineNumber - deleteSelection.startLineNumber; } + + editor.executeEdits(this.id, edits); + resultSelections.unshift(resultPrimarySelection); + editor.setSelections(resultSelections); } } diff --git a/src/vs/editor/contrib/linesOperations/test/common/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/common/linesOperations.test.ts index a5a4649581f99..0ef8958cb3394 100644 --- a/src/vs/editor/contrib/linesOperations/test/common/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/common/linesOperations.test.ts @@ -68,7 +68,7 @@ suite('Editor Contrib - Line Operations', () => { }); }); - test('Join lines', function () { + test('join lines', function () { withMockCodeEditor( [ 'hello', @@ -113,6 +113,50 @@ suite('Editor Contrib - Line Operations', () => { }); }); + test('join lines in multi cursor mode', function () { + withMockCodeEditor( + [ + 'hello', + 'world', + 'hello ', + 'world', + 'hello ', + ' world', + 'hello ', + ' world', + '', + '', + 'hello world' + ], {}, (editor, cursor) => { + let model = editor.getModel(); + let joinLinesAction = new JoinLinesAction(); + + editor.setSelections([ + /** primary cursor */ + new Selection(5, 2, 5, 2), + new Selection(1, 2, 1, 2), + new Selection(3, 2, 4, 2), + new Selection(5, 4, 6, 3), + new Selection(7, 5, 8, 4), + new Selection(10, 1, 10, 1) + ]); + + joinLinesAction.run(null, editor); + assert.equal(model.getLinesContent().join('\n'), 'hello world\nhello world\nhello world\nhello world\n\nhello world', '001'); + assert.deepEqual(editor.getSelections().toString(), [ + /** primary cursor */ + new Selection(3, 2, 3, 8), + new Selection(1, 6, 1, 6), + new Selection(2, 2, 2, 8), + new Selection(4, 5, 4, 9), + new Selection(6, 1, 6, 1) + ].toString(), '002'); + + /** primary cursor */ + assert.deepEqual(editor.getSelection().toString(), new Selection(3, 2, 3, 8).toString(), '003'); + }); + }); + test('transpose', function () { withMockCodeEditor( [