diff --git a/vim-sample/package.json b/vim-sample/package.json index ca0f3fe2..eb7eaa82 100644 --- a/vim-sample/package.json +++ b/vim-sample/package.json @@ -28,6 +28,30 @@ "key": "ctrl+r", "mac": "cmd+r", "when": "editorTextFocus" + },{ + "command": "e", + "key": "ctrl+e", + "when": "editorTextFocus && vim.inNormalMode" + },{ + "command": "d", + "key": "ctrl+d", + "when": "editorTextFocus && vim.inNormalMode" + },{ + "command": "f", + "key": "ctrl+f", + "when": "editorTextFocus && vim.inNormalMode" + },{ + "command": "y", + "key": "ctrl+y", + "when": "editorTextFocus && vim.inNormalMode" + },{ + "command": "u", + "key": "ctrl+u", + "when": "editorTextFocus && vim.inNormalMode" + },{ + "command": "b", + "key": "ctrl+b", + "when": "editorTextFocus && vim.inNormalMode" }] }, "scripts": { diff --git a/vim-sample/src/common.ts b/vim-sample/src/common.ts index f19665f8..b8113bb9 100644 --- a/vim-sample/src/common.ts +++ b/vim-sample/src/common.ts @@ -13,6 +13,12 @@ export enum Mode { REPLACE } +export interface ModifierKeys { + ctrl?: boolean, + alt?: boolean, + shifit?: boolean +} + export class DeleteRegister { public isWholeLine:boolean; public content:string; diff --git a/vim-sample/src/controller.ts b/vim-sample/src/controller.ts index cd72b9fa..2f29e05e 100644 --- a/vim-sample/src/controller.ts +++ b/vim-sample/src/controller.ts @@ -16,7 +16,7 @@ import { import {Words} from './words'; import {MotionState, Motion} from './motions'; -import {Mode, IController, DeleteRegister, Command} from './common'; +import {Mode, IController, DeleteRegister, Command, ModifierKeys} from './common'; import {Mappings} from './mappings'; export interface ITypeResult { @@ -139,7 +139,7 @@ export class Controller implements IController { return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``); } - public type(editor: TextEditor, text: string): Thenable { + public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable { if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) { return Promise.resolve({ hasConsumedInput: false, @@ -160,7 +160,7 @@ export class Controller implements IController { }); } this._currentInput += text; - return this._interpretNormalModeInput(editor); + return this._interpretNormalModeInput(editor, modifierKeys); } public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean { @@ -179,19 +179,19 @@ export class Controller implements IController { return true; } - private _interpretNormalModeInput(editor: TextEditor): Thenable { + private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable { if (this._currentInput.startsWith(':')) { return vscode.window.showInputBox({value: 'tabm'}).then((value) => { - let result = this._findMapping(value || '', editor); + let result = this._findMapping(value || '', editor, modifierKeys); return Promise.resolve(result); }); } - let result = this._findMapping(this._currentInput, editor); + let result = this._findMapping(this._currentInput, editor, modifierKeys); return Promise.resolve(result); } - private _findMapping(input: string, editor: TextEditor): ITypeResult { - let command = Mappings.findCommand(input); + private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult { + let command = Mappings.findCommand(input, modifierKeys); if (command) { this._currentInput = ''; return { @@ -200,7 +200,7 @@ export class Controller implements IController { }; } - let operator = Mappings.findOperator(input); + let operator = Mappings.findOperator(input, modifierKeys); if (operator) { if (this._isVisual) { if (operator.runVisual(this, editor)) { @@ -218,7 +218,7 @@ export class Controller implements IController { }; } - let motionCommand = Mappings.findMotionCommand(input, this._isVisual); + let motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys); if (motionCommand) { this._currentInput = ''; return { diff --git a/vim-sample/src/extension.ts b/vim-sample/src/extension.ts index f623eaa8..bac56b66 100644 --- a/vim-sample/src/extension.ts +++ b/vim-sample/src/extension.ts @@ -9,33 +9,42 @@ import * as vscode from 'vscode'; import {Words} from './words'; import {MotionState, Motion, Motions} from './motions'; import {Operator, Operators} from './operators'; -import {Mode, IController} from './common'; +import {Mode, IController, ModifierKeys} from './common'; import {Mappings} from './mappings'; import {Controller} from './controller'; export function activate(context: vscode.ExtensionContext) { - function registerCommandNice(commandId:string, run:(...args:any[])=>void): void { + function registerCommandNice(commandId: string, run: (...args: any[]) => void): void { context.subscriptions.push(vscode.commands.registerCommand(commandId, run)); } + function registerCtrlKeyBinding(key: string): void { + registerCommandNice(key, function (args) { + if (!vscode.window.activeTextEditor) { + return; + } + vimExt.type(key, {ctrl: true}); + }); + } let vimExt = new VimExt(); - registerCommandNice('type', function(args) { + registerCommandNice('type', function (args) { if (!vscode.window.activeTextEditor) { return; } vimExt.type(args.text); }); - registerCommandNice('replacePreviousChar', function(args) { + registerCommandNice('replacePreviousChar', function (args) { if (!vscode.window.activeTextEditor) { return; } vimExt.replacePrevChar(args.text, args.replaceCharCnt); }); - registerCommandNice('vim.goToNormalMode', function(args) { + registerCommandNice('vim.goToNormalMode', function (args) { vimExt.goToNormalMode(); }); - registerCommandNice('vim.clearInput', function(args) { + registerCommandNice('vim.clearInput', function (args) { + console.log(args); vimExt.clearInput(); }); // registerCommandNice('paste', function(args) { @@ -44,6 +53,13 @@ export function activate(context: vscode.ExtensionContext) { // registerCommandNice('cut', function(args) { // console.log('cut (no args)'); // }); + + registerCtrlKeyBinding('e'); + registerCtrlKeyBinding('d'); + registerCtrlKeyBinding('f'); + registerCtrlKeyBinding('y'); + registerCtrlKeyBinding('u'); + registerCtrlKeyBinding('b'); } export function deactivate() { @@ -126,8 +142,8 @@ class VimExt { this._ensureState(); } - public type(text: string): void { - let r = this._controller.type(vscode.window.activeTextEditor, text).then( + public type(text: string, modifierKeys: ModifierKeys = {ctrl: false, shifit: false, alt: false}): void { + let r = this._controller.type(vscode.window.activeTextEditor, text, modifierKeys).then( (r) => { if (r.hasConsumedInput) { this._ensureState(); @@ -197,11 +213,11 @@ class ContextKey { private _name: string; private _lastValue: boolean; - constructor(name:string) { + constructor(name: string) { this._name = name; } - public set(value:boolean): void { + public set(value: boolean): void { if (this._lastValue === value) { return; } @@ -219,7 +235,7 @@ class StatusBar { this._actual.show(); } - public setText(text:string): void { + public setText(text: string): void { if (this._lastText === text) { return; } diff --git a/vim-sample/src/mappings.ts b/vim-sample/src/mappings.ts index 99bd89f3..d2045fc7 100644 --- a/vim-sample/src/mappings.ts +++ b/vim-sample/src/mappings.ts @@ -7,18 +7,61 @@ import {TextEditor} from 'vscode'; import {Motion, Motions} from './motions'; import {Operator, Operators} from './operators'; -import {IController, Command, AbstractCommandDescriptor} from './common'; +import {IController, Command, AbstractCommandDescriptor, ModifierKeys} from './common'; - -const CHAR_TO_MOTION: { [char: string]: Motion; } = {}; -function defineMotion(char: string, motion: Motion): void { - CHAR_TO_MOTION[char] = motion; +const CHAR_TO_BINDING: { [char: string]: any; } = {}; +function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void { + let key = modifierKeys.ctrl ? 'CTRL + ' + char : char; + CHAR_TO_BINDING[key] = value; +}; +function getBinding(char: string, modifierKeys: ModifierKeys): any { + let key = modifierKeys.ctrl ? 'CTRL + ' + char : char; + return CHAR_TO_BINDING[key]; }; -const CHAR_TO_MOTION_COMMAND: { [char: string]: AbstractCommandDescriptor; } = {}; -function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor): void { - CHAR_TO_MOTION_COMMAND[char] = motionCommand; +function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void { + defineBinding(char + '__operator__', operator, modifierKeys); }; +function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator { + return getBinding(char + '__operator__', modifierKeys); +}; + +function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void { + defineBinding(char + '__command__', {commandId : commandId}, modifierKeys); +}; +function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command { + return getBinding(char + '__command__', modifierKeys); +}; + +function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void { + defineBinding(char + '__motion__', motion, modifierKeys); +}; +function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion { + return getBinding(char + '__motion__', modifierKeys); +}; + +function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void { + defineBinding(char + '__motioncommand__', motionCommand, modifierKeys); +}; +function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor { + return getBinding(char + '__motioncommand__', modifierKeys); +}; + +// Operators +defineOperator('x', Operators.DeleteCharUnderCursor); +defineOperator('i', Operators.Insert); +defineOperator('a', Operators.Append); +defineOperator('A', Operators.AppendEndOfLine); +defineOperator('d', Operators.DeleteTo); +defineOperator('p', Operators.Put); +defineOperator('r', Operators.Replace); +defineOperator('R', Operators.ReplaceMode); +defineOperator('c', Operators.Change); +defineOperator('v', Operators.Visual); + +// Commands +defineCommand('u', 'undo'); +defineCommand('U', 'undo'); // Left-right motions defineMotionCommand('h', Motions.Left); @@ -31,11 +74,11 @@ defineMotionCommand('gm', Motions.WrappedLineColumnCenter); defineMotionCommand('g$', Motions.WrappedLineEnd); defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter); -// Scroll motions -defineMotionCommand('zh', Motions.ScrollLeft); -defineMotionCommand('zl', Motions.ScrollRight); -defineMotionCommand('zH', Motions.ScrollLeftByHalfLine); -defineMotionCommand('zL', Motions.ScrollRightByHalfLine); +// Cursor scroll motions +defineMotionCommand('zh', Motions.CursorScrollLeft); +defineMotionCommand('zl', Motions.CursorScrollRight); +defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine); +defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine); // Up-down motions defineMotionCommand('j', Motions.Down); @@ -61,27 +104,13 @@ defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst); defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast); defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter); -const CHAR_TO_OPERATOR: { [char: string]: Operator; } = {}; -function defineOperator(char: string, operator: Operator): void { - CHAR_TO_OPERATOR[char] = operator; -}; -defineOperator('x', Operators.DeleteCharUnderCursor); -defineOperator('i', Operators.Insert); -defineOperator('a', Operators.Append); -defineOperator('A', Operators.AppendEndOfLine); -defineOperator('d', Operators.DeleteTo); -defineOperator('p', Operators.Put); -defineOperator('r', Operators.Replace); -defineOperator('R', Operators.ReplaceMode); -defineOperator('c', Operators.Change); -defineOperator('v', Operators.Visual); - -const CHAR_TO_COMMAND: { [char: string]: Command; } = {}; -function defineCommand(char: string, commandId: string, args?: any): void { - CHAR_TO_COMMAND[char] = {commandId : commandId, args: args}; -}; -defineCommand('u', 'undo'); -defineCommand('U', 'undo'); +// Scroll motions +defineMotionCommand('e', Motions.ScrollDownByLine, {ctrl: true}); +defineMotionCommand('d', Motions.ScrollDownByHalfPage, {ctrl: true}); +defineMotionCommand('f', Motions.ScrollDownByPage, {ctrl: true}); +defineMotionCommand('y', Motions.ScrollUpByLine, {ctrl: true}); +defineMotionCommand('u', Motions.ScrollUpByHalfPage, {ctrl: true}); +defineMotionCommand('b', Motions.ScrollUpByPage, {ctrl: true}); export interface IFoundOperator { runNormal(controller: IController, editor:TextEditor): boolean; @@ -92,9 +121,9 @@ export class Mappings { public static findMotion(input: string): Motion { let parsed = _parseNumberAndString(input); - let motion = CHAR_TO_MOTION[parsed.input.substr(0, 1)]; + let motion = getMotion(parsed.input.substr(0, 1)); if (!motion) { - motion = CHAR_TO_MOTION[parsed.input.substr(0, 2)]; + motion = getMotion(parsed.input.substr(0, 2)); if (!motion) { return null; } @@ -102,36 +131,36 @@ export class Mappings { return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount); } - public static findMotionCommand(input: string, isVisual: boolean = false): Command { + public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command { let parsed = _parseNumberAndString(input); - let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual); + let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys); if (!command) { parsed = _parseNumberAndString(input, false); - command= Mappings.findMotionCommandFromNumberAndString(parsed, isVisual); + command= Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys); } return command; } - private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean): Command { - let motionCommand = CHAR_TO_MOTION_COMMAND[numberAndString.input.substr(0, 1)]; + private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command { + let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys); if (!motionCommand) { - motionCommand = CHAR_TO_MOTION_COMMAND[numberAndString.input.substr(0, 2)]; + motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys); } if (!motionCommand) { - motionCommand = CHAR_TO_MOTION_COMMAND[numberAndString.input.substr(1, 2)]; + motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys); } if (!motionCommand) { - motionCommand = CHAR_TO_MOTION_COMMAND[numberAndString.input.substr(1, 3)]; + motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys); } if (!motionCommand) { - motionCommand = CHAR_TO_MOTION_COMMAND[numberAndString.input]; + motionCommand = getMotionCommand(numberAndString.input, modifierKeys); } return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined}) : null; } - public static findOperator(input: string): IFoundOperator { + public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator { let parsed = _parseNumberAndString(input); - let operator = CHAR_TO_OPERATOR[parsed.input.substr(0, 1)]; + let operator = getOperator(parsed.input.substr(0, 1), modifierKeys); if (!operator) { return null; } @@ -146,8 +175,8 @@ export class Mappings { }; } - public static findCommand(input: string): Command { - return CHAR_TO_COMMAND[input] || null; + public static findCommand(input: string, modifierKeys: ModifierKeys): Command { + return getCommand(input, modifierKeys) || null; } public static isMotionPrefix(input: string): boolean { diff --git a/vim-sample/src/motions.ts b/vim-sample/src/motions.ts index 5a2ae558..a328e4af 100644 --- a/vim-sample/src/motions.ts +++ b/vim-sample/src/motions.ts @@ -264,6 +264,25 @@ class CursorMoveCommand extends AbstractCommandDescriptor { } } +class EditorScrollCommand extends AbstractCommandDescriptor { + + constructor(private to: string, private by?: string) { + super(); + } + + public createCommand(args?: any): Command { + let editorScrollArgs: any = { + to: this.to, + by: this.by, + value: args.repeat || 1, + } + return { + commandId: 'editorScroll', + args: editorScrollArgs + }; + } +} + class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor { constructor() { @@ -318,10 +337,10 @@ export const Motions = { GoToFirstLine: new GoToFirstLineMotion(), GoToLastLine: new GoToLastLineMotion(), - ScrollLeft: new CursorMoveCommand('left'), - ScrollRight: new CursorMoveCommand('right'), - ScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'), - ScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'), + CursorScrollLeft: new CursorMoveCommand('left'), + CursorScrollRight: new CursorMoveCommand('right'), + CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'), + CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'), WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'), WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'), @@ -341,5 +360,12 @@ export const Motions = { MoveActiveEditorRight: new MoveActiveEditorCommand('right'), MoveActiveEditorFirst: new MoveActiveEditorCommand('first'), MoveActiveEditorLast: new MoveActiveEditorCommand('last'), - MoveActiveEditorCenter: new MoveActiveEditorCommand('center') + MoveActiveEditorCenter: new MoveActiveEditorCommand('center'), + + ScrollDownByLine: new EditorScrollCommand('down', 'line'), + ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'), + ScrollDownByPage: new EditorScrollCommand('down', 'page'), + ScrollUpByLine: new EditorScrollCommand('up', 'line'), + ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'), + ScrollUpByPage: new EditorScrollCommand('up', 'page') };