diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 8488bece790..de1a5ff8e53 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -1233,8 +1233,7 @@ export class DeleteOperator extends BaseOperator { Register.put(text, vimState); } - await TextEditor.delete(new vscode.Range(start, end)); - + let diff = new PositionDiff(0, 0); let resultingPosition: Position; if (currentMode === ModeName.Visual) { @@ -1243,23 +1242,34 @@ export class DeleteOperator extends BaseOperator { if (start.character > TextEditor.getLineAt(start).text.length) { resultingPosition = start.getLeft(); + diff = new PositionDiff(0, -1); } else { resultingPosition = start; } if (registerMode === RegisterMode.LineWise) { resultingPosition = resultingPosition.getLineBegin(); + diff = PositionDiff.NewBOLDiff(); } + vimState.recordedState.transformations.push({ + type : "deleteRange", + range : new Range(start, end), + diff : diff, + }); + return resultingPosition; } public async run(vimState: VimState, start: Position, end: Position, yank = true): Promise { - const result = await this.delete(start, end, vimState.currentMode, vimState.effectiveRegisterMode(), vimState, yank); + await this.delete(start, end, vimState.currentMode, vimState.effectiveRegisterMode(), vimState, yank); vimState.currentMode = ModeName.Normal; - vimState.cursorPosition = result; - vimState.cursorStartPosition = result; + + /* + vimState.cursorPosition = result; + vimState.cursorStartPosition = result; + */ return vimState; } @@ -1505,8 +1515,13 @@ export class PutCommand extends BaseCommand { text = register.text as string; } + let textToAdd: string; + let whereToAddText: Position; + let diff = new PositionDiff(0, 0); + if (register.registerMode === RegisterMode.CharacterWise) { - await TextEditor.insertAt(text, dest); + textToAdd = text; + whereToAddText = dest; } else { if (adjustIndent) { // Adjust indent to current line @@ -1522,9 +1537,11 @@ export class PutCommand extends BaseCommand { } if (after) { - await TextEditor.insertAt(text + "\n", dest.getLineBegin()); + textToAdd = text + "\n"; + whereToAddText = dest.getLineBegin(); } else { - await TextEditor.insertAt("\n" + text, dest.getLineEnd()); + textToAdd = "\n" + text; + whereToAddText = dest.getLineEnd(); } } @@ -1532,15 +1549,22 @@ export class PutCommand extends BaseCommand { // stays in the same place. Otherwise, it moves to the end of what you pasted. if (register.registerMode === RegisterMode.LineWise) { - vimState.cursorPosition = new Position(dest.line + 1, 0); + diff = new PositionDiff(1, 0); } else { + /* if (text.indexOf("\n") === -1) { - vimState.cursorPosition = new Position(dest.line, Math.max(dest.character + text.length - 1, 0)); - } else { - vimState.cursorPosition = dest; + diff = new PositionDiff(0, text.length); } + */ } + vimState.recordedState.transformations.push({ + type : "insertText", + text : textToAdd, + position: whereToAddText, + diff : diff, + }); + vimState.currentRegisterMode = register.registerMode; return vimState; } @@ -1578,13 +1602,7 @@ export class PutCommand extends BaseCommand { } public async execCount(position: Position, vimState: VimState): Promise { - const result = await super.execCount(position, vimState); - - if (vimState.effectiveRegisterMode() === RegisterMode.LineWise) { - result.cursorPosition = new Position(position.line + 1, 0).getFirstLineNonBlankChar(); - } - - return result; + return await super.execCount(position, vimState); } } @@ -1664,7 +1682,10 @@ export class PutCommandVisual extends BaseCommand { public async exec(position: Position, vimState: VimState, after: boolean = false): Promise { const result = await new DeleteOperator().run(vimState, vimState.cursorStartPosition, vimState.cursorPosition, false); - return await new PutCommand().exec(result.cursorPosition, result, true); + // Our two transformations are overlapping, and VSCode has problems with that. + vimState.recordedState.dontParallelizeTransformations = true; + + return await new PutCommand().exec(vimState.cursorStartPosition, result, true); } // TODO - execWithCount diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 5adaa22d499..9c4d282de60 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -256,6 +256,12 @@ export class RecordedState { */ public transformations: Transformation[] = []; + /** + * This turns off transformations running in parallel. It's necessary when + * transformations overlap. + */ + public dontParallelizeTransformations = false; + /** * The operator (e.g. d, y, >) the user wants to run, if there is one. */ @@ -299,7 +305,7 @@ export class RecordedState { res.actionKeys = this.actionKeys.slice(0); res.actionsRun = this.actionsRun.slice(0); - res.hasRunOperator = this.hasRunOperator; + res.hasRunOperator = this.hasRunOperator; return res; } @@ -916,20 +922,24 @@ export class ModeHandler implements vscode.Disposable { )); } - // Keep track of all cursors (in the case of multi-cursor). + if (vimState.recordedState.transformations.length > 0) { + await this.executeCommand(vimState); + } else { + // Keep track of all cursors (in the case of multi-cursor). - resultVimState.allCursors = resultingCursors; + resultVimState.allCursors = resultingCursors; - const selections: vscode.Selection[] = []; + const selections: vscode.Selection[] = []; - for (const cursor of vimState.allCursors) { - selections.push(new vscode.Selection( - cursor.start, - cursor.stop, - )); - } + for (const cursor of vimState.allCursors) { + selections.push(new vscode.Selection( + cursor.start, + cursor.stop, + )); + } - vscode.window.activeTextEditor.selections = selections; + vscode.window.activeTextEditor.selections = selections; + } return resultVimState; } @@ -948,34 +958,68 @@ export class ModeHandler implements vscode.Disposable { let accumulatedPositionDifferences: PositionDiff[] = []; - // batch all text operations together as a single operation - // (this is primarily necessary for multi-cursor mode, since most - // actions will trigger at most one text operation). - await vscode.window.activeTextEditor.edit(edit => { + if (vimState.recordedState.dontParallelizeTransformations) { + // This is the rare case. + for (const command of textTransformations) { - switch (command.type) { - case "insertText": - edit.insert(command.position, command.text); - break; - - case "replaceText": - edit.replace(new vscode.Selection(command.end, command.start), command.text); - break; - - case "deleteText": - edit.delete(new vscode.Range(command.position, command.position.getLeftThroughLineBreaks())); - break; - - case "deleteRange": - edit.delete(new vscode.Selection(command.range.start, command.range.stop)); - break; - } + await vscode.window.activeTextEditor.edit(edit => { + switch (command.type) { + case "insertText": + edit.insert(command.position, command.text); + break; + + case "replaceText": + edit.replace(new vscode.Selection(command.end, command.start), command.text); + break; + + case "deleteText": + edit.delete(new vscode.Range(command.position, command.position.getLeftThroughLineBreaks())); + break; + + case "deleteRange": + edit.delete(new vscode.Selection(command.range.start, command.range.stop)); + break; + } + + if (command.diff) { + accumulatedPositionDifferences.push(command.diff); + } + }); + }; + } else { + // This is the common case! - if (command.diff) { - accumulatedPositionDifferences.push(command.diff); + /** + * batch all text operations together as a single operation + * (this is primarily necessary for multi-cursor mode, since most + * actions will trigger at most one text operation). + */ + await vscode.window.activeTextEditor.edit(edit => { + for (const command of textTransformations) { + switch (command.type) { + case "insertText": + edit.insert(command.position, command.text); + break; + + case "replaceText": + edit.replace(new vscode.Selection(command.end, command.start), command.text); + break; + + case "deleteText": + edit.delete(new vscode.Range(command.position, command.position.getLeftThroughLineBreaks())); + break; + + case "deleteRange": + edit.delete(new vscode.Selection(command.range.start, command.range.stop)); + break; + } + + if (command.diff) { + accumulatedPositionDifferences.push(command.diff); + } } - } - }); + }); + } for (const command of otherTransformations) { switch (command.type) { diff --git a/src/motion/position.ts b/src/motion/position.ts index e99148887f7..27fa381f083 100644 --- a/src/motion/position.ts +++ b/src/motion/position.ts @@ -9,23 +9,92 @@ import { Configuration } from "./../configuration/configuration"; /** * Represents a difference between two positions. Add it to a position - * to get another position. + * to get another position. Create it with the factory methods: + * + * - NewDiff + * - NewBOLDiff */ export class PositionDiff { - public line: number; - public character: number; + private _line: number; + private _character: number; + private _isBOLDiff: boolean; constructor(line: number, character: number) { - this.line = line; - this.character = character; + this._line = line; + this._character = character; } - public add(other: PositionDiff) { + /** + * Creates a new PositionDiff that always brings the cursor to the beginning of the line + * when applied to a position. + */ + public static NewBOLDiff(): PositionDiff { + const result = new PositionDiff(0, 0); + + result._isBOLDiff = true; + return result; + } + + /** + * Add this PositionDiff to another PositionDiff. + */ + public addDiff(other: PositionDiff) { + if (this._isBOLDiff || other._isBOLDiff) { + throw new Error("johnfn hasn't done this case yet and doesnt want to"); + } + return new PositionDiff( - this.line + other.line, - this.character + other.character + this._line + other._line, + this._character + other._character + ); + } + + /** + * Adds a Position to this PositionDiff, returning a new PositionDiff. + */ + public addPosition(other: Position, { boundsCheck = true } = { } ): Position { + let resultChar = this.isBOLDiff() ? 0 : this.character + other.character; + let resultLine = this.line + other.line; + + if (boundsCheck) { + if (resultChar < 0) { resultChar = 0; } + if (resultLine < 0) { resultLine = 0; } + } + + return new Position( + resultLine, + resultChar ); } + + /** + * Difference in lines. + */ + public get line(): number { + return this._line; + } + + /** + * Difference in characters. + */ + public get character(): number { + return this._character; + } + + /** + * Does this diff move the position to the beginning of the line? + */ + public isBOLDiff(): boolean { + return this._isBOLDiff; + } + + public toString(): string { + if (this._isBOLDiff) { + return `[ Diff: BOL ]`; + } + + return `[ Diff: ${ this._line } ${ this._character } ]`; + } } export class Position extends vscode.Position { @@ -208,6 +277,10 @@ export class Position extends vscode.Position { if (resultLine < 0) { resultLine = 0; } } + if (other.isBOLDiff()) { + resultChar = 0; + } + return new Position( resultLine, resultChar