diff --git a/src/actions/actions.ts b/src/actions/actions.ts index c7f55bbd5af..dec18ac2ceb 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -175,7 +175,7 @@ class CommandInsertInInsertMode extends BaseCommand { keys = [""]; public async exec(position: Position, vimState: VimState): Promise { - await TextEditor.insert(vimState.actionState.keysPressed[0]); + await TextEditor.insert(vimState.actionState.actionKeys[0]); vimState.cursorStartPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start); vimState.cursorPosition = Position.FromVSCodePosition(vscode.window.activeTextEditor.selection.start); @@ -360,6 +360,18 @@ class CommandFind extends BaseCommand { } } +@RegisterAction +class CommandDot extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["."]; + + public async exec(position: Position, vimState: VimState): Promise { + vimState.commandAction = VimCommandActions.Dot; + + return vimState; + } +} + @RegisterAction class CommandFold extends BaseCommand { modes = [ModeName.Visual, ModeName.VisualLine]; @@ -698,7 +710,7 @@ class MoveFindForward extends BaseMovement { keys = ["f", ""]; public async execAction(position: Position, vimState: VimState): Promise { - const toFind = vimState.actionState.keysPressed[1]; + const toFind = vimState.actionState.actionKeys[1]; vimState.cursorPosition = position.findForwards(toFind); @@ -712,7 +724,7 @@ class MoveFindBackward extends BaseMovement { keys = ["F", ""]; public async execAction(position: Position, vimState: VimState): Promise { - const toFind = vimState.actionState.keysPressed[1]; + const toFind = vimState.actionState.actionKeys[1]; vimState.cursorPosition = position.findBackwards(toFind); @@ -727,7 +739,7 @@ class MoveTilForward extends BaseMovement { keys = ["t", ""]; public async execAction(position: Position, vimState: VimState): Promise { - const toFind = vimState.actionState.keysPressed[1]; + const toFind = vimState.actionState.actionKeys[1]; vimState.cursorPosition = position.tilForwards(toFind); @@ -741,7 +753,7 @@ class MoveTilBackward extends BaseMovement { keys = ["T", ""]; public async execAction(position: Position, vimState: VimState): Promise { - const toFind = vimState.actionState.keysPressed[1]; + const toFind = vimState.actionState.actionKeys[1]; vimState.cursorPosition = position.tilBackwards(toFind); diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 2c4661022fa..1560af52d12 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -9,7 +9,7 @@ import { VisualMode } from './modeVisual'; import { VisualLineMode } from './modeVisualLine'; import { BaseMovement, BaseCommand, Actions, - BaseOperator, + BaseOperator, PutCommand, KeypressState } from './../actions/actions'; import { Configuration } from '../configuration/configuration'; import { Position } from './../motion/position'; @@ -27,7 +27,8 @@ export enum VimCommandActions { Undo, Redo, MoveFullPageDown, - MoveFullPageUp + MoveFullPageUp, + Dot, } /** @@ -49,6 +50,10 @@ export class VimState { */ public desiredColumn = 0; + public oldDotKeys = []; + + public dotKeys = []; + /** * The position the cursor will be when this action finishes. */ @@ -81,6 +86,25 @@ export class VimState { a.vimState = this; this._actionState = a; } + + public isFullDotAction(): boolean { + const isInNormalMode = this.currentMode === ModeName.Normal; + const justFinishedOperation = this.actionState.operator !== undefined; + const justFinishedPut = this.actionState.command instanceof PutCommand; + + const justReturnedToNormalMode = + this.actionState.actionKeys[0] === "" || + this.actionState.actionKeys[0] === ""; + + return isInNormalMode && (justFinishedOperation || justReturnedToNormalMode || justFinishedPut); + } + + public shouldResetCurrentDotKeys(): boolean { + const isBareMovement = + (this.currentMode === ModeName.Normal && this.actionState.isOnlyAMovement()); + + return isBareMovement || this.isFullDotAction(); + } } /** @@ -97,10 +121,10 @@ export class VimState { */ export class ActionState { /** - * Keeps track of the most recent keys pressed. Comes in handy when parsing + * Keeps track of keys pressed for the next action. Comes in handy when parsing * multiple length movements, e.g. gg. */ - private _keysPressed: string[] = []; + private _actionKeys: string[] = []; /** * The operator (e.g. d, y, >>) the user wants to run, if there is one. @@ -120,9 +144,9 @@ export class ActionState { public readyToExecute = false; - public get keysPressed(): string[] { return this._keysPressed; } - public set keysPressed(val: string[]) { - this._keysPressed = val; + public get actionKeys(): string[] { return this._actionKeys; } + public set actionKeys(val: string[]) { + this._actionKeys = val; this.actionStateChanged(); } @@ -180,12 +204,16 @@ export class ActionState { this._count === 1; } + public isOnlyAMovement(): boolean { + return this._operator === undefined && + this._command === undefined; + } + constructor(vimState: VimState) { this.vimState = vimState; } } - interface IViewState { selectionStart: Position; selectionStop : Position; @@ -213,7 +241,9 @@ export class ModeHandler implements vscode.Disposable { new VisualLineMode(), ]; - this.setCurrentModeByName(ModeName.Normal); + this._vimState.currentMode = ModeName.Normal; + + this.setCurrentModeByName(this._vimState); } /** @@ -223,20 +253,20 @@ export class ModeHandler implements vscode.Disposable { return this._modes.find(mode => mode.isActive); } - setCurrentModeByName(modeName: ModeName) { + setCurrentModeByName(vimState: VimState) { let activeMode: Mode; - this._vimState.currentMode = modeName; + this._vimState.currentMode = vimState.currentMode; // TODO actually making these into functions on modes - // like we used to have is a good idea. for (let mode of this._modes) { - if (mode.name === modeName) { + if (mode.name === vimState.currentMode) { activeMode = mode; } - mode.isActive = (mode.name === modeName); + mode.isActive = (mode.name === vimState.currentMode); } // Draw block cursor. @@ -244,12 +274,12 @@ export class ModeHandler implements vscode.Disposable { // getter/setter and it won't trigger it's event if we modify // the object in place const options = vscode.window.activeTextEditor.options; - (options as any).cursorStyle = modeName === ModeName.Insert ? + (options as any).cursorStyle = vimState.currentMode === ModeName.Insert ? (vscode as any).TextEditorCursorStyle.Line : (vscode as any).TextEditorCursorStyle.Block; vscode.window.activeTextEditor.options = options; - const statusBarText = (this.currentMode.name === ModeName.Normal) ? '' : ModeName[modeName]; + const statusBarText = (this.currentMode.name === ModeName.Normal) ? '' : ModeName[vimState.currentMode]; this.setupStatusBarItem(statusBarText ? `-- ${statusBarText.toUpperCase()} --` : ''); } @@ -257,25 +287,32 @@ export class ModeHandler implements vscode.Disposable { * Along with executeState(), one of the core processing functions of VSCVim. */ async handleKeyEvent(key: string): Promise { + this._vimState = await this.handleKeyEventHelper(key, this._vimState); + + return true; + } + + async handleKeyEventHelper(key: string, vimState: VimState): Promise { // Due to a limitation in Electron, en-US QWERTY char codes are used in international keyboards. // We'll try to mitigate this problem until it's fixed upstream. // https://github.com/Microsoft/vscode/issues/713 key = this._configuration.keyboardLayout.translate(key); - let actionState = this._vimState.actionState; + let actionState = vimState.actionState; - actionState.keysPressed.push(key); + actionState.actionKeys.push(key); + vimState.dotKeys.push(key); - let action = Actions.getRelevantAction(actionState.keysPressed, this._vimState); + let action = Actions.getRelevantAction(actionState.actionKeys, vimState); if (action === KeypressState.NoPossibleMatch) { console.log("Nothing matched!"); - this._vimState.actionState = new ActionState(this._vimState); - return false; + vimState.actionState = new ActionState(vimState); + return vimState; } else if (action === KeypressState.WaitingOnKeys) { - return true; + return vimState; } if (action instanceof BaseMovement) { @@ -289,25 +326,25 @@ export class ModeHandler implements vscode.Disposable { if (actionState.readyToExecute) { if (this.currentMode.name !== ModeName.Visual && this.currentMode.name !== ModeName.VisualLine) { - this._vimState.cursorStartPosition = this._vimState.cursorPosition; + vimState.cursorStartPosition = vimState.cursorPosition; } - this._vimState = await this.executeState(); + vimState = await this.executeState(vimState); - if (this._vimState.commandAction !== VimCommandActions.DoNothing) { - await this.handleCommand(this._vimState); + if (vimState.commandAction !== VimCommandActions.DoNothing) { + vimState = await this.handleCommand(vimState); } // Update mode - if (this._vimState.currentMode !== this.currentModeName) { - this.setCurrentModeByName(this._vimState.currentMode); + if (vimState.currentMode !== this.currentModeName) { + this.setCurrentModeByName(vimState); } - await this.updateView({ - selectionStart: this._vimState.cursorStartPosition, - selectionStop : this._vimState.cursorPosition, - currentMode : this._vimState.currentMode, + await this.updateView(vimState, { + selectionStart: vimState.cursorStartPosition, + selectionStop : vimState.cursorPosition, + currentMode : vimState.currentMode, }); // Updated desired column @@ -315,38 +352,47 @@ export class ModeHandler implements vscode.Disposable { const movement = actionState.movement, command = actionState.command; if ((movement && !movement.doesntChangeDesiredColumn) || command) { if (movement && movement.setsDesiredColumnToEOL) { - this._vimState.desiredColumn = Number.POSITIVE_INFINITY; + vimState.desiredColumn = Number.POSITIVE_INFINITY; } else { - this._vimState.desiredColumn = this._vimState.cursorPosition.character; + vimState.desiredColumn = vimState.cursorPosition.character; } } - this._vimState.actionState = new ActionState(this._vimState); + // Update dot keys + + if (vimState.isFullDotAction()) { + vimState.oldDotKeys = vimState.dotKeys; + } + + if (vimState.shouldResetCurrentDotKeys()) { + vimState.dotKeys = []; + } + + vimState.actionState = new ActionState(vimState); } - actionState.keysPressed = []; + actionState.actionKeys = []; - return !!action; + return vimState; } - private async executeState(): Promise { - let start = this._vimState.cursorStartPosition; - let stop = this._vimState.cursorPosition; - let actionState = this._vimState.actionState; - let newState: VimState; + private async executeState(vimState: VimState): Promise { + let start = vimState.cursorStartPosition; + let stop = vimState.cursorPosition; + let actionState = vimState.actionState; if (actionState.command) { - return await actionState.command.exec(stop, this._vimState); + return await actionState.command.exec(stop, vimState); } if (actionState.movement) { - newState = actionState.operator ? - await actionState.movement.execActionForOperator(stop, this._vimState) : - await actionState.movement.execAction (stop, this._vimState); + vimState = actionState.operator ? + await actionState.movement.execActionForOperator(stop, vimState) : + await actionState.movement.execAction (stop, vimState); - actionState = newState.actionState; - start = newState.cursorStartPosition; - stop = newState.cursorPosition; + actionState = vimState.actionState; + start = vimState.cursorStartPosition; + stop = vimState.cursorPosition; } if (actionState.operator) { @@ -363,14 +409,18 @@ export class ModeHandler implements vscode.Disposable { stop = Position.LaterOf(start, stop).getLineEnd(); } - return await actionState.operator.run(this._vimState, start, stop); + return await actionState.operator.run(vimState, start, stop); } else { - return newState; + return vimState; } } - private async handleCommand(vimState: VimState): Promise { - switch (vimState.commandAction) { + private async handleCommand(vimState: VimState): Promise { + const command = vimState.commandAction; + + vimState.commandAction = VimCommandActions.DoNothing; + + switch (command) { case VimCommandActions.ShowCommandLine: await showCmdLine("", this); break; case VimCommandActions.Find: await vscode.commands.executeCommand("actions.find"); break; case VimCommandActions.Fold: await vscode.commands.executeCommand("editor.fold"); break; @@ -381,12 +431,22 @@ export class ModeHandler implements vscode.Disposable { case VimCommandActions.Redo: await vscode.commands.executeCommand("redo"); break; case VimCommandActions.MoveFullPageDown: await vscode.commands.executeCommand("cursorPageUp"); break; case VimCommandActions.MoveFullPageUp: await vscode.commands.executeCommand("cursorPageDown"); break; + case VimCommandActions.Dot: + const oldDotKeysCopy = vimState.oldDotKeys.slice(0); + + vimState.actionState = new ActionState(vimState); + vimState.dotKeys = []; + + for (let key of oldDotKeysCopy) { + vimState = await this.handleKeyEventHelper(key, vimState); + } + break; } - vimState.commandAction = VimCommandActions.DoNothing; + return vimState; } - private async updateView(viewState: IViewState): Promise { + private async updateView(vimState: VimState, viewState: IViewState): Promise { // Update cursor position let start = viewState.selectionStart; @@ -397,7 +457,7 @@ export class ModeHandler implements vscode.Disposable { if (viewState.currentMode === ModeName.Normal) { if (stop.character >= Position.getLineLength(stop.line)) { stop = stop.getLineEnd().getLeft(); - this._vimState.cursorPosition = stop; + vimState.cursorPosition = stop; } } else if (viewState.currentMode === ModeName.Visual || viewState.currentMode === ModeName.VisualLine) { @@ -408,7 +468,7 @@ export class ModeHandler implements vscode.Disposable { if (stop.character > Position.getLineLength(stop.line)) { stop = stop.getLineEnd(); - this._vimState.cursorPosition = stop; + vimState.cursorPosition = stop; } /** diff --git a/src/mode/modeInsert.ts b/src/mode/modeInsert.ts index b23f52930b6..068235b84ad 100644 --- a/src/mode/modeInsert.ts +++ b/src/mode/modeInsert.ts @@ -1,18 +1,9 @@ "use strict"; import { ModeName, Mode } from './mode'; -import { TextEditor } from './../textEditor'; -import { ActionState } from './modeHandler'; export class InsertMode extends Mode { constructor() { super(ModeName.Insert); } - - async handleAction(action: ActionState): Promise { - // TODO: REALLY dumb, especially since there are actually actions - // that work in insert mode. - - await TextEditor.insert(action.keysPressed[0]); - } } diff --git a/test/mode/modeHandler.test.ts b/test/mode/modeHandler.test.ts index bd2aa7f995e..911e4875a62 100644 --- a/test/mode/modeHandler.test.ts +++ b/test/mode/modeHandler.test.ts @@ -2,9 +2,9 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import {setupWorkspace, cleanUpWorkspace} from './../testUtils'; -import {ModeName} from '../../src/mode/mode'; -import {ModeHandler} from '../../src/mode/modeHandler'; +import { setupWorkspace, cleanUpWorkspace } from './../testUtils'; +import { ModeName } from '../../src/mode/mode'; +import { ModeHandler } from '../../src/mode/modeHandler'; suite("Mode Handler", () => { @@ -19,31 +19,25 @@ suite("Mode Handler", () => { assert.equal(modeHandler.currentMode.isActive, true); }); - test("can set current mode", () => { + test("can set current mode", async () => { var modeHandler = new ModeHandler(); - modeHandler.setCurrentModeByName(ModeName.Normal); assert.equal(modeHandler.currentMode.name, ModeName.Normal); - modeHandler.setCurrentModeByName(ModeName.Insert); + await modeHandler.handleKeyEvent("i"); assert.equal(modeHandler.currentMode.name, ModeName.Insert); - - /* - modeHandler.setCurrentModeByName(ModeName.Visual); - assert.equal(modeHandler.currentMode.Name, ModeName.Visual); - */ }); test("Uses correct cursor style depending on mode", async () => { const modeHandler = new ModeHandler(); - modeHandler.setCurrentModeByName(ModeName.Normal); - assert.equal(vscode.window.activeTextEditor.options.cursorStyle, vscode.TextEditorCursorStyle.Block); + assert.equal((vscode.window.activeTextEditor.options as any).cursorStyle, (vscode as any).TextEditorCursorStyle.Block); - modeHandler.setCurrentModeByName(ModeName.Insert); - assert.equal(vscode.window.activeTextEditor.options.cursorStyle, vscode.TextEditorCursorStyle.Line); + await modeHandler.handleKeyEvent("i"); + assert.equal((vscode.window.activeTextEditor.options as any).cursorStyle, (vscode as any).TextEditorCursorStyle.Line); - modeHandler.setCurrentModeByName(ModeName.Visual); - assert.equal(vscode.window.activeTextEditor.options.cursorStyle, vscode.TextEditorCursorStyle.Block); + await modeHandler.handleMultipleKeyEvents(["", "v"]); + assert.equal((vscode.window.activeTextEditor.options as any).cursorStyle, (vscode as any).TextEditorCursorStyle.Block); }); + });