diff --git a/spec/atom/global-keymap-spec.coffee b/spec/atom/global-keymap-spec.coffee index 286a5d503..371119b8a 100644 --- a/spec/atom/global-keymap-spec.coffee +++ b/spec/atom/global-keymap-spec.coffee @@ -119,53 +119,66 @@ describe "GlobalKeymap", -> keymap.handleKeyEvent(keypressEvent('y', target: target)) expect(bazHandler).toHaveBeenCalled() - describe ".bindAllKeys(fn)", -> - it "calls given fn when selector matches", -> - handler = jasmine.createSpy 'handler' - keymap.bindKeys '.child-node', handler + describe ".bindKeys(selector, fnOrMap)", -> + describe "when called with a function", -> + it "calls the given function when selector matches", -> + handler = jasmine.createSpy 'handler' + keymap.bindKeys '.child-node', handler - target = fragment.find('.grandchild-node')[0] - event = keypressEvent('y', target: target) - keymap.handleKeyEvent event + target = fragment.find('.grandchild-node')[0] + event = keypressEvent('y', target: target) + keymap.handleKeyEvent event - expect(handler).toHaveBeenCalledWith(event) + expect(handler).toHaveBeenCalledWith(event) - describe "when the handler function returns a command string", -> - it "triggers the command event on the target and stops propagating the event", -> - keymap.bindKeys '*', 'x': 'foo' - keymap.bindKeys '*', -> 'bar' - fooHandler = jasmine.createSpy('fooHandler') - barHandler = jasmine.createSpy('barHandler') - fragment.on 'foo', fooHandler - fragment.on 'bar', barHandler + describe "when the function returns a command string", -> + it "triggers the command event on the target and stops propagating the event", -> + keymap.bindKeys '*', 'x': 'foo' + keymap.bindKeys '*', -> 'bar' + fooHandler = jasmine.createSpy('fooHandler') + barHandler = jasmine.createSpy('barHandler') + fragment.on 'foo', fooHandler + fragment.on 'bar', barHandler - target = fragment.find('.child-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) + target = fragment.find('.child-node')[0] + keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(fooHandler).not.toHaveBeenCalled() - expect(barHandler).toHaveBeenCalled() + expect(fooHandler).not.toHaveBeenCalled() + expect(barHandler).toHaveBeenCalled() - describe "when the handler function returns false", -> - it "stops propagating the event", -> - keymap.bindKeys '*', 'x': 'foo' - keymap.bindKeys '*', -> false - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler + describe "when the function returns false", -> + it "stops propagating the event", -> + keymap.bindKeys '*', 'x': 'foo' + keymap.bindKeys '*', -> false + fooHandler = jasmine.createSpy('fooHandler') + fragment.on 'foo', fooHandler - target = fragment.find('.child-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) + target = fragment.find('.child-node')[0] + keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(fooHandler).not.toHaveBeenCalled() + expect(fooHandler).not.toHaveBeenCalled() - describe "when the handler function returns anything other than a string or false", -> - it "continues to propagate the event", -> - keymap.bindKeys '*', 'x': 'foo' - keymap.bindKeys '*', -> undefined - fooHandler = jasmine.createSpy('fooHandler') - fragment.on 'foo', fooHandler + describe "when the function returns anything other than a string or false", -> + it "continues to propagate the event", -> + keymap.bindKeys '*', 'x': 'foo' + keymap.bindKeys '*', -> undefined + fooHandler = jasmine.createSpy('fooHandler') + fragment.on 'foo', fooHandler - target = fragment.find('.child-node')[0] - keymap.handleKeyEvent(keydownEvent('x', target: target)) + target = fragment.find('.child-node')[0] + keymap.handleKeyEvent(keydownEvent('x', target: target)) - expect(fooHandler).toHaveBeenCalled() + expect(fooHandler).toHaveBeenCalled() + describe ".bindKey(selector, pattern, eventName)", -> + it "binds a single key", -> + keymap.bindKey '.child-node', 'z', 'foo' + + fooHandler = jasmine.createSpy('fooHandler') + fragment.on 'foo', fooHandler + + target = fragment.find('.child-node')[0] + keymap.handleKeyEvent(keydownEvent('z', target: target)) + + expect(fooHandler).toHaveBeenCalled() + diff --git a/spec/atom/vim-mode-spec.coffee b/spec/atom/vim-mode-spec.coffee index c6c581410..0c63759ce 100644 --- a/spec/atom/vim-mode-spec.coffee +++ b/spec/atom/vim-mode-spec.coffee @@ -39,6 +39,25 @@ describe "VimMode", -> expect(editor.buffer.getText()).toBe '1345' expect(editor.getCursor()).toEqual(column: 1, row: 0) + describe "basic motion bindings", -> + beforeEach -> + editor.buffer.setText("12345\nabcde\nABCDE") + editor.setCursor(column: 1, row: 1) + + describe "the h keybinding", -> + it "move the cursor left", -> + editor.trigger keydownEvent('h') + expect(editor.getCursor()).toEqual(column: 0, row: 1) + editor.trigger keydownEvent('h') + expect(editor.getCursor()).toEqual(column: 0, row: 1) + + describe "the j keybinding", -> + it "move the cursor up", -> + editor.trigger keydownEvent('j') + expect(editor.getCursor()).toEqual(column: 1, row: 0) + editor.trigger keydownEvent('j') + expect(editor.getCursor()).toEqual(column: 1, row: 0) + describe "numeric prefix binding", -> it "repeats the following operation N times", -> editor.buffer.setText("12345") diff --git a/src/atom/app.coffee b/src/atom/app.coffee index 03a88b447..0e4ff35f4 100644 --- a/src/atom/app.coffee +++ b/src/atom/app.coffee @@ -15,6 +15,9 @@ class App bindKeys: (selector, bindings) -> @globalKeymap.bindKeys(selector, bindings) + bindKey: (selector, pattern, eventName) -> + @globalKeymap.bindKey(selector, pattern, eventName) + open: (url) -> OSX.NSApp.open url diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 52795861d..0e300e146 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -64,5 +64,8 @@ class Editor extends Template deleteChar: -> @aceEditor.remove 'right' + moveLeft: -> + @aceEditor.navigateLeft() - + moveUp: -> + @aceEditor.navigateUp() diff --git a/src/atom/global-keymap.coffee b/src/atom/global-keymap.coffee index 4dfa5d68b..b5f00891f 100644 --- a/src/atom/global-keymap.coffee +++ b/src/atom/global-keymap.coffee @@ -12,6 +12,11 @@ class GlobalKeymap bindKeys: (selector, bindings) -> @bindingSets.unshift(new BindingSet(selector, bindings)) + bindKey: (selector, pattern, eventName) -> + bindings = {} + bindings[pattern] = eventName + @bindKeys(selector, bindings) + handleKeyEvent: (event) -> currentNode = $(event.target) while currentNode.length diff --git a/src/atom/vim-mode-operators.coffee b/src/atom/vim-mode-operators.coffee index 289819395..63d335d2a 100644 --- a/src/atom/vim-mode-operators.coffee +++ b/src/atom/vim-mode-operators.coffee @@ -23,3 +23,17 @@ module.exports = isComplete: -> true + MoveLeft: class + execute: (editor) -> + {column, row} = editor.getCursor() + editor.moveLeft() if column > 0 + + isComplete: -> true + + MoveUp: class + execute: (editor) -> + {column, row} = editor.getCursor() + editor.moveUp() if row > 0 + + isComplete: -> true + diff --git a/src/atom/vim-mode.coffee b/src/atom/vim-mode.coffee index c2b38ddab..5b0dc673c 100644 --- a/src/atom/vim-mode.coffee +++ b/src/atom/vim-mode.coffee @@ -1,6 +1,6 @@ _ = require 'underscore' $ = require 'jquery' -{ NumericPrefix, DeleteChar } = require 'vim-mode-operators' +op = require 'vim-mode-operators' module.exports = class VimMode @@ -9,16 +9,52 @@ class VimMode constructor: (@editor) -> @opStack = [] - atom.bindKeys '.command-mode', -> false - atom.bindKeys '.command-mode', @commandModeBindings() - atom.bindKeys '.insert-mode', '': 'command-mode:activate' - @editor.addClass('command-mode') - @editor.on 'insert-mode:activate', => @activateInsertMode() - @editor.on 'command-mode:activate', => @activateCommandMode() - @editor.on 'command-mode:delete-char', => @deleteChar() - @editor.on 'command-mode:numeric-prefix', (e) => @numericPrefix(e) + atom.bindKeys '.editor', '': 'activate-command-mode' + @editor.on 'activate-command-mode', => @activateCommandMode() + + @setupCommandMode() + + setupCommandMode: -> + atom.bindKeys '.command-mode', -> false + + @bindCommandModeKeys + 'i': 'insert' + 'x': 'delete-char' + 'h': 'move-left' + 'j': 'move-up' + + @handleCommands + 'insert': => @activateInsertMode() + 'delete-char': => new op.DeleteChar + 'move-left': => new op.MoveLeft + 'move-up': => new op.MoveUp + + for i in [0..9] + do (i) => + @registerCommand i, "numeric-prefix-#{i}", => new op.NumericPrefix(i) + + bindCommandModeKeys: (bindings) -> + prefixedBindings = {} + for pattern, commandName of bindings + prefixedBindings[pattern] = "command-mode:#{commandName}" + + atom.bindKeys ".command-mode", prefixedBindings + + handleCommands: (commands) -> + _.each commands, (fn, commandName) => + eventName = "command-mode:#{commandName}" + @editor.on eventName, => + possibleOperator = fn() + @pushOperator(possibleOperator) if possibleOperator.execute? + + registerCommand: (binding, commandName, fn)-> + eventName = "command-mode:#{commandName}" + atom.bindKey '.command-mode', binding, eventName + @editor.on eventName, => + possibleOperator = fn() + @pushOperator(possibleOperator) if possibleOperator.execute? activateInsertMode: -> @editor.removeClass('command-mode') @@ -28,20 +64,6 @@ class VimMode @editor.removeClass('insert-mode') @editor.addClass('command-mode') - deleteChar: -> - @pushOperator(new DeleteChar) - - numericPrefix: (e) -> - @pushOperator(new NumericPrefix(e.keyEvent.char)) - - commandModeBindings: -> - bindings = - 'i': 'insert-mode:activate' - 'x': 'command-mode:delete-char' - for i in [0..9] - bindings[i] = 'command-mode:numeric-prefix' - bindings - pushOperator: (op) -> @opStack.push(op) @processOpStack()