From 6d39306fb25c866d194ea96389d6ead221925dec Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Tue, 7 Feb 2012 18:01:35 -0800 Subject: [PATCH] When trying to compose operators that don't compose, the op stack is cleared --- spec/atom/vim-mode-spec.coffee | 21 ++++++++++++++++- src/atom/vim-mode.coffee | 38 +++++++++++++++++++----------- src/atom/vim-mode/motions.coffee | 2 +- src/atom/vim-mode/operators.coffee | 9 ++++++- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/spec/atom/vim-mode-spec.coffee b/spec/atom/vim-mode-spec.coffee index 295612ed5..d926ef776 100644 --- a/spec/atom/vim-mode-spec.coffee +++ b/spec/atom/vim-mode-spec.coffee @@ -2,7 +2,7 @@ Editor = require 'editor' VimMode = require 'vim-mode' describe "VimMode", -> - editor = null + [editor, vimMode] = [] beforeEach -> editor = new Editor @@ -36,6 +36,25 @@ describe "VimMode", -> editor.setCursorPosition([1, 0]) expect(editor.getCursorPosition()).toEqual [1,0] + it "clears the operator stack when commands can't be composed", -> + editor.trigger keydownEvent('d') + expect(vimMode.opStack.length).toBe 1 + editor.trigger keydownEvent('x') + expect(vimMode.opStack.length).toBe 0 + + editor.trigger keydownEvent('d') + expect(vimMode.opStack.length).toBe 1 + editor.trigger keydownEvent('\\') # \ is an unused key in vim + expect(vimMode.opStack.length).toBe 0 + + describe "the esc keybinding", -> + it "clears the operator stack", -> + editor.trigger keydownEvent('d') + expect(vimMode.opStack.length).toBe 1 + + editor.trigger keydownEvent('esc') + expect(vimMode.opStack.length).toBe 0 + describe "the i keybinding", -> it "puts the editor into insert mode", -> expect(editor).not.toHaveClass 'insert-mode' diff --git a/src/atom/vim-mode.coffee b/src/atom/vim-mode.coffee index 72d2391bb..f0b44ecea 100644 --- a/src/atom/vim-mode.coffee +++ b/src/atom/vim-mode.coffee @@ -21,24 +21,26 @@ class VimMode @setupCommandMode() setupCommandMode: -> - atom.bindKeys '.command-mode', (e) -> + atom.bindKeys '.command-mode', (e) => if e.keystroke.match /^\d$/ return 'command-mode:numeric-prefix' if e.keystroke.match /^.$/ + @resetCommandMode() return false @bindCommandModeKeys - 'i': 'insert' - 'd': 'delete' - 'x': 'delete-right' - 'h': 'move-left' - 'j': 'move-down' - 'k': 'move-up' - 'l': 'move-right' - 'w': 'move-to-next-word' - 'b': 'move-to-previous-word' - 'left': 'move-left' - 'right': 'move-right' + i: 'insert' + d: 'delete' + x: 'delete-right' + h: 'move-left' + j: 'move-down' + k: 'move-up' + l: 'move-right' + w: 'move-to-next-word' + b: 'move-to-previous-word' + esc: 'reset-command-mode' + left: 'move-left' + right: 'move-right' @handleCommands 'insert': => @activateInsertMode() @@ -51,6 +53,7 @@ class VimMode 'move-to-next-word': => new motions.MoveToNextWord(@editor) 'move-to-previous-word': => new motions.MoveToPreviousWord(@editor) 'numeric-prefix': (e) => @numericPrefix(e) + 'reset-command-mode': => @resetCommandMode() bindCommandModeKeys: (bindings) -> prefixedBindings = {} @@ -78,6 +81,9 @@ class VimMode @editor.on 'cursor:position-changed', @moveCursorBeforeNewline + resetCommandMode: -> + @opStack = [] + moveCursorBeforeNewline: => if not @editor.selection.modifyingSelection and @editor.cursor.isOnEOL() and @editor.getCurrentLine().length > 0 @editor.setCursorColumn(@editor.getCurrentLine().length - 1) @@ -107,10 +113,14 @@ class VimMode processOpStack: -> return unless @topOperator().isComplete() + poppedOperator = @opStack.pop() if @opStack.length - @topOperator().compose(poppedOperator) - @processOpStack() + try + @topOperator().compose(poppedOperator) + @processOpStack() + catch e + (e instanceof operators.OperatorError) and @resetCommandMode() or throw e else poppedOperator.execute() diff --git a/src/atom/vim-mode/motions.coffee b/src/atom/vim-mode/motions.coffee index b0cd908d0..9a84058ea 100644 --- a/src/atom/vim-mode/motions.coffee +++ b/src/atom/vim-mode/motions.coffee @@ -61,4 +61,4 @@ class MoveToNextWord extends Motion column = nextLineMatch?.index or 0 { row, column } -module.exports = { MoveLeft, MoveRight, MoveUp, MoveDown, MoveToNextWord, MoveToPreviousWord } +module.exports = { Motion, MoveLeft, MoveRight, MoveUp, MoveDown, MoveToNextWord, MoveToPreviousWord } diff --git a/src/atom/vim-mode/operators.coffee b/src/atom/vim-mode/operators.coffee index 10aeb26f1..c5094ad69 100644 --- a/src/atom/vim-mode/operators.coffee +++ b/src/atom/vim-mode/operators.coffee @@ -1,5 +1,9 @@ _ = require 'underscore' +class OperatorError + constructor: (@message) -> + @name = "Operator Error" + class NumericPrefix count: null complete: null @@ -43,8 +47,11 @@ class Delete @editor.setCursorPosition([@editor.getCursorRow(), 0]) compose: (motion) -> + if not motion.select + throw new OperatorError("Delete must compose with a motion") + @motion = motion @complete = true -module.exports = { NumericPrefix, Delete } +module.exports = { NumericPrefix, Delete, OperatorError }