From 2b9b1548a1904225727dfe93cf3c9a63124efd4a Mon Sep 17 00:00:00 2001 From: Linden Krouse Date: Thu, 12 May 2016 18:07:40 -0400 Subject: [PATCH] Added basic support for rebinding keys. Support is limited but nothing should be changed for anything not related to rebinding keys using user configuration. Partial fix for #185 and #128. --- README.md | 103 ++++++++++++ package.json | 12 ++ src/mode/commands.ts | 155 +++++++++++++++++ src/mode/mode.ts | 5 +- src/mode/modeHandler.ts | 16 +- src/mode/modeInsert.ts | 56 +++---- src/mode/modeNormal.ts | 317 ++++++++++++++++++++--------------- src/mode/modeVisual.ts | 8 +- test/mode/modeInsert.test.ts | 3 +- test/mode/modeNormal.test.ts | 3 +- test/mode/modeVisual.test.ts | 3 +- 11 files changed, 510 insertions(+), 171 deletions(-) create mode 100644 src/mode/commands.ts diff --git a/README.md b/README.md index 4631c5d3365..8de877750ee 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,109 @@ Adjust configurations through user settings (File -> Preferences -> User Setting * vim.keyboardLayout: * Supported Values: `en-US (QWERTY)` (default), `es-ES (QWERTY)`, `de-DE (QWERTZ)`, `da-DK (QWERTY)` + +Keybindings can be overridden for a mode by supplying a `{string: string}` object defining what key or keys should preform what action when pressed. + +Note: Currently, by defining keybindings for a mode, all bindings for that mode will be overridden. This should be fixed in a future update. + +Note: Currently, the escape key is still hardcoded to exit insert mode and the `v` key is still hardcoded to exit visual mode. + +Example: +```json +{ + "vim.normalModeKeybindings": { + "d": "DeleteChar", + "D": "DeleteLastChar" + }, + "vim.insertModeKeybindings": { + "e": "InsertAtCursor", + "E": "InsertAfterCursor" + } +} +``` + +* vim.normalModeKeybindings + * Supported Actions: +``` + MoveUp + MoveDown + MoveLeft + MoveRight + + MoveLineBegin + MoveLineEnd + MoveWordBegin + MoveWordEnd + MoveFullWordBegin + MoveFullWordEnd + MoveLastWord + MoveLastFullWord + MoveLastWordEnd + MoveLastFullWordEnd + + MoveFullPageUp + MoveFullPageDown + + MoveParagraphBegin + MoveParagraphEnd + + MoveNonBlank + MoveNonBlankFirst + MoveNonBlankLast + MoveMatchingBracket + + // Find + Find + + // Text Modification + Undo + Redo + Copy + Paste + + ChangeWord + ChangeFullWord + ChangeCurrentWord + ChangeCurrentWordToNext + ChangeToLineEnd + + DeleteLine + DeleteToNextWord + DeleteToFullNextWord + DeleteToWordEnd + DeleteToFullWordEnd + DeleteToWordBegin + DeleteToFullWordBegin + DeleteToLineEnd + + DeleteChar + DeleteLastChar + + Indent + Outdent + + // Misc + EnterCommand + ExitMessages +``` + +* vim.insertModeKeybindings + * Supported Actions: +``` + // Enter insert mode + InsertAtCursor + InsertAtLineBegin + InsertAfterCursor + InsertAtLineEnd + InsertNewLineBelow + InsertNewLineAbove +``` + +* vim.visualModeKeybindings + * Supported Actions: +``` + EnterVisualMode +``` ## Project Status diff --git a/package.json b/package.json index 1e2bc01cbc0..fcf098aaed7 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,18 @@ "default": "en-US (QWERTY)", "type": "string", "description": "Keyboard layout to use to translated key presses." + }, + "vim.normalModeKeybindings": { + "type": "object", + "description": "Keybinding overrides to use for normal mode." + }, + "vim.insertModeKeybindings": { + "type": "object", + "description": "Keybinding overrides to use for insert mode." + }, + "vim.visualModeKeybindings": { + "type": "object", + "description": "Keybinding overrides to use for visual mode." } } } diff --git a/src/mode/commands.ts b/src/mode/commands.ts new file mode 100644 index 00000000000..f626972ffec --- /dev/null +++ b/src/mode/commands.ts @@ -0,0 +1,155 @@ + +// An enum of all bindable actions in Vim +// TODO: Split off commands into normal, insert, and visual catagories +export enum Command { + // Enter insert mode + InsertAtCursor = 1, + InsertAtLineBegin, + InsertAfterCursor, + InsertAtLineEnd, + InsertNewLineBelow, + InsertNewLineAbove, + + // Movement + MoveUp, + MoveDown, + MoveLeft, + MoveRight, + + MoveLineBegin, + MoveLineEnd, + MoveWordBegin, + MoveWordEnd, + MoveFullWordBegin, + MoveFullWordEnd, + MoveLastWord, + MoveLastFullWord, + MoveLastWordEnd, + MoveLastFullWordEnd, + + // MoveHalfPageUp, + // MoveHalfPageDown, + MoveFullPageUp, + MoveFullPageDown, + // MoveFirstLine, + // MoveLastLine, + + MoveParagraphBegin, + MoveParagraphEnd, + + MoveNonBlank, + MoveNonBlankFirst, + MoveNonBlankLast, + MoveMatchingBracket, + + // Find + Find, + + // Text Modification + Undo, + Redo, + Copy, + Paste, + + ChangeWord, + ChangeFullWord, + ChangeCurrentWord, + ChangeCurrentWordToNext, + ChangeToLineEnd, + + DeleteLine, + DeleteToNextWord, + DeleteToFullNextWord, + DeleteToWordEnd, + DeleteToFullWordEnd, + DeleteToWordBegin, + DeleteToFullWordBegin, + DeleteToLineEnd, + + DeleteChar, + DeleteLastChar, + + Indent, + Outdent, + + // Misc + EnterVisualMode, + EnterCommand, + ExitMessages, +} + +export function newDefaultNormalKeymap() : {[key: string]: Command} { + return { + "h": Command.MoveLeft, + "j": Command.MoveDown, + "k": Command.MoveUp, + "l": Command.MoveRight, + "0": Command.MoveLineBegin, + "$": Command.MoveLineEnd, + + "^": Command.MoveNonBlank, + "gg": Command.MoveNonBlankFirst, + "G": Command.MoveNonBlankLast, + + "w": Command.MoveWordBegin, + "W": Command.MoveFullWordBegin, + "e": Command.MoveWordEnd, + "E": Command.MoveLastFullWordEnd, + "ge": Command.MoveLastWordEnd, + "gE": Command.MoveLastFullWordEnd, + "b": Command.MoveLastWord, + "B": Command.MoveLastFullWord, + + "{": Command.MoveParagraphBegin, + "}": Command.MoveParagraphEnd, + "%": Command.MoveMatchingBracket, + + ">>": Command.Indent, + "<<": Command.Outdent, + + "u": Command.Undo, + "ctrl+r": Command.Redo, + "y": Command.Copy, + "p": Command.Paste, + + "cw": Command.ChangeWord, + "cW": Command.ChangeFullWord, + "ciw": Command.ChangeCurrentWord, + "caw": Command.ChangeCurrentWordToNext, + "C": Command.ChangeToLineEnd, + + "dd": Command.DeleteLine, + "dw": Command.DeleteToNextWord, + "dW": Command.DeleteToFullNextWord, + "db": Command.DeleteToWordBegin, + "dB": Command.DeleteToFullWordBegin, + "de": Command.DeleteToWordEnd, + "dE": Command.DeleteToFullWordEnd, + "D" : Command.DeleteToLineEnd, + + "x": Command.DeleteChar, + "X": Command.DeleteLastChar, + + "/": Command.Find, + ":": Command.EnterCommand, + "v": Command.EnterVisualMode, + "esc": Command.ExitMessages + }; +} + +export function newDefaultInsertKeymap() : {[key: string]: Command} { + return { + "i": Command.InsertAtCursor, + "I": Command.InsertAtLineBegin, + "a": Command.InsertAfterCursor, + "A": Command.InsertAtLineEnd, + "o": Command.InsertNewLineBelow, + "O": Command.InsertNewLineAbove, + }; +} + +export function newDefaultVisualKeymap() : {[key: string]: Command} { + return { + "v": Command.EnterVisualMode + }; +} \ No newline at end of file diff --git a/src/mode/mode.ts b/src/mode/mode.ts index 79caa22278b..92660464679 100644 --- a/src/mode/mode.ts +++ b/src/mode/mode.ts @@ -1,5 +1,6 @@ "use strict"; +import {Command} from './commands'; import {Motion} from './../motion/motion'; import {Position} from './../motion/position'; @@ -14,12 +15,14 @@ export abstract class Mode { private _name : ModeName; private _motion : Motion; protected _keyHistory : string[]; + protected _keymap : {[key: string]: Command}; - constructor(name: ModeName, motion: Motion) { + constructor(name: ModeName, motion: Motion, keymap: {[key: string]: Command}) { this._name = name; this._motion = motion; this._isActive = false; this._keyHistory = []; + this._keymap = keymap; } get name(): ModeName { diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 8e4af24a70c..dbf43d0f4aa 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -3,6 +3,7 @@ import * as _ from 'lodash'; import * as vscode from 'vscode'; +import * as cmds from './commands'; import {Mode, ModeName} from './mode'; import {Motion, MotionMode} from './../motion/motion'; import {NormalMode} from './modeNormal'; @@ -19,11 +20,20 @@ export class ModeHandler implements vscode.Disposable { constructor() { this._configuration = Configuration.fromUserFile(); + // This probably should be somewhere else but will work for now. + // TODO: Only override default settings specified instead of all of them + let normalKeymap = vscode.workspace.getConfiguration("vim") + .get("normalModeKeybindings", cmds.newDefaultNormalKeymap()); + let insertKeymap = vscode.workspace.getConfiguration("vim") + .get("insertModeKeybindings", cmds.newDefaultInsertKeymap()); + let visualKeymap = vscode.workspace.getConfiguration("vim") + .get("visualModeKeybindings", cmds.newDefaultVisualKeymap()); + this._motion = new Motion(null); this._modes = [ - new NormalMode(this._motion, this), - new InsertMode(this._motion), - new VisualMode(this._motion, this), + new NormalMode(this._motion, this, normalKeymap), + new InsertMode(this._motion, insertKeymap), + new VisualMode(this._motion, this, visualKeymap), ]; this.setCurrentModeByName(ModeName.Normal); diff --git a/src/mode/modeInsert.ts b/src/mode/modeInsert.ts index e50f0832b11..87b9a322401 100644 --- a/src/mode/modeInsert.ts +++ b/src/mode/modeInsert.ts @@ -2,48 +2,46 @@ import * as vscode from 'vscode'; +import {Command} from './commands'; import {ModeName, Mode} from './mode'; import {TextEditor} from './../textEditor'; import {Motion} from './../motion/motion'; export class InsertMode extends Mode { - private activationKeyHandler : { [ key : string] : (motion : Motion) => Promise<{}> } = { - "i" : async (c) => { - // insert at cursor - return c.move(); - }, - "I" : async (c) => { - // insert at line beginning - return c.lineBegin().move(); - }, - "a" : async (c) => { - // append after the cursor - return c.right().move(); - }, - "A" : async (c) => { - // append at the end of the line - return c.lineEnd().move(); - }, - "o" : async () => { - // open blank line below current line - return await vscode.commands.executeCommand("editor.action.insertLineAfter"); - }, - "O" : async () => { - // open blank line above current line - return await vscode.commands.executeCommand("editor.action.insertLineBefore"); + protected handleActivationKey(command : Command) : (motion: Motion) => Promise<{}> { + switch ( command ) { + case Command.InsertAtCursor: + return async (c) => { return c.move(); }; + case Command.InsertAtLineBegin: + return async (c) => { return c.lineBegin().move(); }; + case Command.InsertAfterCursor: + return async (c) => { return c.right().move(); }; + case Command.InsertAtLineEnd: + return async (c) => { return c.lineEnd().move(); }; + case Command.InsertNewLineBelow: + return async () => { + return await vscode.commands.executeCommand("editor.action.insertLineAfter"); + }; + case Command.InsertNewLineAbove: + return async () => { + return await vscode.commands.executeCommand("editor.action.insertLineBefore"); + }; + default: + return async () => { return {}; }; } - }; + } - constructor(motion : Motion) { - super(ModeName.Insert, motion); + constructor(motion : Motion, keymap : {[key: string]: Command}) { + super(ModeName.Insert, motion, keymap); } shouldBeActivated(key : string, currentMode : ModeName) : boolean { - return key in this.activationKeyHandler && currentMode === ModeName.Normal; + return key in this._keymap && currentMode === ModeName.Normal; } async handleActivation(key : string): Promise { - await this.activationKeyHandler[key](this.motion); + let command : Command = this._keymap[key]; + await this.handleActivationKey(command)(this.motion); } async handleKeyEvent(key : string) : Promise { diff --git a/src/mode/modeNormal.ts b/src/mode/modeNormal.ts index 76adbb07268..7ce1011f09d 100644 --- a/src/mode/modeNormal.ts +++ b/src/mode/modeNormal.ts @@ -3,6 +3,7 @@ import * as _ from 'lodash'; import * as vscode from 'vscode'; +import {Command} from './commands'; import {ModeName, Mode} from './mode'; import {showCmdLine} from './../cmd_line/main'; import {Motion, MotionMode} from './../motion/motion'; @@ -13,146 +14,196 @@ import {PutOperator} from './../operator/put'; import {TextEditor} from './../textEditor'; export class NormalMode extends Mode { - protected keyHandler : { [key : string] : (motion : Motion) => Promise<{}>; } = { - ":" : async () => { return showCmdLine("", this._modeHandler); }, - "/" : async () => { return vscode.commands.executeCommand("actions.find"); }, - "u" : async () => { return vscode.commands.executeCommand("undo"); }, - "ctrl+r" : async () => { return vscode.commands.executeCommand("redo"); }, - "h" : async (c) => { return c.left().move(); }, - "j" : async (c) => { return c.down().move(); }, - "k" : async (c) => { return c.up().move(); }, - "l" : async (c) => { return c.right().move(); }, - "$" : async (c) => { return c.lineEnd().move(); }, - "0" : async (c) => { return c.lineBegin().move(); }, - "^" : async () => { return vscode.commands.executeCommand("cursorHome"); }, - "gg" : async (c) => { return c.firstLineNonBlankChar().move(); }, - "G" : async (c) => { return c.lastLineNonBlankChar().move(); }, - "w" : async (c) => { return c.wordRight().move(); }, - "W" : async (c) => { return c.bigWordRight().move(); }, - "e" : async (c) => { return c.goToEndOfCurrentWord().move(); }, - "E" : async (c) => { return c.goToEndOfCurrentBigWord().move(); }, - "ge" : async (c) => { return c.goToEndOfLastWord().move(); }, - "gE" : async (c) => { return c.goToEndOfLastWord().move(); }, - "b" : async (c) => { return c.wordLeft().move(); }, - "B" : async (c) => { return c.bigWordLeft().move(); }, - "}" : async (c) => { return c.goToEndOfCurrentParagraph().move(); }, - "{" : async (c) => { return c.goToBeginningOfCurrentParagraph().move(); }, - "ctrl+f": async (c) => { return vscode.commands.executeCommand("cursorPageDown"); }, - "ctrl+b": async (c) => { return vscode.commands.executeCommand("cursorPageUp"); }, - "%" : async () => { return vscode.commands.executeCommand("editor.action.jumpToBracket"); }, - ">>" : async () => { return vscode.commands.executeCommand("editor.action.indentLines"); }, - "<<" : async () => { return vscode.commands.executeCommand("editor.action.outdentLines"); }, - "cw" : async (m) => { - m.changeMode(MotionMode.Cursor); - let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; - if (currentChar === ' ' || currentChar === '\t') { - await new ChangeOperator(this._modeHandler).run(m.position, m.position.getWordRight()); - } else { - await new ChangeOperator(this._modeHandler).run(m.position, m.position.getCurrentWordEnd()); - } - return {}; - }, - "cW" : async (m) => { - m.changeMode(MotionMode.Cursor); - let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; - if (currentChar === ' ' || currentChar === '\t') { - await new ChangeOperator(this._modeHandler).run(m.position, m.position.getWordRight()); - } else { - await new ChangeOperator(this._modeHandler).run(m.position, m.position.getCurrentBigWordEnd()); - } - return {}; - }, - "ciw" : async (m) => { - m.changeMode(MotionMode.Cursor); - let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; - if (currentChar === ' ' || currentChar === '\t') { - await new ChangeOperator(this._modeHandler).run(m.position.getLastWordEnd(), m.position.getWordRight()); - } else { - await new ChangeOperator(this._modeHandler).run(m.position.getWordLeft(), m.position.getCurrentWordEnd()); - } - this._modeHandler.setCurrentModeByName(ModeName.Insert); - return {}; - }, - "caw" : async (m) => { - m.changeMode(MotionMode.Cursor); - let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; - if (currentChar === ' ' || currentChar === '\t') { - await new ChangeOperator(this._modeHandler).run(m.position.getLastWordEnd(), m.position.getCurrentWordEnd()); - } else { - await new ChangeOperator(this._modeHandler).run(m.position.getWordLeft(), m.position.getWordRight()); - } - return {}; - }, - "C" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new ChangeOperator(this._modeHandler).run(m.position, m.position.getLineEnd()); - return {}; - }, - "dd" : async () => { return vscode.commands.executeCommand("editor.action.deleteLines"); }, - "dw" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getWordRight()); + protected handleKey(command : Command) : (motion: Motion) => Promise<{}> { + switch ( command ) { + case Command.EnterCommand: + return async () => { return showCmdLine("", this._modeHandler); }; + case Command.Find: + return async () => { return vscode.commands.executeCommand("actions.find"); }; + case Command.Undo: + return async () => { return vscode.commands.executeCommand("undo"); }; + case Command.Redo : + return async () => { return vscode.commands.executeCommand("redo"); }; + case Command.MoveLeft: + return async (c) => { return c.left().move(); }; + case Command.MoveDown: + return async (c) => { return c.down().move(); }; + case Command.MoveUp: + return async (c) => { return c.up().move(); }; + case Command.MoveRight: + return async (c) => { return c.right().move(); }; + case Command.MoveLineEnd: + return async (c) => { return c.lineEnd().move(); }; + case Command.MoveLineBegin: + return async (c) => { return c.lineBegin().move(); }; + case Command.MoveNonBlank: + return async () => { return vscode.commands.executeCommand("cursorHome"); }; + case Command.MoveNonBlankFirst: + return async (c) => { return c.firstLineNonBlankChar().move(); }; + case Command.MoveNonBlankLast: + return async (c) => { return c.lastLineNonBlankChar().move(); }; + case Command.MoveWordBegin: + return async (c) => { return c.wordRight().move(); }; + case Command.MoveFullWordBegin: + return async (c) => { return c.bigWordRight().move(); }; + case Command.MoveWordEnd: + return async (c) => { return c.goToEndOfCurrentWord().move(); }; + case Command.MoveFullWordEnd: + return async (c) => { return c.goToEndOfCurrentBigWord().move(); }; + case Command.MoveLastWordEnd : + return async (c) => { return c.goToEndOfLastWord().move(); }; + case Command.MoveLastFullWordEnd: + return async (c) => { return c.goToEndOfLastWord().move(); }; + case Command.MoveLastWord: + return async (c) => { return c.wordLeft().move(); }; + case Command.MoveLastWordEnd: + return async (c) => { return c.bigWordLeft().move(); }; + case Command.MoveParagraphEnd: + return async (c) => { return c.goToEndOfCurrentParagraph().move(); }; + case Command.MoveParagraphBegin: + return async (c) => { return c.goToBeginningOfCurrentParagraph().move(); }; + case Command.MoveFullPageDown: + return async (c) => { return vscode.commands.executeCommand("cursorPageDown"); }; + case Command.MoveFullPageUp: + return async (c) => { return vscode.commands.executeCommand("cursorPageUp"); }; + case Command.MoveMatchingBracket: + return async () => { return vscode.commands.executeCommand("editor.action.jumpToBracket"); }; + case Command.Indent: + return async () => { return vscode.commands.executeCommand("editor.action.indentLines"); }; + case Command.Outdent: + return async () => { return vscode.commands.executeCommand("editor.action.outdentLines"); }; + case Command.ChangeWord: + return async (m) => { + m.changeMode(MotionMode.Cursor); + let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; + if (currentChar === ' ' || currentChar === '\t') { + await new ChangeOperator(this._modeHandler).run(m.position, m.position.getWordRight()); + } else { + await new ChangeOperator(this._modeHandler).run(m.position, m.position.getCurrentWordEnd()); + } + return {}; + }; + case Command.ChangeFullWord: + return async (m) => { + m.changeMode(MotionMode.Cursor); + let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; + if (currentChar === ' ' || currentChar === '\t') { + await new ChangeOperator(this._modeHandler).run(m.position, m.position.getWordRight()); + } else { + await new ChangeOperator(this._modeHandler).run(m.position, m.position.getCurrentBigWordEnd()); + } + return {}; + }; + case Command.ChangeCurrentWord: + return async (m) => { + m.changeMode(MotionMode.Cursor); + let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; + if (currentChar === ' ' || currentChar === '\t') { + await new ChangeOperator(this._modeHandler).run(m.position.getLastWordEnd(), m.position.getWordRight()); + } else { + await new ChangeOperator(this._modeHandler).run(m.position.getWordLeft(), m.position.getCurrentWordEnd()); + } + this._modeHandler.setCurrentModeByName(ModeName.Insert); + return {}; + }; + case Command.ChangeCurrentWordToNext: + return async (m) => { + m.changeMode(MotionMode.Cursor); + let currentChar = TextEditor.getLineAt(m.position).text[m.position.character]; + if (currentChar === ' ' || currentChar === '\t') { + await new ChangeOperator(this._modeHandler).run(m.position.getLastWordEnd(), m.position.getCurrentWordEnd()); + } else { + await new ChangeOperator(this._modeHandler).run(m.position.getWordLeft(), m.position.getWordRight()); + } + return {}; + }; + case Command.ChangeToLineEnd: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new ChangeOperator(this._modeHandler).run(m.position, m.position.getLineEnd()); + return {}; + }; + case Command.DeleteLine: + return async () => { return vscode.commands.executeCommand("editor.action.deleteLines"); }; + case Command.DeleteToNextWord: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getWordRight()); - if (m.position.character >= m.position.getLineEnd().character) { - m.left().move(); - } + if (m.position.character >= m.position.getLineEnd().character) { + m.left().move(); + } - return {}; - }, - "dW" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getBigWordRight()); - return {}; - }, - "db" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getWordLeft()); - return {}; - }, - "dB" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getBigWordLeft()); - return {}; - }, - "de" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getCurrentWordEnd()); - return {}; - }, - "dE" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getCurrentBigWordEnd()); - this.motion.left().move(); - return {}; - }, - "D" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getLineEnd()); - this.motion.left().move(); - return {}; - }, - "x" : async (m) => { - m.changeMode(MotionMode.Cursor); - await new DeleteOperator(this._modeHandler).run(m.position, m.position.getRight()); - return {}; - }, - "X" : async (m) => { return vscode.commands.executeCommand("deleteLeft"); }, - "p" : async (m) => { - await new PutOperator(this._modeHandler).run(m.position, null); - return {}; - }, - "esc": async () => { return vscode.commands.executeCommand("workbench.action.closeMessages"); } - }; + return {}; + }; + case Command.DeleteToFullNextWord : + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getBigWordRight()); + return {}; + }; + case Command.DeleteToWordBegin: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getWordLeft()); + return {}; + }; + case Command.DeleteToFullWordBegin: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getBigWordLeft()); + return {}; + }; + case Command.DeleteToWordEnd: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getCurrentWordEnd()); + return {}; + }; + case Command.DeleteToFullWordEnd: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getCurrentBigWordEnd()); + this.motion.left().move(); + return {}; + }; + case Command.DeleteToLineEnd: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getLineEnd()); + this.motion.left().move(); + return {}; + }; + case Command.DeleteChar: + return async (m) => { + m.changeMode(MotionMode.Cursor); + await new DeleteOperator(this._modeHandler).run(m.position, m.position.getRight()); + return {}; + }; + case Command.DeleteLastChar: + return async (m) => { return vscode.commands.executeCommand("deleteLeft"); }; + case Command.Paste: + return async (m) => { + await new PutOperator(this._modeHandler).run(m.position, null); + return {}; + }; + case Command.ExitMessages: + return async () => { return vscode.commands.executeCommand("workbench.action.closeMessages"); }; + default: + return async () => {return {}; }; + } + } private _modeHandler: ModeHandler; - constructor(motion : Motion, modeHandler: ModeHandler) { - super(ModeName.Normal, motion); + constructor(motion : Motion, modeHandler: ModeHandler, keymap: {[key: string]: Command}) { + super(ModeName.Normal, motion, keymap); this._modeHandler = modeHandler; } shouldBeActivated(key : string, currentMode : ModeName) : boolean { + // TODO: Have these keybinds configurable return (key === 'esc' || key === 'ctrl+[' || (key === "v" && currentMode === ModeName.Visual)); } @@ -165,10 +216,12 @@ export class NormalMode extends Mode { let keyHandled = false; let keysPressed: string; + let command: Command; for (let window = this._keyHistory.length; window > 0; window--) { keysPressed = _.takeRight(this._keyHistory, window).join(''); - if (this.keyHandler[keysPressed] !== undefined) { + command = this._keymap[keysPressed]; + if (command !== undefined) { keyHandled = true; break; } @@ -176,7 +229,7 @@ export class NormalMode extends Mode { if (keyHandled) { this._keyHistory = []; - await this.keyHandler[keysPressed](this.motion); + await this.handleKey(command)(this.motion); } return keyHandled; diff --git a/src/mode/modeVisual.ts b/src/mode/modeVisual.ts index 6a8632d10c4..b349baf3884 100644 --- a/src/mode/modeVisual.ts +++ b/src/mode/modeVisual.ts @@ -2,6 +2,7 @@ import * as _ from 'lodash'; +import {Command} from './commands'; import { ModeName, Mode } from './mode'; import { Motion} from './../motion/motion'; import { Position } from './../motion/position'; @@ -24,8 +25,8 @@ export class VisualMode extends Mode { private _keysToOperators: { [key: string]: Operator }; - constructor(motion: Motion, modeHandler: ModeHandler) { - super(ModeName.Visual, motion); + constructor(motion: Motion, modeHandler: ModeHandler, keymap: {[key: string]: Command}) { + super(ModeName.Visual, motion, keymap); this._modeHandler = modeHandler; this._keysToOperators = { @@ -40,7 +41,8 @@ export class VisualMode extends Mode { } shouldBeActivated(key: string, currentMode: ModeName): boolean { - return key === "v" && currentMode === ModeName.Normal; + let command : Command = this._keymap[key]; + return command === Command.EnterVisualMode && currentMode === ModeName.Normal; } async handleActivation(key: string): Promise { diff --git a/test/mode/modeInsert.test.ts b/test/mode/modeInsert.test.ts index 750a1a0f5e5..47a62d04caf 100644 --- a/test/mode/modeInsert.test.ts +++ b/test/mode/modeInsert.test.ts @@ -1,6 +1,7 @@ "use strict"; import * as assert from 'assert'; +import {newDefaultInsertKeymap} from '../../src/mode/commands'; import {setupWorkspace, cleanUpWorkspace, assertEqualLines} from './../testUtils'; import {InsertMode} from '../../src/mode/modeInsert'; import {ModeName} from '../../src/mode/mode'; @@ -16,7 +17,7 @@ suite("Mode Insert", () => { await setupWorkspace(); motion = new Motion(MotionMode.Cursor); - modeInsert = new InsertMode(motion); + modeInsert = new InsertMode(motion, newDefaultInsertKeymap()); }); teardown(cleanUpWorkspace); diff --git a/test/mode/modeNormal.test.ts b/test/mode/modeNormal.test.ts index 07bc8653de9..7411dc297cf 100644 --- a/test/mode/modeNormal.test.ts +++ b/test/mode/modeNormal.test.ts @@ -1,6 +1,7 @@ "use strict"; import * as assert from 'assert'; +import {newDefaultNormalKeymap} from '../../src/mode/commands'; import {setupWorkspace, cleanUpWorkspace, assertEqualLines} from './../testUtils'; import {NormalMode} from '../../src/mode/modeNormal'; import {ModeName} from '../../src/mode/mode'; @@ -19,7 +20,7 @@ suite("Mode Normal", () => { modeHandler = new ModeHandler(); motion = new Motion(MotionMode.Cursor); - modeNormal = new NormalMode(motion, modeHandler); + modeNormal = new NormalMode(motion, modeHandler, newDefaultNormalKeymap()); }); teardown(cleanUpWorkspace); diff --git a/test/mode/modeVisual.test.ts b/test/mode/modeVisual.test.ts index 7c53a1d515e..753a8870d79 100644 --- a/test/mode/modeVisual.test.ts +++ b/test/mode/modeVisual.test.ts @@ -1,6 +1,7 @@ "use strict"; import * as assert from 'assert'; +import {newDefaultVisualKeymap} from '../../src/mode/commands'; import {ModeHandler} from '../../src/mode/modeHandler'; import {setupWorkspace, cleanUpWorkspace, assertEqualLines} from './../testUtils'; import {VisualMode} from '../../src/mode/modeVisual'; @@ -18,7 +19,7 @@ suite("Mode Visual", () => { modeHandler = new ModeHandler(); motion = new Motion(MotionMode.Cursor); - visualMode = new VisualMode(motion, modeHandler); + visualMode = new VisualMode(motion, modeHandler, newDefaultVisualKeymap()); }); teardown(cleanUpWorkspace);