From 5fd59f701e52c6ba341ddc2252f4d4f7139d1b92 Mon Sep 17 00:00:00 2001 From: CVVPK Date: Thu, 20 Feb 2020 15:19:43 -0500 Subject: [PATCH 1/4] Add backspace support for multicursor selections --- src/actions/commands/insert.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/commands/insert.ts b/src/actions/commands/insert.ts index 8b51b3ea481..653f3769dde 100644 --- a/src/actions/commands/insert.ts +++ b/src/actions/commands/insert.ts @@ -244,9 +244,9 @@ export class CommandBackspaceInInsertMode extends BaseCommand { public async exec(position: Position, vimState: VimState): Promise { const line = TextEditor.getLineAt(position).text; - const selection = TextEditor.getSelection(); + const selection = vimState.editor.selections.find(s => s.contains(position)); - if (!selection.isEmpty) { + if (selection && !selection.isEmpty) { // If a selection is active, delete it vimState.recordedState.transformations.push({ type: 'deleteRange', From 59496be313f16188ed8c54c6f77f9ac84a76d06b Mon Sep 17 00:00:00 2001 From: CVVPK Date: Thu, 20 Feb 2020 16:17:49 -0500 Subject: [PATCH 2/4] Let VSCode handle multicursor text insertion --- src/actions/commands/insert.ts | 17 +++++--------- src/mode/modeHandler.ts | 31 +++++++++++++++++++++++++- src/transformations/transformations.ts | 21 +++++++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/actions/commands/insert.ts b/src/actions/commands/insert.ts index 653f3769dde..bbaf628967f 100644 --- a/src/actions/commands/insert.ts +++ b/src/actions/commands/insert.ts @@ -293,18 +293,11 @@ export class CommandInsertInInsertMode extends BaseCommand { public async exec(position: Position, vimState: VimState): Promise { const char = this.keysPressed[this.keysPressed.length - 1]; - if (vimState.isMultiCursor) { - vimState.recordedState.transformations.push({ - type: 'insertText', - text: char, - position: vimState.cursorStopPosition, - }); - } else { - vimState.recordedState.transformations.push({ - type: 'insertTextVSCode', - text: char, - }); - } + vimState.recordedState.transformations.push({ + type: 'insertTextVSCode', + text: char, + isMultiCursor: vimState.isMultiCursor, + }); return vimState; } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index def3699ad56..b54a9a7e902 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -30,6 +30,9 @@ import { areAnyTransformationsOverlapping, isTextTransformation, TextTransformations, + areAllSameTransformation, + isMultiCursorTextTransformation, + InsertTextVSCodeTransformation, } from './../transformations/transformations'; import { globalState } from '../state/globalState'; import { reportSearch } from '../util/statusBarTextUtils'; @@ -834,7 +837,13 @@ export class ModeHandler implements vscode.Disposable { const textTransformations: TextTransformations[] = transformations.filter(x => isTextTransformation(x) ) as any; - const otherTransformations = transformations.filter(x => !isTextTransformation(x)); + const multicursorTextTransformations: InsertTextVSCodeTransformation[] = transformations.filter( + x => isMultiCursorTextTransformation(x) + ) as any; + + const otherTransformations = transformations.filter( + x => !isTextTransformation(x) && !isMultiCursorTextTransformation(x) + ); let accumulatedPositionDifferences: { [key: number]: PositionDiff[] } = {}; @@ -907,6 +916,26 @@ export class ModeHandler implements vscode.Disposable { } } + if (multicursorTextTransformations.length > 0) { + if (areAllSameTransformation(multicursorTextTransformations)) { + /** + * Apply the transformation only once instead of to each cursor + * if they are all the same. + * + * This lets VSCode do multicursor snippets, auto braces and + * all the usual jazz VSCode does on text insertion. + */ + const { text } = multicursorTextTransformations[0]; + + // await vscode.commands.executeCommand('default:type', { text }); + await TextEditor.insert(text); + } else { + this._logger.warn( + `Unhandled multicursor transformations. Not all transformations are the same!` + ); + } + } + for (const command of otherTransformations) { switch (command.type) { case 'insertTextVSCode': diff --git a/src/transformations/transformations.ts b/src/transformations/transformations.ts index 8241174c880..524b073fe8b 100644 --- a/src/transformations/transformations.ts +++ b/src/transformations/transformations.ts @@ -99,6 +99,11 @@ export interface InsertTextVSCodeTransformation { */ text: string; + /** + * Whether this transformation was created in multicursor mode. + */ + isMultiCursor?: boolean; + /** * The index of the cursor that this transformation applies to. */ @@ -291,6 +296,9 @@ export const isTextTransformation = (x: Transformation): x is TextTransformation x.type === 'moveCursor' ); }; +export const isMultiCursorTextTransformation = (x: Transformation): Boolean => { + return (x.type === 'insertTextVSCode' && x.isMultiCursor) ?? false; +}; const getRangeFromTextTransformation = (transformation: TextTransformations): Range | undefined => { switch (transformation.type) { @@ -330,3 +338,16 @@ export const areAnyTransformationsOverlapping = (transformations: TextTransforma return false; }; + +export const areAllSameTransformation = (transformations: Transformation[]): Boolean => { + const firstTransformation = transformations[0]; + + return transformations.every(t => { + for (const [key, value] of Object.entries(firstTransformation)) { + if (t[key] !== value) { + return false; + } + } + return true; + }); +}; From a9167e5b644e043bd5bb7c5749aacd0367ee760a Mon Sep 17 00:00:00 2001 From: CVVPK Date: Thu, 26 Mar 2020 13:45:17 -0400 Subject: [PATCH 3/4] Use every() in favour of for loop --- src/transformations/transformations.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/transformations/transformations.ts b/src/transformations/transformations.ts index 524b073fe8b..dce3f31c469 100644 --- a/src/transformations/transformations.ts +++ b/src/transformations/transformations.ts @@ -343,11 +343,8 @@ export const areAllSameTransformation = (transformations: Transformation[]): Boo const firstTransformation = transformations[0]; return transformations.every(t => { - for (const [key, value] of Object.entries(firstTransformation)) { - if (t[key] !== value) { - return false; - } - } - return true; + return Object.entries(t).every(([key, value]) => { + return firstTransformation[key] === value; + }); }); }; From 84ec4b4ff9d802871a78ac7e50b3b0087192099b Mon Sep 17 00:00:00 2001 From: Jason Fields Date: Fri, 27 Mar 2020 19:03:53 -0400 Subject: [PATCH 4/4] gulp forceprettier --- src/actions/commands/insert.ts | 2 +- src/mode/modeHandler.ts | 11 ++++------- src/transformations/transformations.ts | 2 +- src/util/util.ts | 15 --------------- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/actions/commands/insert.ts b/src/actions/commands/insert.ts index e4dacc3a202..4a81f561d6f 100644 --- a/src/actions/commands/insert.ts +++ b/src/actions/commands/insert.ts @@ -237,7 +237,7 @@ export class CommandBackspaceInInsertMode extends BaseCommand { public async exec(position: Position, vimState: VimState): Promise { const line = TextEditor.getLineAt(position).text; - const selection = vimState.editor.selections.find(s => s.contains(position)); + const selection = vimState.editor.selections.find((s) => s.contains(position)); if (selection && !selection.isEmpty) { // If a selection is active, delete it diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 43594d6b554..65177946405 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -27,7 +27,7 @@ import { VsCodeContext } from '../util/vscode-context'; import { commandLine } from '../cmd_line/commandLine'; import { configuration } from '../configuration/configuration'; import { decoration } from '../configuration/decoration'; -import { getCursorsAfterSync, scrollView, partition } from '../util/util'; +import { getCursorsAfterSync, scrollView } from '../util/util'; import { BaseCommand, CommandQuitRecordMacro, @@ -816,19 +816,17 @@ export class ModeHandler implements vscode.Disposable { return vimState; } - - const textTransformations: TextTransformations[] = transformations.filter(x => + const textTransformations: TextTransformations[] = transformations.filter((x) => isTextTransformation(x) ) as any; const multicursorTextTransformations: InsertTextVSCodeTransformation[] = transformations.filter( - x => isMultiCursorTextTransformation(x) + (x) => isMultiCursorTextTransformation(x) ) as any; const otherTransformations = transformations.filter( - x => !isTextTransformation(x) && !isMultiCursorTextTransformation(x) + (x) => !isTextTransformation(x) && !isMultiCursorTextTransformation(x) ); - let accumulatedPositionDifferences: { [key: number]: PositionDiff[] } = {}; const doTextEditorEdit = (command: TextTransformations, edit: vscode.TextEditorEdit) => { @@ -922,7 +920,6 @@ export class ModeHandler implements vscode.Disposable { for (const transformation of otherTransformations) { switch (transformation.type) { - case 'insertTextVSCode': await TextEditor.insert(transformation.text); vimState.cursors[0] = Range.FromVSCodeSelection(this.vimState.editor.selection); diff --git a/src/transformations/transformations.ts b/src/transformations/transformations.ts index dce3f31c469..e053ff05374 100644 --- a/src/transformations/transformations.ts +++ b/src/transformations/transformations.ts @@ -342,7 +342,7 @@ export const areAnyTransformationsOverlapping = (transformations: TextTransforma export const areAllSameTransformation = (transformations: Transformation[]): Boolean => { const firstTransformation = transformations[0]; - return transformations.every(t => { + return transformations.every((t) => { return Object.entries(t).every(([key, value]) => { return firstTransformation[key] === value; }); diff --git a/src/util/util.ts b/src/util/util.ts index 08efead0d05..8325b06bab4 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -53,18 +53,3 @@ export function scrollView(vimState: VimState, offset: number) { }); } } - -/** - * Partitions an array into two according to a given predicate - * @param array Objects to partition - * @param predicate Function according to which objects will be partitioned - * @returns Array which contains all elements of `array` for which `predicate` is true, and one which contains the rest - */ -export function partition(array: T[], predicate: (x: T) => boolean): [T[], T[]] { - return array.reduce( - ([pass, fail], elem) => { - return predicate(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]]; - }, - [[], []] - ); -}