diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 88c2f0ac256..68a5cc9b1f7 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -43,6 +43,22 @@ const compareKeypressSequence = function (one: string[], two: string[]): boolean return true; }; +/** + * The result of a (more sophisticated) Movement. + */ +export interface IMovement { + start : Position; + stop : Position; + + // It /so/ annoys me that I have to put this here. + registerMode?: RegisterMode; +} + +export function isIMovement(o: IMovement | Position): o is IMovement { + return (o as IMovement).start !== undefined && + (o as IMovement).stop !== undefined; +} + export class BaseAction { /** * Modes that this action can be run in. @@ -102,15 +118,16 @@ export abstract class BaseMovement extends BaseAction { /** * Run the movement. + * + * Generally returns a new Position. If necessary, it can return an IMovement instead. */ - public abstract async execAction(position: Position, vimState: VimState): Promise; + public abstract async execAction(position: Position, vimState: VimState): Promise; /** - * Run the action in an operator context. 99% of the time, this function can be - * ignored, as it is exactly the same as the above function. (But pay attention - * to e!) + * Run the movement in an operator context. 99% of the time, this function can be + * ignored, as it is exactly the same as the above function. */ - public async execActionForOperator(position: Position, vimState: VimState): Promise { + public async execActionForOperator(position: Position, vimState: VimState): Promise { return await this.execAction(position, vimState); } } @@ -192,6 +209,17 @@ export function RegisterAction(action) { +@RegisterAction +class CommandInsertAtCursor extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["i"]; + + public async exec(position: Position, vimState: VimState): Promise { + vimState.currentMode = ModeName.Insert; + + return vimState; + } +} @RegisterAction class CommandInsertInSearchMode extends BaseCommand { @@ -289,9 +317,9 @@ class CommandNextSearchMatch extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["n"]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.searchString === "") { - return vimState; + return position; } let nextPosition: Position; @@ -307,11 +335,10 @@ class CommandNextSearchMatch extends BaseMovement { if (!nextPosition) { // TODO(bell) - return vimState; + return position; } - vimState.cursorPosition = nextPosition; - return vimState; + return nextPosition; } } @@ -321,9 +348,9 @@ class CommandPreviousSearchMatch extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["N"]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.searchString === "") { - return vimState; + return position; } let prevPosition: Position; @@ -339,11 +366,11 @@ class CommandPreviousSearchMatch extends BaseMovement { if (!prevPosition) { // TODO(bell) - return vimState; + return position; } vimState.cursorPosition = prevPosition; - return vimState; + return position; } } @@ -840,18 +867,6 @@ class CommandOpenSquareBracket extends BaseCommand { // begin insert commands -@RegisterAction -class CommandInsertAtCursor extends BaseCommand { - modes = [ModeName.Normal]; - keys = ["i"]; - - public async exec(position: Position, vimState: VimState): Promise { - vimState.currentMode = ModeName.Insert; - - return vimState; - } -} - @RegisterAction class CommandInsertAtLineBegin extends BaseCommand { modes = [ModeName.Normal]; @@ -930,10 +945,8 @@ class MoveLeft extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["h"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getLeft(); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getLeft(); } } @@ -943,10 +956,8 @@ class MoveUp extends BaseMovement { keys = ["k"]; doesntChangeDesiredColumn = true; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getUp(vimState.desiredColumn); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getUp(vimState.desiredColumn); } } @@ -956,10 +967,8 @@ class MoveDown extends BaseMovement { keys = ["j"]; doesntChangeDesiredColumn = true; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getDown(vimState.desiredColumn); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getDown(vimState.desiredColumn); } } @@ -968,10 +977,8 @@ class MoveRight extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["l"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = new Position(position.line, position.character + 1); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return new Position(position.line, position.character + 1); } } @@ -980,19 +987,15 @@ class MoveFindForward extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["f", ""]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { const toFind = vimState.actionState.actionKeys[1]; - vimState.cursorPosition = position.findForwards(toFind); - - return vimState; + return position.findForwards(toFind); } - public async execActionForOperator(position: Position, vimState: VimState): Promise { - const state = await this.execAction(position, vimState); - state.cursorPosition = state.cursorPosition.getRight(); - - return state; + public async execActionForOperator(position: Position, vimState: VimState): Promise { + const pos = await this.execAction(position, vimState); + return pos.getRight(); } } @@ -1001,12 +1004,10 @@ class MoveFindBackward extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["F", ""]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { const toFind = vimState.actionState.actionKeys[1]; - vimState.cursorPosition = position.findBackwards(toFind); - - return vimState; + return position.findBackwards(toFind); } } @@ -1016,19 +1017,14 @@ class MoveTilForward extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["t", ""]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { const toFind = vimState.actionState.actionKeys[1]; - vimState.cursorPosition = position.tilForwards(toFind); - - return vimState; + return position.tilForwards(toFind); } - public async execActionForOperator(position: Position, vimState: VimState): Promise { - const state = await this.execAction(position, vimState); - state.cursorPosition = state.cursorPosition.getRight(); - - return state; + public async execActionForOperator(position: Position, vimState: VimState): Promise { + return (await this.execAction(position, vimState)).getRight(); } } @@ -1037,12 +1033,10 @@ class MoveTilBackward extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["T", ""]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { const toFind = vimState.actionState.actionKeys[1]; - vimState.cursorPosition = position.tilBackwards(toFind); - - return vimState; + return position.tilBackwards(toFind); } } @@ -1052,10 +1046,8 @@ class MoveLineEnd extends BaseMovement { keys = ["$"]; setsDesiredColumnToEOL = true; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getLineEnd(); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getLineEnd(); } } @@ -1064,9 +1056,8 @@ class MoveLineBegin extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["0"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getLineBegin(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getLineBegin(); } } @@ -1075,9 +1066,8 @@ class MoveNonBlank extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["^"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getFirstLineNonBlankChar(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getFirstLineNonBlankChar(); } } @@ -1086,9 +1076,8 @@ class MoveNonBlankFirst extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["g", "g"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getDocumentStart(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getDocumentStart(); } } @@ -1097,9 +1086,8 @@ class MoveNonBlankLast extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["G"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getDocumentEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getDocumentEnd(); } } @@ -1108,49 +1096,44 @@ export class MoveWordBegin extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["w"]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.actionState.operator instanceof ChangeOperator) { /* - From the Vim manual: + From the Vim manual: - Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is - on a non-blank. This is because "cw" is interpreted as change-word, and a - word does not include the following white space. + Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is + on a non-blank. This is because "cw" is interpreted as change-word, and a + word does not include the following white space. */ - vimState.cursorPosition = position.getCurrentWordEnd().getRight(); + return position.getCurrentWordEnd().getRight(); } else { - vimState.cursorPosition = position.getWordRight(); + return position.getWordRight(); } - - return vimState; } - public async execActionForOperator(position: Position, vimState: VimState): Promise { + public async execActionForOperator(position: Position, vimState: VimState): Promise { const result = await this.execAction(position, vimState); - /* - From the Vim documentation: + /* + From the Vim documentation: - Another special case: When using the "w" motion in combination with an - operator and the last word moved over is at the end of a line, the end of - that word becomes the end of the operated text, not the first word in the - next line. + Another special case: When using the "w" motion in combination with an + operator and the last word moved over is at the end of a line, the end of + that word becomes the end of the operated text, not the first word in the + next line. + */ - TODO - move this into actions.ts, add test. - */ - - if (result.cursorPosition.isLineBeginning()) { - result.cursorPosition = result.cursorPosition.getLeftThroughLineBreaks(); - } + if (result.isLineBeginning()) { + return result.getLeftThroughLineBreaks(); + } - if (result.cursorPosition.isLineEnd()) { - result.cursorPosition = new Position(result.cursorPosition.line, result.cursorPosition.character + 1); - } + if (result.isLineEnd()) { + return new Position(result.line, result.character + 1); + } - return vimState; + return result; } - } @RegisterAction @@ -1158,16 +1141,14 @@ class MoveFullWordBegin extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["W"]; - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.actionState.operator instanceof ChangeOperator) { // See note for w - vimState.cursorPosition = position.getCurrentBigWordEnd().getRight(); + return position.getCurrentBigWordEnd().getRight(); } else { - vimState.cursorPosition = position.getBigWordRight(); + return position.getBigWordRight(); } - - return vimState; } } @@ -1176,17 +1157,15 @@ class MoveWordEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["e"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getCurrentWordEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getCurrentWordEnd(); } public async execActionForOperator(position: Position, - vimState: VimState): Promise { + vimState: VimState): Promise { let end = position.getCurrentWordEnd(); - vimState.cursorPosition = new Position(end.line, end.character + 1); - return vimState; + return new Position(end.line, end.character + 1); } } @@ -1195,9 +1174,8 @@ class MoveFullWordEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["E"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getCurrentBigWordEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getCurrentBigWordEnd(); } } @@ -1206,9 +1184,8 @@ class MoveLastWordEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["g", "e"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getLastWordEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getLastWordEnd(); } } @@ -1217,9 +1194,8 @@ class MoveLastFullWordEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["g", "E"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getLastBigWordEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getLastBigWordEnd(); } } @@ -1228,9 +1204,8 @@ class MoveBeginningWord extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["b"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getWordLeft(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getWordLeft(); } } @@ -1239,9 +1214,8 @@ class MoveBeginningFullWord extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["B"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getBigWordLeft(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getBigWordLeft(); } } @@ -1250,9 +1224,8 @@ class MoveParagraphEnd extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["}"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getCurrentParagraphEnd(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getCurrentParagraphEnd(); } } @@ -1261,9 +1234,8 @@ class MoveParagraphBegin extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; keys = ["{"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorPosition = position.getCurrentParagraphBeginning(); - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return position.getCurrentParagraphBeginning(); } } @@ -1347,21 +1319,19 @@ class ActionReplaceCharacter extends BaseCommand { // (dd yy cc << >>) // These work because there is a check in does/couldActionApply where // you can't run an operator if you already have one going (which is logical). - +// However there is the slightly weird behavior where dy actually deletes the whole +// line, lol. @RegisterAction class MoveDD extends BaseMovement { modes = [ModeName.Normal]; keys = ["d"]; - public async execAction(position: Position, vimState: VimState): Promise { - let start = position.getLineBegin(); - let stop = position.getLineEndIncludingEOL(); - - vimState.cursorStartPosition = start; - vimState.cursorPosition = stop; - vimState.currentRegisterMode = RegisterMode.LineWise; - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return { + start : position.getLineBegin(), + stop : position.getLineEndIncludingEOL(), + registerMode: RegisterMode.LineWise, + }; } } @@ -1370,12 +1340,12 @@ class MoveYY extends BaseMovement { modes = [ModeName.Normal]; keys = ["y"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorStartPosition = position.getLineBegin(); - vimState.cursorPosition = position.getLineEnd(); - vimState.currentRegisterMode = RegisterMode.LineWise; - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return { + start : position.getLineBegin(), + stop : position.getLineEndIncludingEOL(), + registerMode: RegisterMode.LineWise, + }; } } @@ -1384,11 +1354,11 @@ class MoveCC extends BaseMovement { modes = [ModeName.Normal]; keys = ["c"]; - public async execAction(position: Position, vimState: VimState): Promise { - vimState.cursorStartPosition = position.getLineBegin(); - vimState.cursorPosition = position.getLineEnd(); - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return { + start : position.getLineBegin(), + stop : position.getLineEnd(), + }; } } @@ -1397,15 +1367,11 @@ class MoveIndent extends BaseMovement { modes = [ModeName.Normal]; keys = [">"]; - public async execAction(position: Position, vimState: VimState): Promise { - let start = position.getLineBegin(); - let stop = position.getLineEndIncludingEOL(); - - vimState.cursorStartPosition = start; - vimState.cursorPosition = stop; - vimState.currentRegisterMode = RegisterMode.LineWise; - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return { + start : position.getLineBegin(), + stop : position.getLineEnd(), + }; } } @@ -1414,15 +1380,11 @@ class MoveOutdent extends BaseMovement { modes = [ModeName.Normal]; keys = ["<"]; - public async execAction(position: Position, vimState: VimState): Promise { - let start = position.getLineBegin(); - let stop = position.getLineEndIncludingEOL(); - - vimState.cursorStartPosition = start; - vimState.cursorPosition = stop; - vimState.currentRegisterMode = RegisterMode.LineWise; - - return vimState; + public async execAction(position: Position, vimState: VimState): Promise { + return { + start : position.getLineBegin(), + stop : position.getLineEnd(), + }; } } @@ -1455,19 +1417,15 @@ class MovementAWordTextObject extends BaseMovement { modes = [ModeName.Normal, ModeName.Visual]; keys = ["a", "w"]; - public async execActionForOperator(position: Position, vimState: VimState): Promise { - const res = await this.execAction(position, vimState); - - res.cursorPosition = res.cursorPosition.getRight(); - - return res; - } - - public async execAction(position: Position, vimState: VimState): Promise { + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.currentMode === ModeName.Visual && !vimState.cursorPosition.isEqual(vimState.cursorStartPosition)) { // TODO: This is kind of a bad way to do this, but text objects only work in // visual mode if you JUST entered visual mode - return new MoveWordBegin().execAction(position, vimState); + // TODO TODO: I just looked at this again and omg this is awful what was I smoking plz get rid of this asap + return { + start: vimState.searchCursorStartPosition, + stop: await new MoveWordBegin().execAction(position, vimState), + }; } const currentChar = TextEditor.getLineAt(position).text[position.character]; @@ -1475,35 +1433,41 @@ class MovementAWordTextObject extends BaseMovement { // TODO(whitespace) - this is a bad way to do this. we need some sort of global // white space checking function. if (currentChar === ' ' || currentChar === '\t') { - vimState.cursorStartPosition = position.getLastWordEnd().getRight(); - vimState.cursorPosition = position.getCurrentWordEnd(); + return { + start: position.getLastWordEnd().getRight(), + stop: position.getCurrentWordEnd() + }; } else { - vimState.cursorStartPosition = position.getWordLeft(true); - vimState.cursorPosition = position.getWordRight().getLeft(); + return { + start: position.getWordLeft(true), + stop: position.getWordRight().getLeft() + }; } - - return vimState; } -} -@RegisterAction -class MovementIWordTextObject extends BaseMovement { - modes = [ModeName.Normal, ModeName.Visual]; - keys = ["i", "w"]; - public async execActionForOperator(position: Position, vimState: VimState): Promise { + public async execActionForOperator(position: Position, vimState: VimState): Promise { const res = await this.execAction(position, vimState); - res.cursorPosition = res.cursorPosition.getRight(); + res.stop = res.stop.getRight(); return res; } +} - public async execAction(position: Position, vimState: VimState): Promise { +@RegisterAction +class MovementIWordTextObject extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual]; + keys = ["i", "w"]; + + public async execAction(position: Position, vimState: VimState): Promise { if (vimState.currentMode === ModeName.Visual && !vimState.cursorPosition.isEqual(vimState.cursorStartPosition)) { // TODO: This is kind of a bad way to do this, but text objects only work in // visual mode if you JUST entered visual mode - return new MoveWordBegin().execAction(position, vimState); + return { + start: vimState.searchCursorStartPosition, + stop: await new MoveWordBegin().execAction(position, vimState), + }; } const currentChar = TextEditor.getLineAt(position).text[position.character]; @@ -1511,14 +1475,24 @@ class MovementIWordTextObject extends BaseMovement { // TODO(whitespace) - this is a bad way to do this. we need some sort of global // white space checking function. if (currentChar === ' ' || currentChar === '\t') { - vimState.cursorStartPosition = position.getLastWordEnd().getRight(); - vimState.cursorPosition = position.getWordRight().getLeft(); + return { + start: position.getLastWordEnd().getRight(), + stop: position.getWordRight().getLeft() + }; } else { - vimState.cursorStartPosition = position.getWordLeft(true); - vimState.cursorPosition = position.getCurrentWordEnd(true); + return { + start: position.getWordLeft(true), + stop: position.getCurrentWordEnd(true), + }; } + } - return vimState; + public async execActionForOperator(position: Position, vimState: VimState): Promise { + const res = await this.execAction(position, vimState); + + res.stop = res.stop.getRight(); + + return res; } } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index b0dba92e02e..517c26e5f18 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -10,7 +10,7 @@ import { SearchInProgressMode } from './modeSearchInProgress'; import { VisualLineMode } from './modeVisualLine'; import { BaseMovement, BaseCommand, Actions, - BaseOperator, PutCommand, + BaseOperator, PutCommand, isIMovement, KeypressState } from './../actions/actions'; import { Configuration } from '../configuration/configuration'; import { Position } from './../motion/position'; @@ -85,7 +85,7 @@ export class VimState { public searchCursorStartPosition: Position = undefined; /** - * 1 === forward + * 1 === forward * -1 === backward */ public searchDirection: number = 1; @@ -126,16 +126,14 @@ export class VimState { } public shouldResetCurrentDotKeys(): boolean { - const isBareMovement = - (this.currentMode === ModeName.Normal && this.actionState.isOnlyAMovement()); - - return isBareMovement || this.isFullDotAction(); + // TODO merge + return this.isFullDotAction(); } } /** - * The ActionState class represents state relevant to the current - * action that the user is doing. Example: Imagine that the user types: + * The ActionState class holds the current action that the user is + * doing. Example: Imagine that the user types: * * 5"qdw * @@ -144,6 +142,16 @@ export class VimState { * * copy into q register * * delete operator * * word movement + * + * + * Or imagine the user types: + * + * vw$}}d + * + * Then the state would be + * * Visual mode action + * * (a list of all the motions you ran) + * * delete operator */ export class ActionState { /** @@ -152,15 +160,15 @@ export class ActionState { */ public actionKeys: string[] = []; + public motionsRun: BaseMovement[] = []; + /** - * The operator (e.g. d, y, >>) the user wants to run, if there is one. + * The operator (e.g. d, y, >) the user wants to run, if there is one. */ public operator: BaseOperator = undefined; public command: BaseCommand = undefined; - public movement: BaseMovement = undefined; - /** * The number of times the user wants to repeat this action. */ @@ -169,14 +177,10 @@ export class ActionState { public vimState: VimState; public readyToExecute(): boolean { - if (this.movement) { - return true; - } - // Visual modes do not require a motion -- they ARE the motion. - if (this.operator && ( + if (this.operator && (this.motionsRun.length > 0 || ( this.vimState.currentMode === ModeName.Visual || - this.vimState.currentMode === ModeName.VisualLine)) { + this.vimState.currentMode === ModeName.VisualLine))) { return true; } @@ -191,15 +195,10 @@ export class ActionState { public get isInInitialState(): boolean { return this.operator === undefined && this.command === undefined && - this.movement === undefined && + this.motionsRun.length === 0 && this.count === 1; } - public isOnlyAMovement(): boolean { - return this.operator === undefined && - this.command === undefined; - } - constructor(vimState: VimState) { this.vimState = vimState; } @@ -310,6 +309,7 @@ export class ModeHandler implements vscode.Disposable { } // Draw block cursor. + // The reason we make a copy of options is because it's a // getter/setter and it won't trigger it's event if we modify // the object in place @@ -323,9 +323,6 @@ export class ModeHandler implements vscode.Disposable { this.setupStatusBarItem(statusBarText ? `-- ${statusBarText.toUpperCase()} --` : ''); } - /** - * Along with executeState(), one of the core processing functions of VSCVim. - */ async handleKeyEvent(key: string): Promise { this._vimState = await this.handleKeyEventHelper(key, this._vimState); @@ -355,54 +352,28 @@ export class ModeHandler implements vscode.Disposable { return vimState; } - if (action instanceof BaseMovement) { - actionState.movement = action; - } else if (action instanceof BaseOperator) { + if (action instanceof BaseOperator) { actionState.operator = action; } else if (action instanceof BaseCommand) { actionState.command = action; + } else if (action instanceof BaseMovement) { + vimState = await this.executeMovement(vimState, action); } else { - console.log("Weird command found!"); + console.log("Weird command found!!!! This is extremely very bads!"); + + return vimState; } if (actionState.readyToExecute()) { - if (this.currentMode.name !== ModeName.Visual && - this.currentMode.name !== ModeName.VisualLine) { - vimState.cursorStartPosition = vimState.cursorPosition; - } - vimState = await this.executeState(vimState); - if (vimState.commandAction !== VimCommandActions.DoNothing) { - vimState = await this.handleCommand(vimState); - } - // Update mode if (vimState.currentMode !== this.currentModeName) { this.setCurrentModeByName(vimState); } - await this.updateView(vimState, { - selectionStart: vimState.cursorStartPosition, - selectionStop : vimState.cursorPosition, - currentMode : vimState.currentMode, - }); - - // Updated desired column - - const movement = actionState.movement, command = actionState.command; - if ((movement && !movement.doesntChangeDesiredColumn) || command) { - // We check !operator here because e.g. d$ should NOT set the desired column to EOL. - - if (movement && movement.setsDesiredColumnToEOL && !actionState.operator) { - vimState.desiredColumn = Number.POSITIVE_INFINITY; - } else { - vimState.desiredColumn = vimState.cursorPosition.character; - } - } - - // Update dot keys + // Update dot keys (TODO - should become useless, soon). if (vimState.isFullDotAction()) { vimState.previousFullAction = vimState.currentFullAction; @@ -412,59 +383,106 @@ export class ModeHandler implements vscode.Disposable { vimState.currentFullAction = []; } - // Scroll to position of cursor + // Reset state + + vimState.actionState = new ActionState(vimState); + } + + actionState.actionKeys = []; + vimState.currentRegisterMode = RegisterMode.FigureItOutFromCurrentMode; + + if (this.currentModeName === ModeName.Normal) { + vimState.cursorStartPosition = vimState.cursorPosition; + } - vscode.window.activeTextEditor.revealRange(new vscode.Range(vimState.cursorPosition, vimState.cursorPosition)); + // Updated desired column - // Draw search highlight + const command = actionState.command; + const movement = action instanceof BaseMovement ? action : undefined; - if (this.currentMode.name === ModeName.SearchInProgressMode && - this._vimState.nextSearchMatchPosition !== undefined) { - let range = new vscode.Range( - this._vimState.nextSearchMatchPosition, - this._vimState.nextSearchMatchPosition.getRight(this._vimState.searchString.length)); + if ((movement && !movement.doesntChangeDesiredColumn) || command) { + // We check !operator here because e.g. d$ should NOT set the desired column to EOL. - vscode.window.activeTextEditor.setDecorations(this._caretDecoration, [range]); + if (movement && movement.setsDesiredColumnToEOL && !actionState.operator) { + vimState.desiredColumn = Number.POSITIVE_INFINITY; } else { - vscode.window.activeTextEditor.setDecorations(this._caretDecoration, []); + vimState.desiredColumn = vimState.cursorPosition.character; } + } - // Reset state + await this.updateView(vimState, { + selectionStart: vimState.cursorStartPosition, + selectionStop : vimState.cursorPosition, + currentMode : vimState.currentMode, + }); - vimState.actionState = new ActionState(vimState); - vimState.currentRegisterMode = RegisterMode.FigureItOutFromCurrentMode; + return vimState; + } + + private async executeMovement(vimState: VimState, movement: BaseMovement): Promise { + let actionState = vimState.actionState; + + const result = actionState.operator ? + await movement.execActionForOperator(vimState.cursorPosition, vimState) : + await movement.execAction (vimState.cursorPosition, vimState); + + if (result instanceof Position) { + vimState.cursorPosition = result; + } else if (isIMovement(result)) { + vimState.cursorPosition = result.stop; + vimState.cursorStartPosition = result.start; + vimState.currentRegisterMode = result.registerMode; } - actionState.actionKeys = []; + let stop = vimState.cursorPosition; + + // Keep the cursor within bounds + + if (vimState.currentMode === ModeName.Normal && !actionState.operator) { + if (stop.character >= Position.getLineLength(stop.line)) { + vimState.cursorPosition = stop.getLineEnd().getLeft(); + } + } else { + + // Vim does this weird thing where it allows you to select and delete + // the newline character, which it places 1 past the last character + // in the line. This is why we use > instead of >=. + + if (stop.character > Position.getLineLength(stop.line)) { + vimState.cursorPosition = stop.getLineEnd(); + } + } + + if (actionState.operator || this.currentMode.name !== ModeName.Normal) { + actionState.motionsRun.push(movement); + } return vimState; } private async executeState(vimState: VimState): Promise { - let start = vimState.cursorStartPosition; - let stop = vimState.cursorPosition; + let start = vimState.cursorStartPosition; + let stop = vimState.cursorPosition; let actionState = vimState.actionState; if (actionState.command) { - return await actionState.command.exec(stop, vimState); - } + vimState = await actionState.command.exec(stop, vimState); - if (actionState.movement) { - vimState = actionState.operator ? - await actionState.movement.execActionForOperator(stop, vimState) : - await actionState.movement.execAction (stop, vimState); + if (vimState.commandAction !== VimCommandActions.DoNothing) { + vimState = await this.handleCommand(vimState); + } - actionState = vimState.actionState; - start = vimState.cursorStartPosition; - stop = vimState.cursorPosition; + return vimState; } if (actionState.operator) { - if (actionState.movement) { + if (vimState.currentMode !== ModeName.Visual && + vimState.currentMode !== ModeName.VisualLine && + vimState.currentRegisterMode !== RegisterMode.LineWise) { if (Position.EarlierOf(start, stop) === start) { stop = stop.getLeft(); } else { - start = start.getLeft(); + stop = stop.getRight(); } } @@ -474,9 +492,9 @@ export class ModeHandler implements vscode.Disposable { } return await actionState.operator.run(vimState, start, stop); - } else { - return vimState; } + + console.log("This is bad! Execution should never get here."); } private async handleCommand(vimState: VimState): Promise { @@ -514,30 +532,15 @@ export class ModeHandler implements vscode.Disposable { return vimState; } + // TODO: this method signature is totally nonsensical!!!! private async updateView(vimState: VimState, viewState: IViewState): Promise { // Update cursor position let start = viewState.selectionStart; let stop = viewState.selectionStop; - // Keep the cursor within bounds - - if (viewState.currentMode === ModeName.Normal) { - if (stop.character >= Position.getLineLength(stop.line)) { - stop = stop.getLineEnd().getLeft(); - vimState.cursorPosition = stop; - } - } else if (viewState.currentMode === ModeName.Visual || - viewState.currentMode === ModeName.VisualLine) { - - // Vim does this weird thing where it allows you to select and delete - // the newline character, which it places 1 past the last character - // in the line. This is why we use > instead of >=. - - if (stop.character > Position.getLineLength(stop.line)) { - stop = stop.getLineEnd(); - vimState.cursorPosition = stop; - } + if (viewState.currentMode === ModeName.Visual || + viewState.currentMode === ModeName.VisualLine) { /** * Always select the letter that we started visual mode on, no matter @@ -567,6 +570,23 @@ export class ModeHandler implements vscode.Disposable { } else { vscode.window.activeTextEditor.selection = new vscode.Selection(stop, stop); } + + // Scroll to position of cursor + + vscode.window.activeTextEditor.revealRange(new vscode.Range(vimState.cursorPosition, vimState.cursorPosition)); + + // Draw search highlight + + if (this.currentMode.name === ModeName.SearchInProgressMode && + this._vimState.nextSearchMatchPosition !== undefined) { + let range = new vscode.Range( + this._vimState.nextSearchMatchPosition, + this._vimState.nextSearchMatchPosition.getRight(this._vimState.searchString.length)); + + vscode.window.activeTextEditor.setDecorations(this._caretDecoration, [range]); + } else { + vscode.window.activeTextEditor.setDecorations(this._caretDecoration, []); + } } async handleMultipleKeyEvents(keys: string[]): Promise {