diff options
| author | Ben Beltran <ben@nsovocal.com> | 2015-07-10 11:12:25 -0500 |
|---|---|---|
| committer | Ben Beltran <ben@nsovocal.com> | 2015-07-10 11:12:25 -0500 |
| commit | 24c7594d62d8d7fbbcdb64b11ce4adc5d8e6991a (patch) | |
| tree | ded312222bb108923da1820ba40b04d710d20e5b /atom/packages/vim-mode/spec | |
| parent | eb786e82d170e2abc351a432ade616d6ecdeeb6b (diff) | |
Adds atom
Diffstat (limited to 'atom/packages/vim-mode/spec')
| -rw-r--r-- | atom/packages/vim-mode/spec/motions-spec.coffee | 1865 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/operators-spec.coffee | 1748 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/prefixes-spec.coffee | 148 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/scroll-spec.coffee | 122 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/spec-helper.coffee | 66 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/text-objects-spec.coffee | 481 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/vim-mode-spec.coffee | 56 | ||||
| -rw-r--r-- | atom/packages/vim-mode/spec/vim-state-spec.coffee | 420 |
8 files changed, 4906 insertions, 0 deletions
diff --git a/atom/packages/vim-mode/spec/motions-spec.coffee b/atom/packages/vim-mode/spec/motions-spec.coffee new file mode 100644 index 0000000..f7114ed --- /dev/null +++ b/atom/packages/vim-mode/spec/motions-spec.coffee @@ -0,0 +1,1865 @@ +helpers = require './spec-helper' + +describe "Motions", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + commandModeInputKeydown = (key, opts = {}) -> + editor.commandModeInputView.editorElement.getModel().setText(key) + + submitCommandModeInputText = (text) -> + commandEditor = editor.commandModeInputView.editorElement + commandEditor.getModel().setText(text) + atom.commands.dispatch(commandEditor, "core:confirm") + + describe "simple motions", -> + beforeEach -> + editor.setText("12345\nabcd\nABCDE") + editor.setCursorScreenPosition([1, 1]) + + describe "the h keybinding", -> + describe "as a motion", -> + it "moves the cursor left, but not to the previous line", -> + keydown('h') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + keydown('h') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + it "moves the cursor to the previous line if wrapLeftRightMotion is true", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('h') + keydown('h') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + describe "as a selection", -> + it "selects the character to the left", -> + keydown('y') + keydown('h') + + expect(vimState.getRegister('"').text).toBe 'a' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "the j keybinding", -> + it "moves the cursor down, but not to the end of the last line", -> + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 1] + + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 1] + + it "moves the cursor to the end of the line, not past it", -> + editor.setCursorScreenPosition([0, 4]) + + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + it "remembers the position it column it was in after moving to shorter line", -> + editor.setCursorScreenPosition([0, 4]) + + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 4] + + describe "when visual mode", -> + beforeEach -> + keydown('v') + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + it "moves the cursor down", -> + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 2] + + it "doesn't go over after the last line", -> + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 2] + + it "selects the text while moving", -> + keydown('j') + expect(editor.getSelectedText()).toBe "bcd\nAB" + + describe "the k keybinding", -> + it "moves the cursor up, but not to the beginning of the first line", -> + keydown('k') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + keydown('k') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + describe "the l keybinding", -> + beforeEach -> editor.setCursorScreenPosition([1, 2]) + + it "moves the cursor right, but not to the next line", -> + keydown('l') + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + keydown('l') + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + it "moves the cursor to the next line if wrapLeftRightMotion is true", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('l') + keydown('l') + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + describe "on a blank line", -> + it "doesn't move the cursor", -> + editor.setText("\n\n\n") + editor.setCursorBufferPosition([1, 0]) + keydown('l') + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + describe "the w keybinding", -> + beforeEach -> editor.setText("ab cde1+- \n xyz\n\nzip") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + it "moves the cursor to the beginning of the next word", -> + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [0, 7] + + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + # FIXME: The definition of Cursor#getEndOfCurrentWordBufferPosition, + # means that the end of the word can't be the current cursor + # position (even though it is when your cursor is on a new line). + # + # Therefore it picks the end of the next word here (which is [3,3]) + # to start looking for the next word, which is also the end of the + # buffer so the cursor never advances. + # + # See atom/vim-mode#3 + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [3, 0] + + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [3, 3] + + # After cursor gets to the EOF, it should stay there. + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual [3, 3] + + it "moves the cursor to the end of the word if last word in file", -> + editor.setText("abc") + editor.setCursorScreenPosition([0, 0]) + keydown('w') + expect(editor.getCursorScreenPosition()).toEqual([0, 3]) + + describe "as a selection", -> + describe "within a word", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('y') + keydown('w') + + it "selects to the end of the word", -> + expect(vimState.getRegister('"').text).toBe 'ab ' + + describe "between words", -> + beforeEach -> + editor.setCursorScreenPosition([0, 2]) + keydown('y') + keydown('w') + + it "selects the whitespace", -> + expect(vimState.getRegister('"').text).toBe ' ' + + describe "the W keybinding", -> + beforeEach -> editor.setText("cde1+- ab \n xyz\n\nzip") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + it "moves the cursor to the beginning of the next word", -> + keydown('W', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [0, 7] + + keydown('W', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + keydown('W', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + keydown('W', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [3, 0] + + describe "as a selection", -> + describe "within a word", -> + it "selects to the end of the whole word", -> + editor.setCursorScreenPosition([0, 0]) + keydown('y') + keydown('W', shift: true) + expect(vimState.getRegister('"').text).toBe 'cde1+- ' + + it "continues past blank lines", -> + editor.setCursorScreenPosition([2, 0]) + + keydown('d') + keydown('W', shift: true) + expect(editor.getText()).toBe "cde1+- ab \n xyz\nzip" + expect(vimState.getRegister('"').text).toBe '\n' + + it "doesn't go past the end of the file", -> + editor.setCursorScreenPosition([3, 0]) + + keydown('d') + keydown('W', shift: true) + expect(editor.getText()).toBe "cde1+- ab \n xyz\n\n" + expect(vimState.getRegister('"').text).toBe 'zip' + + describe "the e keybinding", -> + beforeEach -> editor.setText("ab cde1+- \n xyz\n\nzip") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + it "moves the cursor to the end of the current word", -> + keydown('e') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + keydown('e') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + keydown('e') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + keydown('e') + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + keydown('e') + expect(editor.getCursorScreenPosition()).toEqual [3, 2] + + describe "as selection", -> + describe "within a word", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('y') + keydown('e') + + it "selects to the end of the current word", -> + expect(vimState.getRegister('"').text).toBe 'ab' + + describe "between words", -> + beforeEach -> + editor.setCursorScreenPosition([0, 2]) + keydown('y') + keydown('e') + + it "selects to the end of the next word", -> + expect(vimState.getRegister('"').text).toBe ' cde1' + + describe "the E keybinding", -> + beforeEach -> editor.setText("ab cde1+- \n xyz \n\nzip\n") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + it "moves the cursor to the end of the current word", -> + keydown('E', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + keydown('E', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [0, 9] + + keydown('E', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + keydown('E', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [3, 2] + + keydown('E', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [4, 0] + + describe "as selection", -> + describe "within a word", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('y') + keydown('E', shift: true) + + it "selects to the end of the current word", -> + expect(vimState.getRegister('"').text).toBe 'ab' + + describe "between words", -> + beforeEach -> + editor.setCursorScreenPosition([0, 2]) + keydown('y') + keydown('E', shift: true) + + it "selects to the end of the next word", -> + expect(vimState.getRegister('"').text).toBe ' cde1+-' + + describe "press more than once", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('v') + keydown('E', shift: true) + keydown('E', shift: true) + keydown('y') + + it "selects to the end of the current word", -> + expect(vimState.getRegister('"').text).toBe 'ab cde1+-' + + describe "the } keybinding", -> + beforeEach -> + editor.setText("abcde\n\nfghij\nhijk\n xyz \n\nzip\n\n \nthe end") + editor.setCursorScreenPosition([0, 0]) + + describe "as a motion", -> + it "moves the cursor to the end of the paragraph", -> + keydown('}') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + keydown('}') + expect(editor.getCursorScreenPosition()).toEqual [5, 0] + + keydown('}') + expect(editor.getCursorScreenPosition()).toEqual [7, 0] + + keydown('}') + expect(editor.getCursorScreenPosition()).toEqual [9, 6] + + describe "as a selection", -> + beforeEach -> + keydown('y') + keydown('}') + + it 'selects to the end of the current paragraph', -> + expect(vimState.getRegister('"').text).toBe "abcde\n" + + describe "the { keybinding", -> + beforeEach -> + editor.setText("abcde\n\nfghij\nhijk\n xyz \n\nzip\n\n \nthe end") + editor.setCursorScreenPosition([9, 0]) + + describe "as a motion", -> + it "moves the cursor to the beginning of the paragraph", -> + keydown('{') + expect(editor.getCursorScreenPosition()).toEqual [7, 0] + + keydown('{') + expect(editor.getCursorScreenPosition()).toEqual [5, 0] + + keydown('{') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + keydown('{') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a selection", -> + beforeEach -> + editor.setCursorScreenPosition([7, 0]) + keydown('y') + keydown('{') + + it 'selects to the beginning of the current paragraph', -> + expect(vimState.getRegister('"').text).toBe "\nzip\n" + + describe "the b keybinding", -> + beforeEach -> editor.setText(" ab cde1+- \n xyz\n\nzip }\n last") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([4, 1]) + + it "moves the cursor to the beginning of the previous word", -> + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [3, 4] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [3, 0] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + # Go to start of the file, after moving past the first word + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + # Stay at the start of the file + keydown('b') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a selection", -> + describe "within a word", -> + beforeEach -> + editor.setCursorScreenPosition([0, 2]) + keydown('y') + keydown('b') + + it "selects to the beginning of the current word", -> + expect(vimState.getRegister('"').text).toBe 'a' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + describe "between words", -> + beforeEach -> + editor.setCursorScreenPosition([0, 4]) + keydown('y') + keydown('b') + + it "selects to the beginning of the last word", -> + expect(vimState.getRegister('"').text).toBe 'ab ' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + describe "the B keybinding", -> + beforeEach -> editor.setText("cde1+- ab \n\t xyz-123\n\n zip") + + describe "as a motion", -> + beforeEach -> editor.setCursorScreenPosition([4, 1]) + + it "moves the cursor to the beginning of the previous word", -> + keydown('B', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [3, 1] + + keydown('B', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + keydown('B', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + + keydown('B', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [0, 7] + + keydown('B', shift: true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a selection", -> + it "selects to the beginning of the whole word", -> + editor.setCursorScreenPosition([1, 10]) + keydown('y') + keydown('B', shift: true) + expect(vimState.getRegister('"').text).toBe 'xyz-123' + + it "doesn't go past the beginning of the file", -> + editor.setCursorScreenPosition([0, 0]) + vimState.setRegister('"', text: 'abc') + keydown('y') + keydown('B', shift: true) + expect(vimState.getRegister('"').text).toBe 'abc' + + describe "the ^ keybinding", -> + beforeEach -> + editor.setText(" abcde") + + describe "from the beginning of the line", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + describe "as a motion", -> + beforeEach -> keydown('^') + + it "moves the cursor to the first character of the line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('^') + + it 'selects to the first character of the line', -> + expect(editor.getText()).toBe 'abcde' + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "from the first character of the line", -> + beforeEach -> editor.setCursorScreenPosition([0, 2]) + + describe "as a motion", -> + beforeEach -> keydown('^') + + it "stays put", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('^') + + it "does nothing", -> + expect(editor.getText()).toBe ' abcde' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "from the middle of a word", -> + beforeEach -> editor.setCursorScreenPosition([0, 4]) + + describe "as a motion", -> + beforeEach -> keydown('^') + + it "moves the cursor to the first character of the line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('^') + + it 'selects to the first character of the line', -> + expect(editor.getText()).toBe ' cde' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "the 0 keybinding", -> + beforeEach -> + editor.setText(" abcde") + editor.setCursorScreenPosition([0, 4]) + + describe "as a motion", -> + beforeEach -> keydown('0') + + it "moves the cursor to the first column", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('0') + + it 'selects to the first column of the line', -> + expect(editor.getText()).toBe 'cde' + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "the $ keybinding", -> + beforeEach -> + editor.setText(" abcde\n\n1234567890") + editor.setCursorScreenPosition([0, 4]) + + describe "as a motion from empty line", -> + beforeEach -> editor.setCursorScreenPosition([1, 0]) + + it "moves the cursor to the end of the line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "as a motion", -> + beforeEach -> keydown('$') + + # FIXME: See atom/vim-mode#2 + it "moves the cursor to the end of the line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it "should remain in the last column when moving down", -> + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + keydown('j') + expect(editor.getCursorScreenPosition()).toEqual [2, 9] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('$') + + it "selects to the beginning of the lines", -> + expect(editor.getText()).toBe " ab\n\n1234567890" + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "the 0 keybinding", -> + beforeEach -> + editor.setText(" a\n") + editor.setCursorScreenPosition([0, 2]) + + describe "as a motion", -> + beforeEach -> keydown('0') + + it "moves the cursor to the beginning of the line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "the - keybinding", -> + beforeEach -> + editor.setText("abcdefg\n abc\n abc\n") + + describe "from the middle of a line", -> + beforeEach -> editor.setCursorScreenPosition([1, 3]) + + describe "as a motion", -> + beforeEach -> keydown('-') + + it "moves the cursor to the first character of the previous line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('-') + + it "deletes the current and previous line", -> + expect(editor.getText()).toBe " abc\n" + # commented out because the column is wrong due to a bug in `k`; re-enable when `k` is fixed + #expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "from the first character of a line indented the same as the previous one", -> + beforeEach -> editor.setCursorScreenPosition([2, 2]) + + describe "as a motion", -> + beforeEach -> keydown('-') + + it "moves to the first character of the previous line (directly above)", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('-') + + it "selects to the first character of the previous line (directly above)", -> + expect(editor.getText()).toBe "abcdefg\n" + # commented out because the column is wrong due to a bug in `k`; re-enable when `k` is fixed + #expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "from the beginning of a line preceded by an indented line", -> + beforeEach -> editor.setCursorScreenPosition([2, 0]) + + describe "as a motion", -> + beforeEach -> keydown('-') + + it "moves the cursor to the first character of the previous line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('-') + + it "selects to the first character of the previous line", -> + expect(editor.getText()).toBe "abcdefg\n" + # commented out because the column is wrong due to a bug in `k`; re-enable when `k` is fixed + #expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "with a count", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n") + editor.setCursorScreenPosition([4, 0]) + + describe "as a motion", -> + beforeEach -> + keydown('3') + keydown('-') + + it "moves the cursor to the first character of that many lines previous", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('3') + keydown('-') + + it "deletes the current line plus that many previous lines", -> + expect(editor.getText()).toBe "1\n6\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "the + keybinding", -> + beforeEach -> + editor.setText(" abc\n abc\nabcdefg\n") + + describe "from the middle of a line", -> + beforeEach -> editor.setCursorScreenPosition([1, 3]) + + describe "as a motion", -> + beforeEach -> keydown('+') + + it "moves the cursor to the first character of the next line", -> + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('+') + + it "deletes the current and next line", -> + expect(editor.getText()).toBe " abc\n" + # commented out because the column is wrong due to a bug in `j`; re-enable when `j` is fixed + #expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "from the first character of a line indented the same as the next one", -> + beforeEach -> editor.setCursorScreenPosition([0, 2]) + + describe "as a motion", -> + beforeEach -> keydown('+') + + it "moves to the first character of the next line (directly below)", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('+') + + it "selects to the first character of the next line (directly below)", -> + expect(editor.getText()).toBe "abcdefg\n" + # commented out because the column is wrong due to a bug in `j`; re-enable when `j` is fixed + #expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "from the beginning of a line followed by an indented line", -> + beforeEach -> editor.setCursorScreenPosition([0, 0]) + + describe "as a motion", -> + beforeEach -> keydown('+') + + it "moves the cursor to the first character of the next line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('+') + + it "selects to the first character of the next line", -> + expect(editor.getText()).toBe "abcdefg\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "with a count", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n") + editor.setCursorScreenPosition([1, 0]) + + describe "as a motion", -> + beforeEach -> + keydown('3') + keydown('+') + + it "moves the cursor to the first character of that many lines following", -> + expect(editor.getCursorScreenPosition()).toEqual [4, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('3') + keydown('+') + + it "deletes the current line plus that many following lines", -> + expect(editor.getText()).toBe "1\n6\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "the _ keybinding", -> + beforeEach -> + editor.setText(" abc\n abc\nabcdefg\n") + + describe "from the middle of a line", -> + beforeEach -> editor.setCursorScreenPosition([1, 3]) + + describe "as a motion", -> + beforeEach -> keydown('_') + + it "moves the cursor to the first character of the current line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('_') + + it "deletes the current line", -> + expect(editor.getText()).toBe " abc\nabcdefg\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "with a count", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n") + editor.setCursorScreenPosition([1, 0]) + + describe "as a motion", -> + beforeEach -> + keydown('3') + keydown('_') + + it "moves the cursor to the first character of that many lines following", -> + expect(editor.getCursorScreenPosition()).toEqual [3, 0] + + describe "as a selection", -> + beforeEach -> + keydown('d') + keydown('3') + keydown('_') + + it "deletes the current line plus that many following lines", -> + expect(editor.getText()).toBe "1\n5\n6\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "the enter keybinding", -> + keydownCodeForEnter = '\r' # 'enter' does not work + startingText = " abc\n abc\nabcdefg\n" + + describe "from the middle of a line", -> + startingCursorPosition = [1, 3] + + describe "as a motion", -> + it "acts the same as the + keybinding", -> + # do it with + and save the results + editor.setText(startingText) + editor.setCursorScreenPosition(startingCursorPosition) + keydown('+') + referenceCursorPosition = editor.getCursorScreenPosition() + # do it again with enter and compare the results + editor.setText(startingText) + editor.setCursorScreenPosition(startingCursorPosition) + keydown(keydownCodeForEnter) + expect(editor.getCursorScreenPosition()).toEqual referenceCursorPosition + + describe "as a selection", -> + it "acts the same as the + keybinding", -> + # do it with + and save the results + editor.setText(startingText) + editor.setCursorScreenPosition(startingCursorPosition) + keydown('d') + keydown('+') + referenceText = editor.getText() + referenceCursorPosition = editor.getCursorScreenPosition() + # do it again with enter and compare the results + editor.setText(startingText) + editor.setCursorScreenPosition(startingCursorPosition) + keydown('d') + keydown(keydownCodeForEnter) + expect(editor.getText()).toEqual referenceText + expect(editor.getCursorScreenPosition()).toEqual referenceCursorPosition + + describe "the gg keybinding", -> + beforeEach -> + editor.setText(" 1abc\n 2\n3\n") + editor.setCursorScreenPosition([0, 2]) + + describe "as a motion", -> + describe "in command mode", -> + beforeEach -> + keydown('g') + keydown('g') + + it "moves the cursor to the beginning of the first line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "in linewise visual mode", -> + beforeEach -> + editor.setCursorScreenPosition([1, 0]) + vimState.activateVisualMode('linewise') + keydown('g') + keydown('g') + + it "selects to the first line in the file", -> + expect(editor.getSelectedText()).toBe " 1abc\n 2\n" + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "in characterwise visual mode", -> + beforeEach -> + editor.setCursorScreenPosition([1, 1]) + vimState.activateVisualMode() + keydown('g') + keydown('g') + + it "selects to the first line in the file", -> + expect(editor.getSelectedText()).toBe "1abc\n 2" + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + describe "as a repeated motion", -> + describe "in command mode", -> + beforeEach -> + keydown('2') + keydown('g') + keydown('g') + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "in linewise visual motion", -> + beforeEach -> + editor.setCursorScreenPosition([2, 0]) + vimState.activateVisualMode('linewise') + keydown('2') + keydown('g') + keydown('g') + + it "selects to a specified line", -> + expect(editor.getSelectedText()).toBe " 2\n3\n" + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "in characterwise visual motion", -> + beforeEach -> + editor.setCursorScreenPosition([2, 0]) + vimState.activateVisualMode() + keydown('2') + keydown('g') + keydown('g') + + it "selects to a first character of specified line", -> + expect(editor.getSelectedText()).toBe "2\n3" + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + describe "the g_ keybinding", -> + beforeEach -> + editor.setText("1 \n 2 \n 3abc\n ") + + describe "as a motion", -> + it "moves the cursor to the last nonblank character", -> + editor.setCursorScreenPosition([1, 0]) + keydown('g') + keydown('_') + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + it "will move the cursor to the beginning of the line if necessary", -> + editor.setCursorScreenPosition([0, 2]) + keydown('g') + keydown('_') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "as a repeated motion", -> + it "moves the cursor downward and outward", -> + editor.setCursorScreenPosition([0, 0]) + keydown('2') + keydown('g') + keydown('_') + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + describe "as a selection", -> + it "selects the current line excluding whitespace", -> + editor.setCursorScreenPosition([1, 2]) + vimState.activateVisualMode() + keydown('2') + keydown('g') + keydown('_') + expect(editor.getSelectedText()).toEqual " 2 \n 3abc" + + describe "the G keybinding", -> + beforeEach -> + editor.setText("1\n 2\n 3abc\n ") + editor.setCursorScreenPosition([0, 2]) + + describe "as a motion", -> + beforeEach -> keydown('G', shift: true) + + it "moves the cursor to the last line after whitespace", -> + expect(editor.getCursorScreenPosition()).toEqual [3, 1] + + describe "as a repeated motion", -> + beforeEach -> + keydown('2') + keydown('G', shift: true) + + it "moves the cursor to a specified line", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + describe "as a selection", -> + beforeEach -> + editor.setCursorScreenPosition([1, 0]) + vimState.activateVisualMode() + keydown('G', shift: true) + + it "selects to the last line in the file", -> + expect(editor.getSelectedText()).toBe " 2\n 3abc\n " + + it "moves the cursor to the last line after whitespace", -> + expect(editor.getCursorScreenPosition()).toEqual [3, 1] + + describe "the / keybinding", -> + pane = null + + beforeEach -> + pane = {activate: jasmine.createSpy("activate")} + spyOn(atom.workspace, 'getActivePane').andReturn(pane) + + editor.setText("abc\ndef\nabc\ndef\n") + editor.setCursorBufferPosition([0, 0]) + + describe "as a motion", -> + it "moves the cursor to the specified search pattern", -> + keydown('/') + + submitCommandModeInputText 'def' + + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + expect(pane.activate).toHaveBeenCalled() + + it "loops back around", -> + editor.setCursorBufferPosition([3, 0]) + keydown('/') + submitCommandModeInputText 'def' + + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + it "uses a valid regex as a regex", -> + keydown('/') + # Cycle through the 'abc' on the first line with a character pattern + submitCommandModeInputText '[abc]' + expect(editor.getCursorBufferPosition()).toEqual [0, 1] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [0, 2] + + it "uses an invalid regex as a literal string", -> + # Go straight to the literal [abc + editor.setText("abc\n[abc]\n") + keydown('/') + submitCommandModeInputText '[abc' + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + it "uses ? as a literal string", -> + editor.setText("abc\n[a?c?\n") + keydown('/') + submitCommandModeInputText '?' + expect(editor.getCursorBufferPosition()).toEqual [1, 2] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [1, 4] + + it 'works with selection in visual mode', -> + editor.setText('one two three') + keydown('v') + keydown('/') + submitCommandModeInputText 'th' + expect(editor.getCursorBufferPosition()).toEqual [0, 9] + keydown('d') + expect(editor.getText()).toBe 'hree' + + it 'extends selection when repeating search in visual mode', -> + editor.setText('line1\nline2\nline3') + keydown('v') + keydown('/') + submitCommandModeInputText 'line' + {start, end} = editor.getSelectedBufferRange() + expect(start.row).toEqual 0 + expect(end.row).toEqual 1 + keydown('n') + {start, end} = editor.getSelectedBufferRange() + expect(start.row).toEqual 0 + expect(end.row).toEqual 2 + + describe "case sensitivity", -> + beforeEach -> + editor.setText("\nabc\nABC\n") + editor.setCursorBufferPosition([0, 0]) + keydown('/') + + it "works in case sensitive mode", -> + submitCommandModeInputText 'ABC' + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "works in case insensitive mode", -> + submitCommandModeInputText '\\cAbC' + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "works in case insensitive mode wherever \\c is", -> + submitCommandModeInputText 'AbC\\c' + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "uses case insensitive search if useSmartcaseForSearch is true and searching lowercase", -> + atom.config.set 'vim-mode.useSmartcaseForSearch', true + submitCommandModeInputText 'abc' + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "uses case sensitive search if useSmartcaseForSearch is true and searching uppercase", -> + atom.config.set 'vim-mode.useSmartcaseForSearch', true + submitCommandModeInputText 'ABC' + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + describe "repeating", -> + it "does nothing with no search history", -> + # This tests that no exception is raised + keydown('n') + + beforeEach -> + keydown('/') + submitCommandModeInputText 'def' + + it "repeats previous search with /<enter>", -> + keydown('/') + submitCommandModeInputText('') + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + it "repeats previous search with //", -> + keydown('/') + submitCommandModeInputText('/') + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe "the n keybinding", -> + it "repeats the last search", -> + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe "the N keybinding", -> + it "repeats the last search backwards", -> + editor.setCursorBufferPosition([0, 0]) + keydown('N', shift: true) + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + keydown('N', shift: true) + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + describe "composing", -> + it "composes with operators", -> + keydown('d') + keydown('/') + submitCommandModeInputText('def') + expect(editor.getText()).toEqual "def\nabc\ndef\n" + + it "repeats correctly with operators", -> + keydown('d') + keydown('/') + submitCommandModeInputText('def') + + keydown('.') + expect(editor.getText()).toEqual "def\n" + + describe "when reversed as ?", -> + it "moves the cursor backwards to the specified search pattern", -> + keydown('?') + submitCommandModeInputText('def') + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + it "accepts / as a literal search pattern", -> + editor.setText("abc\nd/f\nabc\nd/f\n") + editor.setCursorBufferPosition([0, 0]) + keydown('?') + submitCommandModeInputText('/') + expect(editor.getCursorBufferPosition()).toEqual [3, 1] + keydown('?') + submitCommandModeInputText('/') + expect(editor.getCursorBufferPosition()).toEqual [1, 1] + + describe "repeating", -> + beforeEach -> + keydown('?') + submitCommandModeInputText('def') + + it "repeats previous search as reversed with ?<enter>", -> + keydown('?') + submitCommandModeInputText('') + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + it "repeats previous search as reversed with ??", -> + keydown('?') + submitCommandModeInputText('?') + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + describe 'the n keybinding', -> + it "repeats the last search backwards", -> + editor.setCursorBufferPosition([0, 0]) + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe 'the N keybinding', -> + it "repeats the last search forwards", -> + editor.setCursorBufferPosition([0, 0]) + keydown('N', shift: true) + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + describe "using search history", -> + commandEditor = null + + beforeEach -> + keydown('/') + submitCommandModeInputText('def') + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + keydown('/') + submitCommandModeInputText('abc') + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + commandEditor = editor.commandModeInputView.editorElement + + it "allows searching history in the search field", -> + keydown('/') + atom.commands.dispatch(commandEditor, 'core:move-up') + expect(commandEditor.getModel().getText()).toEqual('abc') + atom.commands.dispatch(commandEditor, 'core:move-up') + expect(commandEditor.getModel().getText()).toEqual('def') + atom.commands.dispatch(commandEditor, 'core:move-up') + expect(commandEditor.getModel().getText()).toEqual('def') + + it "resets the search field to empty when scrolling back", -> + keydown('/') + atom.commands.dispatch(commandEditor, 'core:move-up') + expect(commandEditor.getModel().getText()).toEqual('abc') + atom.commands.dispatch(commandEditor, 'core:move-up') + expect(commandEditor.getModel().getText()).toEqual('def') + atom.commands.dispatch(commandEditor, 'core:move-down') + expect(commandEditor.getModel().getText()).toEqual('abc') + atom.commands.dispatch(commandEditor, 'core:move-down') + expect(commandEditor.getModel().getText()).toEqual '' + + describe "the * keybinding", -> + beforeEach -> + editor.setText("abd\n@def\nabd\ndef\n") + editor.setCursorBufferPosition([0, 0]) + + describe "as a motion", -> + it "moves cursor to next occurence of word under cursor", -> + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "repeats with the n key", -> + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + keydown("n") + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + + it "doesn't move cursor unless next occurence is the exact word (no partial matches)", -> + editor.setText("abc\ndef\nghiabc\njkl\nabcdef") + editor.setCursorBufferPosition([0, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + + describe "with words that contain 'non-word' characters", -> + it "moves cursor to next occurence of word under cursor", -> + editor.setText("abc\n@def\nabc\n@def\n") + editor.setCursorBufferPosition([1, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + it "doesn't move cursor unless next match has exact word ending", -> + editor.setText("abc\n@def\nabc\n@def1\n") + # FIXME: I suspect there is a bug laying around + # Cursor#getEndOfCurrentWordBufferPosition, this function + # is returning '@' as a word, instead of returning the whole + # word '@def', this behavior is avoided in this test, when we + # execute the '*' command when cursor is on character after '@' + # (in this particular example, the 'd' char) + editor.setCursorBufferPosition([1, 1]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + # FIXME: This behavior is different from the one found in + # vim. This is because the word boundary match in Javascript + # ignores starting 'non-word' characters. + # e.g. + # in Vim: /\<def\>/.test("@def") => false + # in Javascript: /\bdef\b/.test("@def") => true + it "moves cursor to the start of valid word char", -> + editor.setText("abc\ndef\nabc\n@def\n") + editor.setCursorBufferPosition([1, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 1] + + describe "when cursor is on non-word char column", -> + it "matches only the non-word char", -> + editor.setText("abc\n@def\nabc\n@def\n") + editor.setCursorBufferPosition([1, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe "when cursor is not on a word", -> + it "does a match with the next word", -> + editor.setText("abc\na @def\n abc\n @def") + editor.setCursorBufferPosition([1, 1]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 1] + + describe "when cursor is at EOF", -> + it "doesn't try to do any match", -> + editor.setText("abc\n@def\nabc\n ") + editor.setCursorBufferPosition([3, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe "the hash keybinding", -> + describe "as a motion", -> + it "moves cursor to previous occurence of word under cursor", -> + editor.setText("abc\n@def\nabc\ndef\n") + editor.setCursorBufferPosition([2, 1]) + keydown("#") + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + + it "repeats with n", -> + editor.setText("abc\n@def\nabc\ndef\nabc\n") + editor.setCursorBufferPosition([2, 1]) + keydown("#") + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + keydown("n") + expect(editor.getCursorBufferPosition()).toEqual [4, 0] + keydown("n") + expect(editor.getCursorBufferPosition()).toEqual [2, 0] + + it "doesn't move cursor unless next occurence is the exact word (no partial matches)", -> + editor.setText("abc\ndef\nghiabc\njkl\nabcdef") + editor.setCursorBufferPosition([0, 0]) + keydown("#") + expect(editor.getCursorBufferPosition()).toEqual [0, 0] + + describe "with words that containt 'non-word' characters", -> + it "moves cursor to next occurence of word under cursor", -> + editor.setText("abc\n@def\nabc\n@def\n") + editor.setCursorBufferPosition([3, 0]) + keydown("#") + expect(editor.getCursorBufferPosition()).toEqual [1, 0] + + it "moves cursor to the start of valid word char", -> + editor.setText("abc\n@def\nabc\ndef\n") + editor.setCursorBufferPosition([3, 0]) + keydown("#") + expect(editor.getCursorBufferPosition()).toEqual [1, 1] + + describe "when cursor is on non-word char column", -> + it "matches only the non-word char", -> + editor.setText("abc\n@def\nabc\n@def\n") + editor.setCursorBufferPosition([1, 0]) + keydown("*") + expect(editor.getCursorBufferPosition()).toEqual [3, 0] + + describe "the H keybinding", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") + editor.setCursorScreenPosition([8, 0]) + spyOn(editor.getLastCursor(), 'setScreenPosition') + + it "moves the cursor to the first row if visible", -> + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(0) + keydown('H', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([0, 0]) + + it "moves the cursor to the first visible row plus offset", -> + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(2) + keydown('H', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([4, 0]) + + it "respects counts", -> + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(0) + keydown('3') + keydown('H', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([2, 0]) + + describe "the L keybinding", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") + editor.setCursorScreenPosition([8, 0]) + spyOn(editor.getLastCursor(), 'setScreenPosition') + + it "moves the cursor to the first row if visible", -> + spyOn(editor, 'getLastVisibleScreenRow').andReturn(10) + keydown('L', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([10, 0]) + + it "moves the cursor to the first visible row plus offset", -> + spyOn(editor, 'getLastVisibleScreenRow').andReturn(6) + keydown('L', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([4, 0]) + + it "respects counts", -> + spyOn(editor, 'getLastVisibleScreenRow').andReturn(10) + keydown('3') + keydown('L', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([8, 0]) + + describe "the M keybinding", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") + editor.setCursorScreenPosition([8, 0]) + spyOn(editor.getLastCursor(), 'setScreenPosition') + spyOn(editor, 'getLastVisibleScreenRow').andReturn(10) + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(0) + + it "moves the cursor to the first row if visible", -> + keydown('M', shift: true) + expect(editor.getLastCursor().setScreenPosition).toHaveBeenCalledWith([5, 0]) + + describe 'the mark keybindings', -> + beforeEach -> + editor.setText(' 12\n 34\n56\n') + editor.setCursorBufferPosition([0, 1]) + + it 'moves to the beginning of the line of a mark', -> + editor.setCursorBufferPosition([1, 1]) + keydown('m') + commandModeInputKeydown('a') + editor.setCursorBufferPosition([0, 0]) + keydown('\'') + commandModeInputKeydown('a') + expect(editor.getCursorBufferPosition()).toEqual [1, 4] + + it 'moves literally to a mark', -> + editor.setCursorBufferPosition([1, 1]) + keydown('m') + commandModeInputKeydown('a') + editor.setCursorBufferPosition([0, 0]) + keydown('`') + commandModeInputKeydown('a') + expect(editor.getCursorBufferPosition()).toEqual [1, 1] + + it 'deletes to a mark by line', -> + editor.setCursorBufferPosition([1, 5]) + keydown('m') + commandModeInputKeydown('a') + editor.setCursorBufferPosition([0, 0]) + keydown('d') + keydown('\'') + commandModeInputKeydown('a') + expect(editor.getText()).toEqual '56\n' + + it 'deletes before to a mark literally', -> + editor.setCursorBufferPosition([1, 5]) + keydown('m') + commandModeInputKeydown('a') + editor.setCursorBufferPosition([0, 1]) + keydown('d') + keydown('`') + commandModeInputKeydown('a') + expect(editor.getText()).toEqual ' 4\n56\n' + + it 'deletes after to a mark literally', -> + editor.setCursorBufferPosition([1, 5]) + keydown('m') + commandModeInputKeydown('a') + editor.setCursorBufferPosition([2, 1]) + keydown('d') + keydown('`') + commandModeInputKeydown('a') + expect(editor.getText()).toEqual ' 12\n 36\n' + + it 'moves back to previous', -> + editor.setCursorBufferPosition([1, 5]) + keydown('`') + commandModeInputKeydown('`') + editor.setCursorBufferPosition([2, 1]) + keydown('`') + commandModeInputKeydown('`') + expect(editor.getCursorBufferPosition()).toEqual [1, 5] + + describe 'the f/F keybindings', -> + beforeEach -> + editor.setText("abcabcabcabc\n") + editor.setCursorScreenPosition([0, 0]) + + it 'moves to the first specified character it finds', -> + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it 'moves backwards to the first specified character it finds', -> + editor.setCursorScreenPosition([0, 2]) + keydown('F', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it 'respects count forward', -> + keydown('2') + keydown('f') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it 'respects count backward', -> + editor.setCursorScreenPosition([0, 6]) + keydown('2') + keydown('F', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it "doesn't move if the character specified isn't found", -> + keydown('f') + commandModeInputKeydown('d') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it "doesn't move if there aren't the specified count of the specified character", -> + keydown('1') + keydown('0') + keydown('f') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + # a bug was making this behaviour depend on the count + keydown('1') + keydown('1') + keydown('f') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + # and backwards now + editor.setCursorScreenPosition([0, 6]) + keydown('1') + keydown('0') + keydown('F', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + keydown('1') + keydown('1') + keydown('F', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it "composes with d", -> + editor.setCursorScreenPosition([0, 3]) + keydown('d') + keydown('2') + keydown('f') + commandModeInputKeydown('a') + expect(editor.getText()).toEqual 'abcbc\n' + + describe 'the t/T keybindings', -> + beforeEach -> + editor.setText("abcabcabcabc\n") + editor.setCursorScreenPosition([0, 0]) + + it 'moves to the character previous to the first specified character it finds', -> + keydown('t') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + # or stays put when it's already there + keydown('t') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it 'moves backwards to the character after the first specified character it finds', -> + editor.setCursorScreenPosition([0, 2]) + keydown('T', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + it 'respects count forward', -> + keydown('2') + keydown('t') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + + it 'respects count backward', -> + editor.setCursorScreenPosition([0, 6]) + keydown('2') + keydown('T', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + it "doesn't move if the character specified isn't found", -> + keydown('t') + commandModeInputKeydown('d') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it "doesn't move if there aren't the specified count of the specified character", -> + keydown('1') + keydown('0') + keydown('t') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + # a bug was making this behaviour depend on the count + keydown('1') + keydown('1') + keydown('t') + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + # and backwards now + editor.setCursorScreenPosition([0, 6]) + keydown('1') + keydown('0') + keydown('T', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + keydown('1') + keydown('1') + keydown('T', shift: true) + commandModeInputKeydown('a') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it "composes with d", -> + editor.setCursorScreenPosition([0, 3]) + keydown('d') + keydown('2') + keydown('t') + commandModeInputKeydown('b') + expect(editor.getText()).toBe 'abcbcabc\n' + + describe 'the V keybinding', -> + beforeEach -> + editor.setText("01\n002\n0003\n00004\n000005\n") + editor.setCursorScreenPosition([1, 1]) + + it "selects down a line", -> + keydown('V', shift: true) + keydown('j') + keydown('j') + expect(editor.getSelectedText()).toBe "002\n0003\n00004\n" + + it "selects up a line", -> + keydown('V', shift: true) + keydown('k') + expect(editor.getSelectedText()).toBe "01\n002\n" + + describe 'the ; and , keybindings', -> + beforeEach -> + editor.setText("abcabcabcabc\n") + editor.setCursorScreenPosition([0, 0]) + + it "repeat f in same direction", -> + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + it "repeat F in same direction", -> + editor.setCursorScreenPosition([0, 10]) + keydown('F', shift: true) + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it "repeat f in opposite direction", -> + editor.setCursorScreenPosition([0, 6]) + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it "repeat F in opposite direction", -> + editor.setCursorScreenPosition([0, 4]) + keydown('F', shift: true) + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + it "alternate repeat f in same direction and reverse", -> + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it "alternate repeat F in same direction and reverse", -> + editor.setCursorScreenPosition([0, 10]) + keydown('F', shift: true) + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + it "repeat t in same direction", -> + keydown('t') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + it "repeat T in same direction", -> + editor.setCursorScreenPosition([0, 10]) + keydown('T', shift: true) + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 9] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it "repeat t in opposite direction first, and then reverse", -> + editor.setCursorScreenPosition([0, 3]) + keydown('t') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + it "repeat T in opposite direction first, and then reverse", -> + editor.setCursorScreenPosition([0, 4]) + keydown('T', shift: true) + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + it "repeat with count in same direction", -> + editor.setCursorScreenPosition([0, 0]) + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + keydown('2') + keydown(';') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + + it "repeat with count in reverse direction", -> + editor.setCursorScreenPosition([0, 6]) + keydown('f') + commandModeInputKeydown('c') + expect(editor.getCursorScreenPosition()).toEqual [0, 8] + keydown('2') + keydown(',') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe 'the % motion', -> + beforeEach -> + editor.setText("( ( ) )--{ text in here; and a function call(with parameters) }\n") + editor.setCursorScreenPosition([0, 0]) + + it 'matches the correct parenthesis', -> + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it 'matches the correct brace', -> + editor.setCursorScreenPosition([0, 9]) + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 62] + + it 'composes correctly with d', -> + editor.setCursorScreenPosition([0, 9]) + keydown('d') + keydown('%') + expect(editor.getText()).toEqual "( ( ) )--\n" + + it 'moves correctly when composed with v going forward', -> + keydown('v') + keydown('h') + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 7] + + it 'moves correctly when composed with v going backward', -> + editor.setCursorScreenPosition([0, 5]) + keydown('v') + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it 'it moves appropriately to find the nearest matching action', -> + editor.setCursorScreenPosition([0, 3]) + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + expect(editor.getText()).toEqual "( ( ) )--{ text in here; and a function call(with parameters) }\n" + + it 'it moves appropriately to find the nearest matching action', -> + editor.setCursorScreenPosition([0, 26]) + keydown('%') + expect(editor.getCursorScreenPosition()).toEqual [0, 60] + expect(editor.getText()).toEqual "( ( ) )--{ text in here; and a function call(with parameters) }\n" + + it "finds matches across multiple lines", -> + editor.setText("...(\n...)") + editor.setCursorScreenPosition([0, 0]) + keydown("%") + expect(editor.getCursorScreenPosition()).toEqual([1, 3]) + + it "does not affect search history", -> + keydown('/') + submitCommandModeInputText 'func' + expect(editor.getCursorBufferPosition()).toEqual [0, 31] + keydown('%') + expect(editor.getCursorBufferPosition()).toEqual [0, 60] + keydown('n') + expect(editor.getCursorBufferPosition()).toEqual [0, 31] + + describe "scrolling screen and keeping cursor in the same screen position", -> + beforeEach -> + editor.setText([0...80].join("\n")) + editor.setHeight(20 * 10) + editor.setLineHeightInPixels(10) + editor.setScrollTop(40 * 10) + editor.setCursorBufferPosition([42, 0]) + + describe "the ctrl-u keybinding", -> + it "moves the screen down by half screen size and keeps cursor onscreen", -> + keydown('u', ctrl: true) + expect(editor.getScrollTop()).toEqual 300 + expect(editor.getCursorBufferPosition()).toEqual [32, 0] + + it "selects on visual mode", -> + editor.setCursorBufferPosition([42, 1]) + vimState.activateVisualMode() + keydown('u', ctrl: true) + expect(editor.getSelectedText()).toEqual [32..42].join("\n") + + it "selects on linewise mode", -> + vimState.activateVisualMode('linewise') + keydown('u', ctrl: true) + expect(editor.getSelectedText()).toEqual [32..42].join("\n").concat("\n") + + describe "the ctrl-b keybinding", -> + it "moves screen up one page", -> + keydown('b', ctrl: true) + expect(editor.getScrollTop()).toEqual 200 + expect(editor.getCursorScreenPosition()).toEqual [22, 0] + + it "selects on visual mode", -> + editor.setCursorBufferPosition([42, 1]) + vimState.activateVisualMode() + keydown('b', ctrl: true) + expect(editor.getSelectedText()).toEqual [22..42].join("\n") + + it "selects on linewise mode", -> + vimState.activateVisualMode('linewise') + keydown('b', ctrl: true) + expect(editor.getSelectedText()).toEqual [22..42].join("\n").concat("\n") + + + describe "the ctrl-d keybinding", -> + it "moves the screen down by half screen size and keeps cursor onscreen", -> + keydown('d', ctrl: true) + expect(editor.getScrollTop()).toEqual 500 + expect(editor.getCursorBufferPosition()).toEqual [52, 0] + + it "selects on visual mode", -> + editor.setCursorBufferPosition([42, 1]) + vimState.activateVisualMode() + keydown('d', ctrl: true) + expect(editor.getSelectedText()).toEqual [42..52].join("\n").slice(1, -1) + + it "selects on linewise mode", -> + vimState.activateVisualMode('linewise') + keydown('d', ctrl: true) + expect(editor.getSelectedText()).toEqual [42..52].join("\n").concat("\n") + + describe "the ctrl-f keybinding", -> + it "moves screen down one page", -> + keydown('f', ctrl: true) + expect(editor.getScrollTop()).toEqual 600 + expect(editor.getCursorScreenPosition()).toEqual [62, 0] + + it "selects on visual mode", -> + editor.setCursorBufferPosition([42, 1]) + vimState.activateVisualMode() + keydown('f', ctrl: true) + expect(editor.getSelectedText()).toEqual [42..62].join("\n").slice(1, -1) + + it "selects on linewise mode", -> + vimState.activateVisualMode('linewise') + keydown('f', ctrl: true) + expect(editor.getSelectedText()).toEqual [42..62].join("\n").concat("\n") diff --git a/atom/packages/vim-mode/spec/operators-spec.coffee b/atom/packages/vim-mode/spec/operators-spec.coffee new file mode 100644 index 0000000..a41edb3 --- /dev/null +++ b/atom/packages/vim-mode/spec/operators-spec.coffee @@ -0,0 +1,1748 @@ +helpers = require './spec-helper' +settings = require '../lib/settings' + +describe "Operators", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + commandModeInputKeydown = (key, opts = {}) -> + editor.commandModeInputView.editorElement.getModel().setText(key) + + describe "cancelling operations", -> + it "does not throw an error even if no operation is pending", -> + # cancel operation pushes an empty input operation + # doing this without a pending operation throws an exception + expect(-> vimState.pushOperations(new Input(''))).toThrow() + + # make sure commandModeInputView is created + keydown('/') + expect(vimState.isOperatorPending()).toBe true + editor.commandModeInputView.viewModel.cancel() + + expect(vimState.isOperatorPending()).toBe false + expect(-> editor.commandModeInputView.viewModel.cancel()).not.toThrow() + + describe "the x keybinding", -> + describe "on a line with content", -> + describe "without vim-mode.wrapLeftRightMotion", -> + beforeEach -> + editor.setText("abc\n012345\n\nxyz") + editor.setCursorScreenPosition([1, 4]) + + it "deletes a character", -> + keydown('x') + expect(editor.getText()).toBe 'abc\n01235\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + expect(vimState.getRegister('"').text).toBe '4' + + keydown('x') + expect(editor.getText()).toBe 'abc\n0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + expect(vimState.getRegister('"').text).toBe '5' + + keydown('x') + expect(editor.getText()).toBe 'abc\n012\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + expect(vimState.getRegister('"').text).toBe '3' + + keydown('x') + expect(editor.getText()).toBe 'abc\n01\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + expect(vimState.getRegister('"').text).toBe '2' + + keydown('x') + expect(editor.getText()).toBe 'abc\n0\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '1' + + keydown('x') + expect(editor.getText()).toBe 'abc\n\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '0' + + it "deletes multiple characters with a count", -> + keydown('2') + keydown('x') + expect(editor.getText()).toBe 'abc\n0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + expect(vimState.getRegister('"').text).toBe '45' + + editor.setCursorScreenPosition([0, 1]) + keydown('3') + keydown('x') + expect(editor.getText()).toBe 'a\n0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(vimState.getRegister('"').text).toBe 'bc' + + describe "with vim-mode.wrapLeftRightMotion", -> + beforeEach -> + editor.setText("abc\n012345\n\nxyz") + editor.setCursorScreenPosition([1, 4]) + atom.config.set('vim-mode.wrapLeftRightMotion', true) + + it "deletes a character", -> + # copy of the earlier test because wrapLeftRightMotion should not affect it + keydown('x') + expect(editor.getText()).toBe 'abc\n01235\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + expect(vimState.getRegister('"').text).toBe '4' + + keydown('x') + expect(editor.getText()).toBe 'abc\n0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + expect(vimState.getRegister('"').text).toBe '5' + + keydown('x') + expect(editor.getText()).toBe 'abc\n012\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + expect(vimState.getRegister('"').text).toBe '3' + + keydown('x') + expect(editor.getText()).toBe 'abc\n01\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + expect(vimState.getRegister('"').text).toBe '2' + + keydown('x') + expect(editor.getText()).toBe 'abc\n0\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '1' + + keydown('x') + expect(editor.getText()).toBe 'abc\n\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '0' + + it "deletes multiple characters and newlines with a count", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('2') + keydown('x') + expect(editor.getText()).toBe 'abc\n0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [1, 3] + expect(vimState.getRegister('"').text).toBe '45' + + editor.setCursorScreenPosition([0, 1]) + keydown('3') + keydown('x') + expect(editor.getText()).toBe 'a0123\n\nxyz' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(vimState.getRegister('"').text).toBe 'bc\n' + + keydown('7') + keydown('x') + expect(editor.getText()).toBe 'ayz' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(vimState.getRegister('"').text).toBe '0123\n\nx' + + + describe "on an empty line", -> + beforeEach -> + editor.setText("abc\n012345\n\nxyz") + editor.setCursorScreenPosition([2, 0]) + + it "deletes nothing on an empty line when vim-mode.wrapLeftRightMotion is false", -> + atom.config.set('vim-mode.wrapLeftRightMotion', false) + keydown('x') + expect(editor.getText()).toBe "abc\n012345\n\nxyz" + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + it "deletes an empty line when vim-mode.wrapLeftRightMotion is true", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('x') + expect(editor.getText()).toBe "abc\n012345\nxyz" + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + + describe "the X keybinding", -> + describe "on a line with content", -> + beforeEach -> + editor.setText("ab\n012345") + editor.setCursorScreenPosition([1, 2]) + + it "deletes a character", -> + keydown('X', shift: true) + expect(editor.getText()).toBe 'ab\n02345' + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + expect(vimState.getRegister('"').text).toBe '1' + + keydown('X', shift: true) + expect(editor.getText()).toBe 'ab\n2345' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '0' + + keydown('X', shift: true) + expect(editor.getText()).toBe 'ab\n2345' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe '0' + + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('X', shift: true) + expect(editor.getText()).toBe 'ab2345' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + expect(vimState.getRegister('"').text).toBe '\n' + + + describe "on an empty line", -> + beforeEach -> + editor.setText("012345\n\nabcdef") + editor.setCursorScreenPosition([1, 0]) + + it "deletes nothing when vim-mode.wrapLeftRightMotion is false", -> + atom.config.set('vim-mode.wrapLeftRightMotion', false) + keydown('X', shift: true) + expect(editor.getText()).toBe "012345\n\nabcdef" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + it "deletes the newline when wrapLeftRightMotion is true", -> + atom.config.set('vim-mode.wrapLeftRightMotion', true) + keydown('X', shift: true) + expect(editor.getText()).toBe "012345\nabcdef" + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + + describe "the s keybinding", -> + beforeEach -> + editor.setText('012345') + editor.setCursorScreenPosition([0, 1]) + + it "deletes the character to the right and enters insert mode", -> + keydown('s') + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getText()).toBe '02345' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(vimState.getRegister('"').text).toBe '1' + + it "is repeatable", -> + editor.setCursorScreenPosition([0, 0]) + keydown('3') + keydown('s') + editor.insertText("ab") + keydown('escape') + expect(editor.getText()).toBe 'ab345' + editor.setCursorScreenPosition([0, 2]) + keydown('.') + expect(editor.getText()).toBe 'abab' + + it "is undoable", -> + editor.setCursorScreenPosition([0, 0]) + keydown('3') + keydown('s') + editor.insertText("ab") + keydown('escape') + expect(editor.getText()).toBe 'ab345' + keydown('u') + expect(editor.getText()).toBe '012345' + expect(editor.getSelectedText()).toBe '' + + describe "in visual mode", -> + beforeEach -> + keydown('v') + editor.selectRight() + keydown('s') + + it "deletes the selected characters and enters insert mode", -> + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getText()).toBe '0345' + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(vimState.getRegister('"').text).toBe '12' + + describe "the S keybinding", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE") + editor.setCursorScreenPosition([1, 3]) + + it "deletes the entire line and enters insert mode", -> + keydown('S', shift: true) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getText()).toBe "12345\n\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe "abcde\n" + expect(vimState.getRegister('"').type).toBe 'linewise' + + it "is repeatable", -> + keydown('S', shift: true) + editor.insertText("abc") + keydown 'escape' + expect(editor.getText()).toBe "12345\nabc\nABCDE" + editor.setCursorScreenPosition([2, 3]) + keydown '.' + expect(editor.getText()).toBe "12345\nabc\nabc\n" + + it "is undoable", -> + keydown('S', shift: true) + editor.insertText("abc") + keydown 'escape' + expect(editor.getText()).toBe "12345\nabc\nABCDE" + keydown 'u' + expect(editor.getText()).toBe "12345\nabcde\nABCDE" + expect(editor.getSelectedText()).toBe '' + + it "works when the cursor's goal column is greater than its current column", -> + editor.setText("\n12345") + editor.setCursorBufferPosition([1, Infinity]) + editor.moveUp() + keydown("S", shift: true) + expect(editor.getText()).toBe("\n12345") + + # Can't be tested without setting grammar of test buffer + xit "respects indentation", -> + + describe "the d keybinding", -> + it "enters operator-pending mode", -> + keydown('d') + expect(editorElement.classList.contains('operator-pending-mode')).toBe(true) + expect(editorElement.classList.contains('command-mode')).toBe(false) + + describe "when followed by a d", -> + it "deletes the current line and exits operator-pending mode", -> + editor.setText("12345\nabcde\n\nABCDE") + editor.setCursorScreenPosition([1, 1]) + + keydown('d') + keydown('d') + + expect(editor.getText()).toBe "12345\n\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe "abcde\n" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "deletes the last line", -> + editor.setText("12345\nabcde\nABCDE") + editor.setCursorScreenPosition([2, 1]) + + keydown('d') + keydown('d') + + expect(editor.getText()).toBe "12345\nabcde\n" + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + it "leaves the cursor on the first nonblank character", -> + editor.setText("12345\n abcde\n") + editor.setCursorScreenPosition([0, 4]) + + keydown('d') + keydown('d') + + expect(editor.getText()).toBe " abcde\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "undo behavior", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE\nQWERT") + editor.setCursorScreenPosition([1, 1]) + + keydown('d') + keydown('2') + keydown('d') + + keydown('u') + + it "undoes both lines", -> + expect(editor.getText()).toBe "12345\nabcde\nABCDE\nQWERT" + expect(editor.getSelectedText()).toBe '' + + describe "when followed by a w", -> + it "deletes the next word until the end of the line and exits operator-pending mode", -> + editor.setText("abcd efg\nabc") + editor.setCursorScreenPosition([0, 5]) + + keydown('d') + keydown('w') + + # Incompatibility with VIM. In vim, `w` behaves differently as an + # operator than as a motion; it stops at the end of a line.expect(editor.getText()).toBe "abcd abc" + expect(editor.getText()).toBe "abcd abc" + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "deletes to the beginning of the next word", -> + editor.setText('abcd efg') + editor.setCursorScreenPosition([0, 2]) + + keydown('d') + keydown('w') + + expect(editor.getText()).toBe 'abefg' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + editor.setText('one two three four') + editor.setCursorScreenPosition([0, 0]) + + keydown('d') + keydown('3') + keydown('w') + + expect(editor.getText()).toBe 'four' + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "when followed by an iw", -> + it "deletes the containing word", -> + editor.setText("12345 abcde ABCDE") + editor.setCursorScreenPosition([0, 9]) + + keydown('d') + expect(editorElement.classList.contains('operator-pending-mode')).toBe(true) + keydown('i') + keydown('w') + + expect(editor.getText()).toBe "12345 ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + expect(vimState.getRegister('"').text).toBe "abcde" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "when followed by an j", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the file", -> + editor.setCursorScreenPosition([0, 0]) + it "deletes the next two lines", -> + keydown('d') + keydown('j') + expect(editor.getText()).toBe("ABCDE") + + describe "on the end of the file", -> + editor.setCursorScreenPosition([4, 2]) + it "deletes nothing", -> + keydown('d') + keydown('j') + expect(editor.getText()).toBe(originalText) + + describe "on the middle of second line", -> + editor.setCursorScreenPosition([2, 1]) + it "deletes the last two lines", -> + keydown('d') + keydown('j') + expect(editor.getText()).toBe("12345") + + describe "when followed by an k", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the end of the file", -> + editor.setCursorScreenPosition([4, 2]) + it "deletes the bottom two lines", -> + keydown('d') + keydown('k') + expect(editor.getText()).toBe("ABCDE") + + describe "on the beginning of the file", -> + editor.setCursorScreenPosition([0, 0]) + it "deletes nothing", -> + keydown('d') + keydown('k') + expect(editor.getText()).toBe(originalText) + + describe "when on the middle of second line", -> + editor.setCursorScreenPosition([2, 1]) + it "deletes the first two lines", -> + keydown('d') + keydown('k') + expect(editor.getText()).toBe("12345") + + describe "when followed by a G", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 0]) + keydown('d') + keydown('G', shift: true) + expect(editor.getText()).toBe("12345\n") + + describe "on the middle of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 2]) + keydown('d') + keydown('G', shift: true) + expect(editor.getText()).toBe("12345\n") + + describe "when followed by a goto line G", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 0]) + keydown('d') + keydown('2') + keydown('G', shift: true) + expect(editor.getText()).toBe("12345\nABCDE") + + describe "on the middle of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 2]) + keydown('d') + keydown('2') + keydown('G', shift: true) + expect(editor.getText()).toBe("12345\nABCDE") + + describe "when followed by a t)", -> + describe "with the entire line yanked before", -> + beforeEach -> + editor.setText("test (xyz)") + editor.setCursorScreenPosition([0, 6]) + + it "deletes until the closing parenthesis", -> + keydown('y') + keydown('y') + keydown('d') + keydown('t') + commandModeInputKeydown(')') + expect(editor.getText()).toBe("test ()") + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + describe "with multiple cursors", -> + it "deletes each selection", -> + editor.setText("abcd\n1234\nABCD\n") + editor.setCursorBufferPosition([0, 1]) + editor.addCursorAtBufferPosition([1, 2]) + editor.addCursorAtBufferPosition([2, 3]) + + keydown('d') + keydown('e') + + expect(editor.getText()).toBe "a\n12\nABC" + expect(editor.getCursorBufferPositions()).toEqual [ + [0, 0], + [1, 1], + [2, 2], + ] + + it "doesn't delete empty selections", -> + editor.setText("abcd\nabc\nabd") + editor.setCursorBufferPosition([0, 0]) + editor.addCursorAtBufferPosition([1, 0]) + editor.addCursorAtBufferPosition([2, 0]) + + keydown('d') + keydown('t') + commandModeInputKeydown('d') + + expect(editor.getText()).toBe "d\nabc\nd" + expect(editor.getCursorBufferPositions()).toEqual [ + [0, 0], + [1, 0], + [2, 0], + ] + + describe "the D keybinding", -> + beforeEach -> + editor.getBuffer().setText("012\n") + editor.setCursorScreenPosition([0, 1]) + keydown('D', shift: true) + + it "deletes the contents until the end of the line", -> + expect(editor.getText()).toBe "0\n" + + describe "the c keybinding", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE") + + describe "when followed by a c", -> + it "deletes the current line and enters insert mode", -> + editor.setCursorScreenPosition([1, 1]) + + keydown('c') + keydown('c') + + expect(editor.getText()).toBe "12345\n\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(editorElement.classList.contains('command-mode')).toBe(false) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "when the cursor is on the last line", -> + it "deletes the line's content and enters insert mode on the last line", -> + editor.setCursorScreenPosition([2, 1]) + + keydown('c') + keydown('c') + + expect(editor.getText()).toBe "12345\nabcde\n\n" + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + expect(editorElement.classList.contains('command-mode')).toBe(false) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "when the cursor is on the only line", -> + it "deletes the line's content and enters insert mode", -> + editor.setText("12345") + editor.setCursorScreenPosition([0, 2]) + + keydown('c') + keydown('c') + + expect(editor.getText()).toBe "\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('command-mode')).toBe(false) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "when followed by i w", -> + it "undo's and redo's completely", -> + editor.setCursorScreenPosition([1, 1]) + + keydown('c') + keydown('i') + keydown('w') + expect(editor.getText()).toBe "12345\n\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + # Just cannot get "typing" to work correctly in test. + editor.setText("12345\nfg\nABCDE") + keydown('escape') + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editor.getText()).toBe "12345\nfg\nABCDE" + + keydown('u') + expect(editor.getText()).toBe "12345\nabcde\nABCDE" + keydown('r', ctrl: true) + expect(editor.getText()).toBe "12345\nfg\nABCDE" + + describe "when followed by a w", -> + it "changes the word", -> + editor.setText("word1 word2 word3") + editor.setCursorBufferPosition([0, "word1 w".length]) + + keydown("c") + keydown("w") + keydown("escape") + + expect(editor.getText()).toBe "word1 w word3" + + describe "when followed by a G", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 0]) + keydown('c') + keydown('G', shift: true) + keydown('escape') + expect(editor.getText()).toBe("12345\n\n") + + describe "on the middle of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 2]) + keydown('c') + keydown('G', shift: true) + keydown('escape') + expect(editor.getText()).toBe("12345\n\n") + + describe "when followed by a goto line G", -> + beforeEach -> + editor.setText "12345\nabcde\nABCDE" + + describe "on the beginning of the second line", -> + it "deletes all the text on the line", -> + editor.setCursorScreenPosition([1, 0]) + keydown('c') + keydown('2') + keydown('G', shift: true) + keydown('escape') + expect(editor.getText()).toBe("12345\n\nABCDE") + + describe "on the middle of the second line", -> + it "deletes all the text on the line", -> + editor.setCursorScreenPosition([1, 2]) + keydown('c') + keydown('2') + keydown('G', shift: true) + keydown('escape') + expect(editor.getText()).toBe("12345\n\nABCDE") + + describe "the C keybinding", -> + beforeEach -> + editor.getBuffer().setText("012\n") + editor.setCursorScreenPosition([0, 1]) + keydown('C', shift: true) + + it "deletes the contents until the end of the line and enters insert mode", -> + expect(editor.getText()).toBe "0\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('command-mode')).toBe(false) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "the y keybinding", -> + beforeEach -> + editor.getBuffer().setText("012 345\nabc\n") + editor.setCursorScreenPosition([0, 4]) + + describe "when selected lines in visual linewise mode", -> + beforeEach -> + keydown('V', shift: true) + keydown('j') + keydown('y') + + it "is in linewise motion", -> + expect(vimState.getRegister('"').type).toEqual "linewise" + + it "saves the lines to the default register", -> + expect(vimState.getRegister('"').text).toBe "012 345\nabc\n" + + it "places the cursor at the beginning of the selection", -> + expect(editor.getCursorBufferPositions()).toEqual([[0, 0]]) + + describe "when followed by a second y ", -> + beforeEach -> + keydown('y') + keydown('y') + + it "saves the line to the default register", -> + expect(vimState.getRegister('"').text).toBe "012 345\n" + + it "leaves the cursor at the starting position", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + describe "when useClipboardAsDefaultRegister enabled", -> + it "writes to clipboard", -> + atom.config.set 'vim-mode.useClipboardAsDefaultRegister', true + keydown('y') + keydown('y') + expect(atom.clipboard.read()).toBe '012 345\n' + + describe "when followed with a repeated y", -> + beforeEach -> + keydown('y') + keydown('2') + keydown('y') + + it "copies n lines, starting from the current", -> + expect(vimState.getRegister('"').text).toBe "012 345\nabc\n" + + it "leaves the cursor at the starting position", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + describe "with a register", -> + beforeEach -> + keydown('"') + keydown('a') + keydown('y') + keydown('y') + + it "saves the line to the a register", -> + expect(vimState.getRegister('a').text).toBe "012 345\n" + + it "appends the line to the A register", -> + keydown('"') + keydown('A', shift: true) + keydown('y') + keydown('y') + expect(vimState.getRegister('a').text).toBe "012 345\n012 345\n" + + describe "with a forward motion", -> + beforeEach -> + keydown('y') + keydown('e') + + it "saves the selected text to the default register", -> + expect(vimState.getRegister('"').text).toBe '345' + + it "leaves the cursor at the starting position", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + it "does not yank when motion fails", -> + keydown('y') + keydown('t') + commandModeInputKeydown('x') + expect(vimState.getRegister('"').text).toBe '345' + + describe "with a text object", -> + it "moves the cursor to the beginning of the text object", -> + editor.setCursorBufferPosition([0, 5]) + keydown("y") + keydown("i") + keydown("w") + expect(editor.getCursorBufferPositions()).toEqual([[0, 4]]) + + describe "with a left motion", -> + beforeEach -> + keydown('y') + keydown('h') + + it "saves the left letter to the default register", -> + expect(vimState.getRegister('"').text).toBe " " + + it "moves the cursor position to the left", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "with a down motion", -> + beforeEach -> + keydown 'y' + keydown 'j' + + it "saves both full lines to the default register", -> + expect(vimState.getRegister('"').text).toBe "012 345\nabc\n" + + it "leaves the cursor at the starting position", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + describe "when followed by a G", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 0]) + keydown('y') + keydown('G', shift: true) + keydown('P', shift: true) + expect(editor.getText()).toBe("12345\nabcde\nABCDE\nabcde\nABCDE") + + describe "on the middle of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 2]) + keydown('y') + keydown('G', shift: true) + keydown('P', shift: true) + expect(editor.getText()).toBe("12345\nabcde\nABCDE\nabcde\nABCDE") + + describe "when followed by a goto line G", -> + beforeEach -> + originalText = "12345\nabcde\nABCDE" + editor.setText(originalText) + + describe "on the beginning of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 0]) + keydown('y') + keydown('2') + keydown('G', shift: true) + keydown('P', shift: true) + expect(editor.getText()).toBe("12345\nabcde\nabcde\nABCDE") + + describe "on the middle of the second line", -> + it "deletes the bottom two lines", -> + editor.setCursorScreenPosition([1, 2]) + keydown('y') + keydown('2') + keydown('G', shift: true) + keydown('P', shift: true) + expect(editor.getText()).toBe("12345\nabcde\nabcde\nABCDE") + + describe "with multiple cursors", -> + it "moves each cursor and copies the last selection's text", -> + editor.setText " abcd\n 1234" + editor.setCursorBufferPosition([0, 0]) + editor.addCursorAtBufferPosition([1, 5]) + + keydown("y") + keydown("^") + + expect(vimState.getRegister('"').text).toBe '123' + expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 2]] + + describe "the yy keybinding", -> + describe "on a single line file", -> + beforeEach -> + editor.getBuffer().setText "exclamation!\n" + editor.setCursorScreenPosition [0, 0] + + it "copies the entire line and pastes it correctly", -> + keydown('y') + keydown('y') + keydown('p') + + expect(vimState.getRegister('"').text).toBe "exclamation!\n" + expect(editor.getText()).toBe "exclamation!\nexclamation!\n" + + describe "on a single line file with no newline", -> + beforeEach -> + editor.getBuffer().setText "no newline!" + editor.setCursorScreenPosition [0, 0] + + it "copies the entire line and pastes it correctly", -> + keydown('y') + keydown('y') + keydown('p') + + expect(vimState.getRegister('"').text).toBe "no newline!\n" + expect(editor.getText()).toBe "no newline!\nno newline!" + + it "copies the entire line and pastes it respecting count and new lines", -> + keydown('y') + keydown('y') + keydown('2') + keydown('p') + + expect(vimState.getRegister('"').text).toBe "no newline!\n" + expect(editor.getText()).toBe "no newline!\nno newline!\nno newline!" + + describe "the Y keybinding", -> + beforeEach -> + editor.getBuffer().setText "012 345\nabc\n" + editor.setCursorScreenPosition [0, 4] + + it "saves the line to the default register", -> + keydown('Y', shift: true) + + expect(vimState.getRegister('"').text).toBe "012 345\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 4] + + describe "the p keybinding", -> + describe "with character contents", -> + beforeEach -> + editor.getBuffer().setText "012\n" + editor.setCursorScreenPosition [0, 0] + vimState.setRegister('"', text: '345') + vimState.setRegister('a', text: 'a') + atom.clipboard.write "clip" + + describe "from the default register", -> + beforeEach -> keydown('p') + + it "inserts the contents", -> + expect(editor.getText()).toBe "034512\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "when useClipboardAsDefaultRegister enabled", -> + it "inserts contents from clipboard", -> + atom.config.set 'vim-mode.useClipboardAsDefaultRegister', true + keydown('p') + expect(editor.getText()).toBe "0clip12\n" + + describe "from a specified register", -> + beforeEach -> + keydown('"') + keydown('a') + keydown('p') + + it "inserts the contents of the 'a' register", -> + expect(editor.getText()).toBe "0a12\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + + describe "at the end of a line", -> + it "inserts before the current line's newline", -> + editor.setText("abcde\none two three") + editor.setCursorScreenPosition([1, 4]) + + keydown 'd' + keydown '$' + keydown 'k' + keydown '$' + keydown 'p' + + expect(editor.getText()).toBe "abcdetwo three\none " + + describe "with a selection", -> + beforeEach -> + editor.selectRight() + keydown('p') + + it "replaces the current selection", -> + expect(editor.getText()).toBe "34512\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "with linewise contents", -> + describe "on a single line", -> + beforeEach -> + editor.getBuffer().setText("012") + editor.setCursorScreenPosition([0, 1]) + vimState.setRegister('"', text: " 345\n", type: 'linewise') + + it "inserts the contents of the default register", -> + keydown('p') + + expect(editor.getText()).toBe "012\n 345" + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + it "replaces the current selection", -> + editor.selectRight() + keydown('p') + + expect(editor.getText()).toBe "0 345\n2" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "on multiple lines", -> + beforeEach -> + editor.getBuffer().setText("012\n 345") + vimState.setRegister('"', text: " 456\n", type: 'linewise') + + it "inserts the contents of the default register at middle line", -> + editor.setCursorScreenPosition([0, 1]) + keydown('p') + + expect(editor.getText()).toBe "012\n 456\n 345" + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + it "inserts the contents of the default register at end of line", -> + editor.setCursorScreenPosition([1, 1]) + keydown('p') + + expect(editor.getText()).toBe "012\n 345\n 456" + expect(editor.getCursorScreenPosition()).toEqual [2, 1] + + describe "with multiple linewise contents", -> + beforeEach -> + editor.getBuffer().setText("012\nabc") + editor.setCursorScreenPosition([1, 0]) + vimState.setRegister('"', text: " 345\n 678\n", type: 'linewise') + keydown('p') + + it "inserts the contents of the default register", -> + expect(editor.getText()).toBe "012\nabc\n 345\n 678" + expect(editor.getCursorScreenPosition()).toEqual [2, 1] + + describe "pasting twice", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE\nQWERT") + editor.setCursorScreenPosition([1, 1]) + vimState.setRegister('"', text: '123') + keydown('2') + keydown('p') + + it "inserts the same line twice", -> + expect(editor.getText()).toBe "12345\nab123123cde\nABCDE\nQWERT" + + describe "when undone", -> + beforeEach -> + keydown('u') + + it "removes both lines", -> + expect(editor.getText()).toBe "12345\nabcde\nABCDE\nQWERT" + + describe "the P keybinding", -> + describe "with character contents", -> + beforeEach -> + editor.getBuffer().setText("012\n") + editor.setCursorScreenPosition([0, 0]) + vimState.setRegister('"', text: '345') + vimState.setRegister('a', text: 'a') + keydown('P', shift: true) + + it "inserts the contents of the default register above", -> + expect(editor.getText()).toBe "345012\n" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "the O keybinding", -> + beforeEach -> + spyOn(editor, 'shouldAutoIndent').andReturn(true) + spyOn(editor, 'autoIndentBufferRow').andCallFake (line) -> + editor.indent() + + editor.getBuffer().setText(" abc\n 012\n") + editor.setCursorScreenPosition([1, 1]) + + it "switches to insert and adds a newline above the current one", -> + keydown('O', shift: true) + expect(editor.getText()).toBe " abc\n \n 012\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + it "is repeatable", -> + editor.getBuffer().setText(" abc\n 012\n 4spaces\n") + editor.setCursorScreenPosition([1, 1]) + keydown('O', shift: true) + editor.insertText "def" + keydown 'escape' + expect(editor.getText()).toBe " abc\n def\n 012\n 4spaces\n" + editor.setCursorScreenPosition([1, 1]) + keydown '.' + expect(editor.getText()).toBe " abc\n def\n def\n 012\n 4spaces\n" + editor.setCursorScreenPosition([4, 1]) + keydown '.' + expect(editor.getText()).toBe " abc\n def\n def\n 012\n def\n 4spaces\n" + + it "is undoable", -> + keydown('O', shift: true) + editor.insertText "def" + keydown 'escape' + expect(editor.getText()).toBe " abc\n def\n 012\n" + keydown 'u' + expect(editor.getText()).toBe " abc\n 012\n" + + describe "the o keybinding", -> + beforeEach -> + spyOn(editor, 'shouldAutoIndent').andReturn(true) + spyOn(editor, 'autoIndentBufferRow').andCallFake (line) -> + editor.indent() + + editor.getBuffer().setText("abc\n 012\n") + editor.setCursorScreenPosition([1, 2]) + + it "switches to insert and adds a newline above the current one", -> + keydown('o') + expect(editor.getText()).toBe "abc\n 012\n \n" + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getCursorScreenPosition()).toEqual [2, 2] + + # This works in practice, but the editor doesn't respect the indentation + # rules without a syntax grammar. Need to set the editor's grammar + # to fix it. + xit "is repeatable", -> + editor.getBuffer().setText(" abc\n 012\n 4spaces\n") + editor.setCursorScreenPosition([1, 1]) + keydown('o') + editor.insertText "def" + keydown 'escape' + expect(editor.getText()).toBe " abc\n 012\n def\n 4spaces\n" + keydown '.' + expect(editor.getText()).toBe " abc\n 012\n def\n def\n 4spaces\n" + editor.setCursorScreenPosition([4, 1]) + keydown '.' + expect(editor.getText()).toBe " abc\n def\n def\n 012\n 4spaces\n def\n" + + it "is undoable", -> + keydown('o') + editor.insertText "def" + keydown 'escape' + expect(editor.getText()).toBe "abc\n 012\n def\n" + keydown 'u' + expect(editor.getText()).toBe "abc\n 012\n" + + describe "the a keybinding", -> + beforeEach -> + editor.getBuffer().setText("012\n") + + describe "at the beginning of the line", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('a') + + it "switches to insert mode and shifts to the right", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "at the end of the line", -> + beforeEach -> + editor.setCursorScreenPosition([0, 3]) + keydown('a') + + it "doesn't linewrap", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 3] + + describe "the A keybinding", -> + beforeEach -> + editor.getBuffer().setText("11\n22\n") + + describe "at the beginning of a line", -> + it "switches to insert mode at the end of the line", -> + editor.setCursorScreenPosition([0, 0]) + keydown('A', shift: true) + + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it "repeats always as insert at the end of the line", -> + editor.setCursorScreenPosition([0, 0]) + keydown('A', shift: true) + editor.insertText("abc") + keydown 'escape' + editor.setCursorScreenPosition([1, 0]) + keydown '.' + + expect(editor.getText()).toBe "11abc\n22abc\n" + expect(editorElement.classList.contains('insert-mode')).toBe(false) + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + describe "the I keybinding", -> + beforeEach -> + editor.getBuffer().setText("11\n 22\n") + + describe "at the end of a line", -> + it "switches to insert mode at the beginning of the line", -> + editor.setCursorScreenPosition([0, 2]) + keydown('I', shift: true) + + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + it "switches to insert mode after leading whitespace", -> + editor.setCursorScreenPosition([1, 4]) + keydown('I', shift: true) + + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + it "repeats always as insert at the first character of the line", -> + editor.setCursorScreenPosition([0, 2]) + keydown('I', shift: true) + editor.insertText("abc") + keydown 'escape' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + editor.setCursorScreenPosition([1, 4]) + keydown '.' + + expect(editor.getText()).toBe "abc11\n abc22\n" + expect(editorElement.classList.contains('insert-mode')).toBe(false) + expect(editor.getCursorScreenPosition()).toEqual [1, 4] + + describe "the J keybinding", -> + beforeEach -> + editor.getBuffer().setText("012\n 456\n") + editor.setCursorScreenPosition([0, 1]) + + describe "without repeating", -> + beforeEach -> keydown('J', shift: true) + + it "joins the contents of the current line with the one below it", -> + expect(editor.getText()).toBe "012 456\n" + + describe "with repeating", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE\nQWERT") + editor.setCursorScreenPosition([1, 1]) + keydown('2') + keydown('J', shift: true) + + describe "undo behavior", -> + beforeEach -> keydown('u') + + it "handles repeats", -> + expect(editor.getText()).toBe "12345\nabcde\nABCDE\nQWERT" + + describe "the > keybinding", -> + beforeEach -> + editor.setText("12345\nabcde\nABCDE") + + describe "on the last line", -> + beforeEach -> + editor.setCursorScreenPosition([2, 0]) + + describe "when followed by a >", -> + beforeEach -> + keydown('>') + keydown('>') + + it "indents the current line", -> + expect(editor.getText()).toBe "12345\nabcde\n ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [2, 2] + + describe "on the first line", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + + describe "when followed by a >", -> + beforeEach -> + keydown('>') + keydown('>') + + it "indents the current line", -> + expect(editor.getText()).toBe " 12345\nabcde\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "when followed by a repeating >", -> + beforeEach -> + keydown('3') + keydown('>') + keydown('>') + + it "indents multiple lines at once", -> + expect(editor.getText()).toBe " 12345\n abcde\n ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "undo behavior", -> + beforeEach -> keydown('u') + + it "outdents all three lines", -> + expect(editor.getText()).toBe "12345\nabcde\nABCDE" + + describe "in visual mode", -> + beforeEach -> + editor.setCursorScreenPosition([0, 0]) + keydown('v', shift: true) + keydown('>') + + it "indents the current line and remains in visual mode", -> + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(editor.getText()).toBe " 12345\nabcde\nABCDE" + expect(editor.getSelectedText()).toBe " 12345\n" + + it "allows repeating the operation", -> + keydown("escape") + keydown(".") + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editor.getText()).toBe " 12345\nabcde\nABCDE" + + describe "the < keybinding", -> + beforeEach -> + editor.setText(" 12345\n abcde\nABCDE") + editor.setCursorScreenPosition([0, 0]) + + describe "when followed by a <", -> + beforeEach -> + keydown('<') + keydown('<') + + it "indents the current line", -> + expect(editor.getText()).toBe "12345\n abcde\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "when followed by a repeating <", -> + beforeEach -> + keydown('2') + keydown('<') + keydown('<') + + it "indents multiple lines at once", -> + expect(editor.getText()).toBe "12345\nabcde\nABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + + describe "undo behavior", -> + beforeEach -> keydown('u') + + it "indents both lines", -> + expect(editor.getText()).toBe " 12345\n abcde\nABCDE" + + describe "in visual mode", -> + beforeEach -> + keydown('v', shift: true) + keydown('<') + + it "indents the current line and remains in visual mode", -> + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(editor.getText()).toBe "12345\n abcde\nABCDE" + expect(editor.getSelectedText()).toBe "12345\n" + + describe "the = keybinding", -> + oldGrammar = [] + + beforeEach -> + waitsForPromise -> + atom.packages.activatePackage('language-javascript') + + oldGrammar = editor.getGrammar() + editor.setText("foo\n bar\n baz") + editor.setCursorScreenPosition([1, 0]) + + describe "when used in a scope that supports auto-indent", -> + beforeEach -> + jsGrammar = atom.grammars.grammarForScopeName('source.js') + editor.setGrammar(jsGrammar) + + afterEach -> + editor.setGrammar(oldGrammar) + + describe "when followed by a =", -> + beforeEach -> + keydown('=') + keydown('=') + + it "indents the current line", -> + expect(editor.indentationForBufferRow(1)).toBe 0 + + describe "when followed by a repeating =", -> + beforeEach -> + keydown('2') + keydown('=') + keydown('=') + + it "autoindents multiple lines at once", -> + expect(editor.getText()).toBe "foo\nbar\nbaz" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "undo behavior", -> + beforeEach -> keydown('u') + + it "indents both lines", -> + expect(editor.getText()).toBe "foo\n bar\n baz" + + describe "the . keybinding", -> + beforeEach -> + editor.setText("12\n34\n56\n78") + editor.setCursorScreenPosition([0, 0]) + + it "repeats the last operation", -> + keydown '2' + keydown 'd' + keydown 'd' + keydown '.' + + expect(editor.getText()).toBe "" + + it "composes with motions", -> + keydown 'd' + keydown 'd' + keydown '2' + keydown '.' + + expect(editor.getText()).toBe "78" + + describe "the r keybinding", -> + beforeEach -> + editor.setText("12\n34\n\n") + editor.setCursorBufferPosition([0, 0]) + editor.addCursorAtBufferPosition([1, 0]) + + it "replaces a single character", -> + keydown('r') + commandModeInputKeydown('x') + expect(editor.getText()).toBe 'x2\nx4\n\n' + + it "does nothing when cancelled", -> + keydown('r') + expect(editorElement.classList.contains('operator-pending-mode')).toBe(true) + keydown('escape') + expect(editor.getText()).toBe '12\n34\n\n' + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "replaces a single character with a line break", -> + keydown('r') + atom.commands.dispatch(editor.commandModeInputView.editorElement, 'core:confirm') + expect(editor.getText()).toBe '\n2\n\n4\n\n' + expect(editor.getCursorBufferPositions()).toEqual [[1, 0], [3, 0]] + + it "composes properly with motions", -> + keydown('2') + keydown('r') + commandModeInputKeydown('x') + expect(editor.getText()).toBe 'xx\nxx\n\n' + + it "does nothing on an empty line", -> + editor.setCursorBufferPosition([2, 0]) + keydown('r') + commandModeInputKeydown('x') + expect(editor.getText()).toBe '12\n34\n\n' + + it "does nothing if asked to replace more characters than there are on a line", -> + keydown('3') + keydown('r') + commandModeInputKeydown('x') + expect(editor.getText()).toBe '12\n34\n\n' + + describe "when in visual mode", -> + beforeEach -> + keydown('v') + keydown('e') + + it "replaces the entire selection with the given character", -> + keydown('r') + commandModeInputKeydown('x') + expect(editor.getText()).toBe 'xx\nxx\n\n' + + it "leaves the cursor at the beginning of the selection", -> + keydown('r') + commandModeInputKeydown('x') + expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 0]] + + describe 'the m keybinding', -> + beforeEach -> + editor.setText('12\n34\n56\n') + editor.setCursorBufferPosition([0, 1]) + + it 'marks a position', -> + keydown('m') + commandModeInputKeydown('a') + expect(vimState.getMark('a')).toEqual [0, 1] + + describe 'the ~ keybinding', -> + beforeEach -> + editor.setText('aBc\nXyZ') + editor.setCursorBufferPosition([0, 0]) + editor.addCursorAtBufferPosition([1, 0]) + + it 'toggles the case and moves right', -> + keydown('~') + expect(editor.getText()).toBe 'ABc\nxyZ' + expect(editor.getCursorScreenPositions()).toEqual [[0, 1], [1, 1]] + + keydown('~') + expect(editor.getText()).toBe 'Abc\nxYZ' + expect(editor.getCursorScreenPositions()).toEqual [[0, 2], [1, 2]] + + keydown('~') + expect(editor.getText()).toBe 'AbC\nxYz' + expect(editor.getCursorScreenPositions()).toEqual [[0, 2], [1, 2]] + + it 'takes a count', -> + keydown('4') + keydown('~') + + expect(editor.getText()).toBe 'AbC\nxYz' + expect(editor.getCursorScreenPositions()).toEqual [[0, 2], [1, 2]] + + describe "in visual mode", -> + it "toggles the case of the selected text", -> + editor.setCursorBufferPosition([0, 0]) + keydown("V", shift: true) + keydown("~") + expect(editor.getText()).toBe 'AbC\nXyZ' + + describe "with g and motion", -> + it "toggles the case of text", -> + editor.setCursorBufferPosition([0, 0]) + keydown("g") + keydown("~") + keydown("2") + keydown("l") + expect(editor.getText()).toBe 'Abc\nXyZ' + + describe 'the U keybinding', -> + beforeEach -> + editor.setText('aBc\nXyZ') + editor.setCursorBufferPosition([0, 0]) + + it "makes text uppercase with g and motion", -> + keydown("g") + keydown("U", shift: true) + keydown("l") + expect(editor.getText()).toBe 'ABc\nXyZ' + + keydown("g") + keydown("U", shift: true) + keydown("e") + expect(editor.getText()).toBe 'ABC\nXyZ' + + editor.setCursorBufferPosition([1, 0]) + keydown("g") + keydown("U", shift: true) + keydown("$") + expect(editor.getText()).toBe 'ABC\nXYZ' + expect(editor.getCursorScreenPosition()).toEqual [1, 2] + + it "makes the selected text uppercase in visual mode", -> + keydown("V", shift: true) + keydown("U", shift: true) + expect(editor.getText()).toBe 'ABC\nXyZ' + + describe 'the u keybinding', -> + beforeEach -> + editor.setText('aBc\nXyZ') + editor.setCursorBufferPosition([0, 0]) + + it "makes text lowercase with g and motion", -> + keydown("g") + keydown("u") + keydown("$") + expect(editor.getText()).toBe 'abc\nXyZ' + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + it "makes the selected text lowercase in visual mode", -> + keydown("V", shift: true) + keydown("u") + expect(editor.getText()).toBe 'abc\nXyZ' + + describe "the i keybinding", -> + beforeEach -> + editor.setText('123\n4567') + editor.setCursorBufferPosition([0, 0]) + editor.addCursorAtBufferPosition([1, 0]) + + it "allows undoing an entire batch of typing", -> + keydown 'i' + editor.insertText("abcXX") + editor.backspace() + editor.backspace() + keydown 'escape' + expect(editor.getText()).toBe "abc123\nabc4567" + + keydown 'i' + editor.insertText "d" + editor.insertText "e" + editor.insertText "f" + keydown 'escape' + expect(editor.getText()).toBe "abdefc123\nabdefc4567" + + keydown 'u' + expect(editor.getText()).toBe "abc123\nabc4567" + + keydown 'u' + expect(editor.getText()).toBe "123\n4567" + + it "allows repeating typing", -> + keydown 'i' + editor.insertText("abcXX") + editor.backspace() + editor.backspace() + keydown 'escape' + expect(editor.getText()).toBe "abc123\nabc4567" + + keydown '.' + expect(editor.getText()).toBe "ababcc123\nababcc4567" + + keydown '.' + expect(editor.getText()).toBe "abababccc123\nabababccc4567" + + describe 'with nonlinear input', -> + beforeEach -> + editor.setText '' + editor.setCursorBufferPosition [0, 0] + + it 'deals with auto-matched brackets', -> + keydown 'i' + # this sequence simulates what the bracket-matcher package does + # when the user types (a)b<enter> + editor.insertText '()' + editor.moveLeft() + editor.insertText 'a' + editor.moveRight() + editor.insertText 'b\n' + keydown 'escape' + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + keydown '.' + expect(editor.getText()).toBe '(a)b\n(a)b\n' + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + + it 'deals with autocomplete', -> + keydown 'i' + # this sequence simulates autocompletion of 'add' to 'addFoo' + editor.insertText 'a' + editor.insertText 'd' + editor.insertText 'd' + editor.setTextInBufferRange [[0, 0], [0, 3]], 'addFoo' + keydown 'escape' + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + expect(editor.getText()).toBe 'addFoo' + + keydown '.' + expect(editor.getText()).toBe 'addFoaddFooo' + expect(editor.getCursorScreenPosition()).toEqual [0, 10] + + describe 'the a keybinding', -> + beforeEach -> + editor.setText('') + editor.setCursorBufferPosition([0, 0]) + + it "can be undone in one go", -> + keydown 'a' + editor.insertText("abc") + keydown 'escape' + expect(editor.getText()).toBe "abc" + keydown 'u' + expect(editor.getText()).toBe "" + + it "repeats correctly", -> + keydown 'a' + editor.insertText("abc") + keydown 'escape' + expect(editor.getText()).toBe "abc" + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + keydown '.' + expect(editor.getText()).toBe "abcabc" + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + + describe "the ctrl-a/ctrl-x keybindings", -> + beforeEach -> + atom.config.set 'vim-mode.numberRegex', settings.config.numberRegex.default + editor.setText('123\nab45\ncd-67ef\nab-5\na-bcdef') + editor.setCursorBufferPosition [0, 0] + editor.addCursorAtBufferPosition [1, 0] + editor.addCursorAtBufferPosition [2, 0] + editor.addCursorAtBufferPosition [3, 3] + editor.addCursorAtBufferPosition [4, 0] + + describe "increasing numbers", -> + it "increases the next number", -> + keydown('a', ctrl: true) + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]] + expect(editor.getText()).toBe '124\nab46\ncd-66ef\nab-4\na-bcdef' + + it "repeats with .", -> + keydown 'a', ctrl: true + keydown '.' + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]] + expect(editor.getText()).toBe '125\nab47\ncd-65ef\nab-3\na-bcdef' + + it "can have a count", -> + keydown '5' + keydown 'a', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 2], [4, 0]] + expect(editor.getText()).toBe '128\nab50\ncd-62ef\nab0\na-bcdef' + + it "can make a negative number positive, change number of digits", -> + keydown '9' + keydown '9' + keydown 'a', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 4], [2, 3], [3, 3], [4, 0]] + expect(editor.getText()).toBe '222\nab144\ncd32ef\nab94\na-bcdef' + + it "does nothing when cursor is after the number", -> + editor.setCursorBufferPosition [2, 5] + keydown 'a', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[2, 5]] + expect(editor.getText()).toBe '123\nab45\ncd-67ef\nab-5\na-bcdef' + + it "does nothing on an empty line", -> + editor.setText('\n') + editor.setCursorBufferPosition [0, 0] + editor.addCursorAtBufferPosition [1, 0] + keydown 'a', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 0]] + expect(editor.getText()).toBe '\n' + + it "honours the vim-mode:numberRegex setting", -> + editor.setText('123\nab45\ncd -67ef\nab-5\na-bcdef') + editor.setCursorBufferPosition [0, 0] + editor.addCursorAtBufferPosition [1, 0] + editor.addCursorAtBufferPosition [2, 0] + editor.addCursorAtBufferPosition [3, 3] + editor.addCursorAtBufferPosition [4, 0] + atom.config.set('vim-mode.numberRegex', '(?:\\B-)?[0-9]+') + keydown('a', ctrl: true) + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 5], [3, 3], [4, 0]] + expect(editor.getText()).toBe '124\nab46\ncd -66ef\nab-6\na-bcdef' + + describe "decreasing numbers", -> + it "decreases the next number", -> + keydown('x', ctrl: true) + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]] + expect(editor.getText()).toBe '122\nab44\ncd-68ef\nab-6\na-bcdef' + + it "repeats with .", -> + keydown 'x', ctrl: true + keydown '.' + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 3], [4, 0]] + expect(editor.getText()).toBe '121\nab43\ncd-69ef\nab-7\na-bcdef' + + it "can have a count", -> + keydown '5' + keydown 'x', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 4], [3, 4], [4, 0]] + expect(editor.getText()).toBe '118\nab40\ncd-72ef\nab-10\na-bcdef' + + it "can make a positive number negative, change number of digits", -> + keydown '9' + keydown '9' + keydown 'x', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 1], [1, 4], [2, 5], [3, 5], [4, 0]] + expect(editor.getText()).toBe '24\nab-54\ncd-166ef\nab-104\na-bcdef' + + it "does nothing when cursor is after the number", -> + editor.setCursorBufferPosition [2, 5] + keydown 'x', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[2, 5]] + expect(editor.getText()).toBe '123\nab45\ncd-67ef\nab-5\na-bcdef' + + it "does nothing on an empty line", -> + editor.setText('\n') + editor.setCursorBufferPosition [0, 0] + editor.addCursorAtBufferPosition [1, 0] + keydown 'x', ctrl: true + expect(editor.getCursorBufferPositions()).toEqual [[0, 0], [1, 0]] + expect(editor.getText()).toBe '\n' + + it "honours the vim-mode:numberRegex setting", -> + editor.setText('123\nab45\ncd -67ef\nab-5\na-bcdef') + editor.setCursorBufferPosition [0, 0] + editor.addCursorAtBufferPosition [1, 0] + editor.addCursorAtBufferPosition [2, 0] + editor.addCursorAtBufferPosition [3, 3] + editor.addCursorAtBufferPosition [4, 0] + atom.config.set('vim-mode.numberRegex', '(?:\\B-)?[0-9]+') + keydown('x', ctrl: true) + expect(editor.getCursorBufferPositions()).toEqual [[0, 2], [1, 3], [2, 5], [3, 3], [4, 0]] + expect(editor.getText()).toBe '122\nab44\ncd -68ef\nab-4\na-bcdef' diff --git a/atom/packages/vim-mode/spec/prefixes-spec.coffee b/atom/packages/vim-mode/spec/prefixes-spec.coffee new file mode 100644 index 0000000..e782d20 --- /dev/null +++ b/atom/packages/vim-mode/spec/prefixes-spec.coffee @@ -0,0 +1,148 @@ +helpers = require './spec-helper' + +describe "Prefixes", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + describe "Repeat", -> + describe "with operations", -> + beforeEach -> + editor.setText("123456789abc") + editor.setCursorScreenPosition([0, 0]) + + it "repeats N times", -> + keydown('3') + keydown('x') + + expect(editor.getText()).toBe '456789abc' + + it "repeats NN times", -> + keydown('1') + keydown('0') + keydown('x') + + expect(editor.getText()).toBe 'bc' + + describe "with motions", -> + beforeEach -> + editor.setText('one two three') + editor.setCursorScreenPosition([0, 0]) + + it "repeats N times", -> + keydown('d') + keydown('2') + keydown('w') + + expect(editor.getText()).toBe 'three' + + describe "in visual mode", -> + beforeEach -> + editor.setText('one two three') + editor.setCursorScreenPosition([0, 0]) + + it "repeats movements in visual mode", -> + keydown("v") + keydown("2") + keydown("w") + + expect(editor.getCursorScreenPosition()).toEqual [0, 9] + + describe "Register", -> + describe "the a register", -> + it "saves a value for future reading", -> + vimState.setRegister('a', text: 'new content') + expect(vimState.getRegister("a").text).toEqual 'new content' + + it "overwrites a value previously in the register", -> + vimState.setRegister('a', text: 'content') + vimState.setRegister('a', text: 'new content') + expect(vimState.getRegister("a").text).toEqual 'new content' + + describe "the B register", -> + it "saves a value for future reading", -> + vimState.setRegister('B', text: 'new content') + expect(vimState.getRegister("b").text).toEqual 'new content' + expect(vimState.getRegister("B").text).toEqual 'new content' + + it "appends to a value previously in the register", -> + vimState.setRegister('b', text: 'content') + vimState.setRegister('B', text: 'new content') + expect(vimState.getRegister("b").text).toEqual 'contentnew content' + + it "appends linewise to a linewise value previously in the register", -> + vimState.setRegister('b', {type: 'linewise', text: 'content\n'}) + vimState.setRegister('B', text: 'new content') + expect(vimState.getRegister("b").text).toEqual 'content\nnew content\n' + + it "appends linewise to a character value previously in the register", -> + vimState.setRegister('b', text: 'content') + vimState.setRegister('B', {type: 'linewise', text: 'new content\n'}) + expect(vimState.getRegister("b").text).toEqual 'content\nnew content\n' + + + describe "the * register", -> + describe "reading", -> + it "is the same the system clipboard", -> + expect(vimState.getRegister('*').text).toEqual 'initial clipboard content' + expect(vimState.getRegister('*').type).toEqual 'character' + + describe "writing", -> + beforeEach -> + vimState.setRegister('*', text: 'new content') + + it "overwrites the contents of the system clipboard", -> + expect(atom.clipboard.read()).toEqual 'new content' + + # FIXME: once linux support comes out, this needs to read from + # the correct clipboard. For now it behaves just like the * register + # See :help x11-cut-buffer and :help registers for more details on how these + # registers work on an X11 based system. + describe "the + register", -> + describe "reading", -> + it "is the same the system clipboard", -> + expect(vimState.getRegister('*').text).toEqual 'initial clipboard content' + expect(vimState.getRegister('*').type).toEqual 'character' + + describe "writing", -> + beforeEach -> + vimState.setRegister('*', text: 'new content') + + it "overwrites the contents of the system clipboard", -> + expect(atom.clipboard.read()).toEqual 'new content' + + describe "the _ register", -> + describe "reading", -> + it "is always the empty string", -> + expect(vimState.getRegister("_").text).toEqual '' + + describe "writing", -> + it "throws away anything written to it", -> + vimState.setRegister('_', text: 'new content') + expect(vimState.getRegister("_").text).toEqual '' + + describe "the % register", -> + beforeEach -> + spyOn(editor, 'getURI').andReturn('/Users/atom/known_value.txt') + + describe "reading", -> + it "returns the filename of the current editor", -> + expect(vimState.getRegister('%').text).toEqual '/Users/atom/known_value.txt' + + describe "writing", -> + it "throws away anything written to it", -> + vimState.setRegister('%', "new content") + expect(vimState.getRegister('%').text).toEqual '/Users/atom/known_value.txt' diff --git a/atom/packages/vim-mode/spec/scroll-spec.coffee b/atom/packages/vim-mode/spec/scroll-spec.coffee new file mode 100644 index 0000000..9473193 --- /dev/null +++ b/atom/packages/vim-mode/spec/scroll-spec.coffee @@ -0,0 +1,122 @@ +helpers = require './spec-helper' + +describe "Scrolling", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + describe "scrolling keybindings", -> + beforeEach -> + editor.setText("1\n2\n3\n4\n5\n6\n7\n8\n9\n10") + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(2) + spyOn(editor, 'getLastVisibleScreenRow').andReturn(8) + spyOn(editor, 'scrollToScreenPosition') + + describe "the ctrl-e keybinding", -> + beforeEach -> + spyOn(editor, 'getCursorScreenPosition').andReturn({row: 4, column: 0}) + spyOn(editor, 'setCursorScreenPosition') + + it "moves the screen down by one and keeps cursor onscreen", -> + keydown('e', ctrl: true) + expect(editor.scrollToScreenPosition).toHaveBeenCalledWith([7, 0]) + expect(editor.setCursorScreenPosition).toHaveBeenCalledWith([6, 0]) + + describe "the ctrl-y keybinding", -> + beforeEach -> + spyOn(editor, 'getCursorScreenPosition').andReturn({row: 6, column: 0}) + spyOn(editor, 'setCursorScreenPosition') + + it "moves the screen up by one and keeps the cursor onscreen", -> + keydown('y', ctrl: true) + expect(editor.scrollToScreenPosition).toHaveBeenCalledWith([3, 0]) + expect(editor.setCursorScreenPosition).toHaveBeenCalledWith([4, 0]) + + describe "scroll cursor keybindings", -> + beforeEach -> + text = "" + for i in [1..200] + text += "#{i}\n" + editor.setText(text) + + spyOn(editor, 'moveToFirstCharacterOfLine') + spyOn(editor, 'getLineHeightInPixels').andReturn(20) + spyOn(editor, 'setScrollTop') + spyOn(editor, 'getHeight').andReturn(200) + spyOn(editor, 'getFirstVisibleScreenRow').andReturn(90) + spyOn(editor, 'getLastVisibleScreenRow').andReturn(110) + + describe "the z<CR> keybinding", -> + keydownCodeForEnter = '\r' + + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the top of the window and moves cursor to first non-blank in the line", -> + keydown('z') + keydown(keydownCodeForEnter) + expect(editor.setScrollTop).toHaveBeenCalledWith(960) + expect(editor.moveToFirstCharacterOfLine).toHaveBeenCalled() + + describe "the zt keybinding", -> + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the top of the window and leave cursor in the same column", -> + keydown('z') + keydown('t') + expect(editor.setScrollTop).toHaveBeenCalledWith(960) + expect(editor.moveToFirstCharacterOfLine).not.toHaveBeenCalled() + + describe "the z. keybinding", -> + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the center of the window and moves cursor to first non-blank in the line", -> + keydown('z') + keydown('.') + expect(editor.setScrollTop).toHaveBeenCalledWith(900) + expect(editor.moveToFirstCharacterOfLine).toHaveBeenCalled() + + describe "the zz keybinding", -> + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the center of the window and leave cursor in the same column", -> + keydown('z') + keydown('z') + expect(editor.setScrollTop).toHaveBeenCalledWith(900) + expect(editor.moveToFirstCharacterOfLine).not.toHaveBeenCalled() + + describe "the z- keybinding", -> + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the bottom of the window and moves cursor to first non-blank in the line", -> + keydown('z') + keydown('-') + expect(editor.setScrollTop).toHaveBeenCalledWith(860) + expect(editor.moveToFirstCharacterOfLine).toHaveBeenCalled() + + describe "the zb keybinding", -> + beforeEach -> + spyOn(editor, 'pixelPositionForScreenPosition').andReturn({top: 1000, left: 0}) + + it "moves the screen to position cursor at the bottom of the window and leave cursor in the same column", -> + keydown('z') + keydown('b') + expect(editor.setScrollTop).toHaveBeenCalledWith(860) + expect(editor.moveToFirstCharacterOfLine).not.toHaveBeenCalled() diff --git a/atom/packages/vim-mode/spec/spec-helper.coffee b/atom/packages/vim-mode/spec/spec-helper.coffee new file mode 100644 index 0000000..88ba4b2 --- /dev/null +++ b/atom/packages/vim-mode/spec/spec-helper.coffee @@ -0,0 +1,66 @@ +VimState = require '../lib/vim-state' +GlobalVimState = require '../lib/global-vim-state' +VimMode = require '../lib/vim-mode' +StatusBarManager = require '../lib/status-bar-manager' + +beforeEach -> + atom.workspace ||= {} + +getEditorElement = (callback) -> + textEditor = null + + waitsForPromise -> + atom.project.open().then (e) -> + textEditor = e + + runs -> + element = document.createElement("atom-text-editor") + element.setModel(textEditor) + element.classList.add('vim-mode') + element.vimState = new VimState(element, new StatusBarManager, new GlobalVimState) + + element.addEventListener "keydown", (e) -> + atom.keymaps.handleKeyboardEvent(e) + + callback(element) + +mockPlatform = (editorElement, platform) -> + wrapper = document.createElement('div') + wrapper.className = platform + wrapper.appendChild(editorElement) + +unmockPlatform = (editorElement) -> + editorElement.parentNode.removeChild(editorElement) + +dispatchKeyboardEvent = (target, eventArgs...) -> + e = document.createEvent('KeyboardEvent') + e.initKeyboardEvent(eventArgs...) + # 0 is the default, and it's valid ASCII, but it's wrong. + Object.defineProperty(e, 'keyCode', get: -> undefined) if e.keyCode is 0 + target.dispatchEvent e + +dispatchTextEvent = (target, eventArgs...) -> + e = document.createEvent('TextEvent') + e.initTextEvent(eventArgs...) + target.dispatchEvent e + +keydown = (key, {element, ctrl, shift, alt, meta, raw}={}) -> + key = "U+#{key.charCodeAt(0).toString(16)}" unless key is 'escape' or raw? + element ||= document.activeElement + eventArgs = [ + true, # bubbles + true, # cancelable + null, # view + key, # key + 0, # location + ctrl, alt, shift, meta + ] + + canceled = not dispatchKeyboardEvent(element, 'keydown', eventArgs...) + dispatchKeyboardEvent(element, 'keypress', eventArgs...) + if not canceled + if dispatchTextEvent(element, 'textInput', eventArgs...) + element.value += key + dispatchKeyboardEvent(element, 'keyup', eventArgs...) + +module.exports = {keydown, getEditorElement, mockPlatform, unmockPlatform} diff --git a/atom/packages/vim-mode/spec/text-objects-spec.coffee b/atom/packages/vim-mode/spec/text-objects-spec.coffee new file mode 100644 index 0000000..6991dc7 --- /dev/null +++ b/atom/packages/vim-mode/spec/text-objects-spec.coffee @@ -0,0 +1,481 @@ +helpers = require './spec-helper' + +describe "TextObjects", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + commandModeInputKeydown = (key, opts = {}) -> + editor.commandModeInputView.editorElement.getModel().setText(key) + + describe "the 'iw' text object", -> + beforeEach -> + editor.setText("12345 abcde ABCDE") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('w') + + expect(editor.getText()).toBe "12345 ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + expect(vimState.getRegister('"').text).toBe "abcde" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "selects inside the current word in visual mode", -> + keydown('v') + keydown('i') + keydown('w') + + expect(editor.getSelectedScreenRange()).toEqual [[0, 6], [0, 11]] + + it "works with multiple cursors", -> + editor.addCursorAtBufferPosition([0, 1]) + keydown("v") + keydown("i") + keydown("w") + expect(editor.getSelectedBufferRanges()).toEqual [ + [[0, 6], [0, 11]] + [[0, 0], [0, 5]] + ] + + describe "the 'i(' text object", -> + beforeEach -> + editor.setText("( something in here and in (here) )") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('(') + expect(editor.getText()).toBe "()" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the current word in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('(') + expect(editor.getText()).toBe "( something in here and in () )" + expect(editor.getCursorScreenPosition()).toEqual [0, 28] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "works with multiple cursors", -> + editor.setText("( a b ) cde ( f g h ) ijk") + editor.setCursorBufferPosition([0, 2]) + editor.addCursorAtBufferPosition([0, 18]) + + keydown("v") + keydown("i") + keydown("(") + + expect(editor.getSelectedBufferRanges()).toEqual [ + [[0, 1], [0, 6]] + [[0, 13], [0, 20]] + ] + + describe "the 'i{' text object", -> + beforeEach -> + editor.setText("{ something in here and in {here} }") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('{') + expect(editor.getText()).toBe "{}" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the current word in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('{') + expect(editor.getText()).toBe "{ something in here and in {} }" + expect(editor.getCursorScreenPosition()).toEqual [0, 28] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'i<' text object", -> + beforeEach -> + editor.setText("< something in here and in <here> >") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('<') + expect(editor.getText()).toBe "<>" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the current word in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('<') + expect(editor.getText()).toBe "< something in here and in <> >" + expect(editor.getCursorScreenPosition()).toEqual [0, 28] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'it' text object", -> + beforeEach -> + editor.setText("<something>here</something><again>") + editor.setCursorScreenPosition([0, 5]) + + it "applies only if in the value of a tag", -> + keydown('d') + keydown('i') + keydown('t') + expect(editor.getText()).toBe "<something>here</something><again>" + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the current word in operator-pending mode", -> + editor.setCursorScreenPosition([0, 13]) + keydown('d') + keydown('i') + keydown('t') + expect(editor.getText()).toBe "<something></something><again>" + expect(editor.getCursorScreenPosition()).toEqual [0, 11] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'ip' text object", -> + beforeEach -> + editor.setText("\nParagraph-1\nParagraph-1\nParagraph-1\n\n") + editor.setCursorScreenPosition([2, 2]) + + it "applies operators inside the current paragraph in operator-pending mode", -> + + keydown('y') + keydown('i') + keydown('p') + + expect(editor.getText()).toBe "\nParagraph-1\nParagraph-1\nParagraph-1\n\n" + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + expect(vimState.getRegister('"').text).toBe "Paragraph-1\nParagraph-1\nParagraph-1\n" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "selects inside the current paragraph in visual mode", -> + keydown('v') + keydown('i') + keydown('p') + + expect(editor.getSelectedScreenRange()).toEqual [[1, 0], [4, 0]] + + describe "the 'ap' text object", -> + beforeEach -> + editor.setText("text\n\nParagraph-1\nParagraph-1\nParagraph-1\n\nmoretext") + editor.setCursorScreenPosition([3, 2]) + + it "applies operators around the current paragraph in operator-pending mode", -> + + keydown('y') + keydown('a') + keydown('p') + + expect(editor.getText()).toBe "text\n\nParagraph-1\nParagraph-1\nParagraph-1\n\nmoretext" + expect(editor.getCursorScreenPosition()).toEqual [2, 0] + expect(vimState.getRegister('"').text).toBe "Paragraph-1\nParagraph-1\nParagraph-1\n\n" + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "selects around the current paragraph in visual mode", -> + keydown('v') + keydown('a') + keydown('p') + + expect(editor.getSelectedScreenRange()).toEqual [[2, 0], [6, 0]] + + describe "the 'i[' text object", -> + beforeEach -> + editor.setText("[ something in here and in [here] ]") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current word in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('[') + expect(editor.getText()).toBe "[]" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the current word in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('[') + expect(editor.getText()).toBe "[ something in here and in [] ]" + expect(editor.getCursorScreenPosition()).toEqual [0, 28] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'i\'' text object", -> + beforeEach -> + editor.setText("' something in here and in 'here' ' and over here") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current string in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('\'') + expect(editor.getText()).toBe "''here' ' and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the next string in operator-pending mode (if not in a string)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('\'') + expect(editor.getText()).toBe "' something in here and in 'here'' and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 33] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "makes no change if past the last string on a line", -> + editor.setCursorScreenPosition([0, 39]) + keydown('d') + keydown('i') + keydown('\'') + expect(editor.getText()).toBe "' something in here and in 'here' ' and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 39] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'i\"' text object", -> + beforeEach -> + editor.setText("\" something in here and in \"here\" \" and over here") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators inside the current string in operator-pending mode", -> + keydown('d') + keydown('i') + keydown('"') + expect(editor.getText()).toBe "\"\"here\" \" and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 1] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators inside the next string in operator-pending mode (if not in a string)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('i') + keydown('"') + expect(editor.getText()).toBe "\" something in here and in \"here\"\" and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 33] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "makes no change if past the last string on a line", -> + editor.setCursorScreenPosition([0, 39]) + keydown('d') + keydown('i') + keydown('"') + expect(editor.getText()).toBe "\" something in here and in \"here\" \" and over here" + expect(editor.getCursorScreenPosition()).toEqual [0, 39] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + + describe "the 'aw' text object", -> + beforeEach -> + editor.setText("12345 abcde ABCDE") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators from the start of the current word to the start of the next word in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('w') + + expect(editor.getText()).toBe "12345 ABCDE" + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + expect(vimState.getRegister('"').text).toBe "abcde " + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "selects from the start of the current word to the start of the next word in visual mode", -> + keydown('v') + keydown('a') + keydown('w') + + expect(editor.getSelectedScreenRange()).toEqual [[0, 6], [0, 12]] + + it "doesn't span newlines", -> + editor.setText("12345\nabcde ABCDE") + editor.setCursorBufferPosition([0, 3]) + + keydown("v") + keydown("a") + keydown("w") + + expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 5]]] + + describe "the 'a(' text object", -> + beforeEach -> + editor.setText("( something in here and in (here) )") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current parentheses in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('(') + expect(editor.getText()).toBe "" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current parentheses in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('(') + expect(editor.getText()).toBe "( something in here and in )" + expect(editor.getCursorScreenPosition()).toEqual [0, 27] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'a{' text object", -> + beforeEach -> + editor.setText("{ something in here and in {here} }") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current curly brackets in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('{') + expect(editor.getText()).toBe "" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current curly brackets in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('{') + expect(editor.getText()).toBe "{ something in here and in }" + expect(editor.getCursorScreenPosition()).toEqual [0, 27] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'a<' text object", -> + beforeEach -> + editor.setText("< something in here and in <here> >") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current angle brackets in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('<') + expect(editor.getText()).toBe "" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current angle brackets in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('<') + expect(editor.getText()).toBe "< something in here and in >" + expect(editor.getCursorScreenPosition()).toEqual [0, 27] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'a[' text object", -> + beforeEach -> + editor.setText("[ something in here and in [here] ]") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current square brackets in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('[') + expect(editor.getText()).toBe "" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current square brackets in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('[') + expect(editor.getText()).toBe "[ something in here and in ]" + expect(editor.getCursorScreenPosition()).toEqual [0, 27] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'a\'' text object", -> + beforeEach -> + editor.setText("' something in here and in 'here' '") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current single quotes in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('\'') + expect(editor.getText()).toBe "here' '" + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current single quotes in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('\'') + expect(editor.getText()).toBe "' something in here and in 'here" + expect(editor.getCursorScreenPosition()).toEqual [0, 31] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "the 'a\"' text object", -> + beforeEach -> + editor.setText("\" something in here and in \"here\" \"") + editor.setCursorScreenPosition([0, 9]) + + it "applies operators around the current double quotes in operator-pending mode", -> + keydown('d') + keydown('a') + keydown('""') + expect(editor.getText()).toBe 'here" "' + expect(editor.getCursorScreenPosition()).toEqual [0, 0] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "applies operators around the current double quotes in operator-pending mode (second test)", -> + editor.setCursorScreenPosition([0, 29]) + keydown('d') + keydown('a') + keydown('"') + expect(editor.getText()).toBe "\" something in here and in \"here" + expect(editor.getCursorScreenPosition()).toEqual [0, 31] + expect(editorElement.classList.contains('operator-pending-mode')).toBe(false) + expect(editorElement.classList.contains('command-mode')).toBe(true) diff --git a/atom/packages/vim-mode/spec/vim-mode-spec.coffee b/atom/packages/vim-mode/spec/vim-mode-spec.coffee new file mode 100644 index 0000000..0f4d8b8 --- /dev/null +++ b/atom/packages/vim-mode/spec/vim-mode-spec.coffee @@ -0,0 +1,56 @@ +describe "VimMode", -> + [editor, editorElement, workspaceElement] = [] + + beforeEach -> + workspaceElement = atom.views.getView(atom.workspace) + + waitsForPromise -> + atom.workspace.open() + + waitsForPromise -> + atom.packages.activatePackage('vim-mode') + + waitsForPromise -> + atom.packages.activatePackage('status-bar') + + runs -> + editor = atom.workspace.getActiveTextEditor() + editorElement = atom.views.getView(editor) + + describe ".activate", -> + it "puts the editor in command-mode initially by default", -> + expect(editorElement.classList.contains('vim-mode')).toBe(true) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "shows the current vim mode in the status bar", -> + statusBarTile = workspaceElement.querySelector("#status-bar-vim-mode") + expect(statusBarTile.textContent).toBe("Command") + atom.commands.dispatch(editorElement, "vim-mode:activate-insert-mode") + expect(statusBarTile.textContent).toBe("Insert") + + it "doesn't register duplicate command listeners for editors", -> + editor.setText("12345") + editor.setCursorBufferPosition([0, 0]) + + pane = atom.workspace.getActivePane() + newPane = pane.splitRight() + pane.removeItem(editor) + newPane.addItem(editor) + + atom.commands.dispatch(editorElement, "vim-mode:move-right") + expect(editor.getCursorBufferPosition()).toEqual([0, 1]) + + describe ".deactivate", -> + it "removes the vim classes from the editor", -> + atom.packages.deactivatePackage('vim-mode') + expect(editorElement.classList.contains("vim-mode")).toBe(false) + expect(editorElement.classList.contains("command-mode")).toBe(false) + + it "removes the vim commands from the editor element", -> + vimCommands = -> + atom.commands.findCommands(target: editorElement).filter (cmd) -> + cmd.name.startsWith("vim-mode:") + + expect(vimCommands().length).toBeGreaterThan(0) + atom.packages.deactivatePackage('vim-mode') + expect(vimCommands().length).toBe(0) diff --git a/atom/packages/vim-mode/spec/vim-state-spec.coffee b/atom/packages/vim-mode/spec/vim-state-spec.coffee new file mode 100644 index 0000000..741c371 --- /dev/null +++ b/atom/packages/vim-mode/spec/vim-state-spec.coffee @@ -0,0 +1,420 @@ +_ = require 'underscore-plus' +helpers = require './spec-helper' +VimState = require '../lib/vim-state' +StatusBarManager = require '../lib/status-bar-manager' + +describe "VimState", -> + [editor, editorElement, vimState] = [] + + beforeEach -> + vimMode = atom.packages.loadPackage('vim-mode') + vimMode.activateResources() + + helpers.getEditorElement (element) -> + editorElement = element + editor = editorElement.getModel() + vimState = editorElement.vimState + vimState.activateCommandMode() + vimState.resetCommandMode() + + keydown = (key, options={}) -> + options.element ?= editorElement + helpers.keydown(key, options) + + commandModeInputKeydown = (key, opts = {}) -> + editor.commandModeInputView.editorElement.getModel().setText(key) + + describe "initialization", -> + it "puts the editor in command-mode initially by default", -> + expect(editorElement.classList.contains('vim-mode')).toBe(true) + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "puts the editor in insert-mode if startInInsertMode is true", -> + atom.config.set 'vim-mode.startInInsertMode', true + editor.vimState = new VimState(editorElement, new StatusBarManager) + expect(editorElement.classList.contains('insert-mode')).toBe(true) + + describe "::destroy", -> + it "re-enables text input on the editor", -> + expect(editorElement.component.isInputEnabled()).toBeFalsy() + vimState.destroy() + expect(editorElement.component.isInputEnabled()).toBeTruthy() + + it "removes the mode classes from the editor", -> + expect(editorElement.classList.contains("command-mode")).toBeTruthy() + vimState.destroy() + expect(editorElement.classList.contains("command-mode")).toBeFalsy() + + it "is a noop when the editor is already destroyed", -> + editorElement.getModel().destroy() + vimState.destroy() + + describe "command-mode", -> + describe "when entering an insertable character", -> + beforeEach -> keydown('\\') + + it "stops propagation", -> + expect(editor.getText()).toEqual '' + + describe "when entering an operator", -> + beforeEach -> keydown('d') + + describe "with an operator that can't be composed", -> + beforeEach -> keydown('x') + + it "clears the operator stack", -> + expect(vimState.opStack.length).toBe 0 + + describe "the escape keybinding", -> + beforeEach -> keydown('escape') + + it "clears the operator stack", -> + expect(vimState.opStack.length).toBe 0 + + describe "the ctrl-c keybinding", -> + beforeEach -> keydown('c', ctrl: true) + + it "clears the operator stack", -> + expect(vimState.opStack.length).toBe 0 + + describe "the escape keybinding", -> + it "clears any extra cursors", -> + editor.setText("one-two-three") + editor.addCursorAtBufferPosition([0, 3]) + expect(editor.getCursors().length).toBe 2 + keydown('escape') + expect(editor.getCursors().length).toBe 1 + + describe "the v keybinding", -> + beforeEach -> keydown('v') + + it "puts the editor into visual characterwise mode", -> + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'characterwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + describe "the V keybinding", -> + beforeEach -> + editor.setText("012345\nabcdef") + editor.setCursorScreenPosition([0, 0]) + keydown('V', shift: true) + + it "puts the editor into visual linewise mode", -> + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'linewise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + it "selects the current line", -> + expect(editor.getLastSelection().getText()).toEqual '012345\n' + + describe "the ctrl-v keybinding", -> + beforeEach -> keydown('v', ctrl: true) + + it "puts the editor into visual characterwise mode", -> + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'blockwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + describe "selecting text", -> + beforeEach -> + spyOn(_._, "now").andCallFake -> window.now + editor.setText("abc def") + + it "puts the editor into visual mode", -> + expect(vimState.mode).toEqual 'command' + editor.setSelectedBufferRanges([[[0, 0], [0, 3]]]) + + advanceClock(100) + + expect(vimState.mode).toEqual 'visual' + expect(vimState.submode).toEqual 'characterwise' + expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [0, 3]]]) + + it "handles the editor being destroyed shortly after selecting text", -> + editor.setSelectedBufferRanges([[[0, 0], [0, 3]]]) + editor.destroy() + vimState.destroy() + advanceClock(100) + + describe "the i keybinding", -> + beforeEach -> keydown('i') + + it "puts the editor into insert mode", -> + expect(editorElement.classList.contains('insert-mode')).toBe(true) + expect(editorElement.classList.contains('command-mode')).toBe(false) + + describe "with content", -> + beforeEach -> editor.setText("012345\n\nabcdef") + + # FIXME: See atom/vim-mode#2 + xdescribe "on a line with content", -> + beforeEach -> editor.setCursorScreenPosition([0, 6]) + + it "does not allow the cursor to be placed on the \n character", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 5] + + describe "on an empty line", -> + beforeEach -> editor.setCursorScreenPosition([1, 0]) + + it "allows the cursor to be placed on the \n character", -> + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe 'with character-input operations', -> + beforeEach -> editor.setText('012345\nabcdef') + + it 'properly clears the opStack', -> + keydown('d') + keydown('r') + expect(vimState.mode).toBe 'command' + expect(vimState.opStack.length).toBe 0 + atom.commands.dispatch(editor.commandModeInputView.editorElement, "core:cancel") + keydown('d') + expect(editor.getText()).toBe '012345\nabcdef' + + describe "insert-mode", -> + beforeEach -> + keydown('i') + + describe "with content", -> + beforeEach -> editor.setText("012345\n\nabcdef") + + describe "when cursor is in the middle of the line", -> + beforeEach -> editor.setCursorScreenPosition([0, 3]) + + it "moves the cursor to the left when exiting insert mode", -> + keydown('escape') + expect(editor.getCursorScreenPosition()).toEqual [0, 2] + + describe "when cursor is at the beginning of line", -> + beforeEach -> editor.setCursorScreenPosition([1, 0]) + + it "leaves the cursor at the beginning of line", -> + keydown('escape') + expect(editor.getCursorScreenPosition()).toEqual [1, 0] + + describe "on a line with content", -> + beforeEach -> editor.setCursorScreenPosition([0, 6]) + + it "allows the cursor to be placed on the \n character", -> + expect(editor.getCursorScreenPosition()).toEqual [0, 6] + + it "puts the editor into command mode when <escape> is pressed", -> + keydown('escape') + + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editorElement.classList.contains('insert-mode')).toBe(false) + expect(editorElement.classList.contains('visual-mode')).toBe(false) + + it "puts the editor into command mode when <ctrl-c> is pressed", -> + helpers.mockPlatform(editorElement, 'platform-darwin') + keydown('c', ctrl: true) + helpers.unmockPlatform(editorElement) + + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editorElement.classList.contains('insert-mode')).toBe(false) + expect(editorElement.classList.contains('visual-mode')).toBe(false) + + describe "visual-mode", -> + beforeEach -> + editor.setText("one two three") + editor.setCursorBufferPosition([0, 4]) + keydown('v') + + it "selects the character under the cursor", -> + expect(editor.getSelectedBufferRanges()).toEqual [[[0, 4], [0, 5]]] + expect(editor.getSelectedText()).toBe("t") + + it "puts the editor into command mode when <escape> is pressed", -> + keydown('escape') + + expect(editor.getCursorBufferPositions()).toEqual [[0, 4]] + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editorElement.classList.contains('visual-mode')).toBe(false) + + it "puts the editor into command mode when <escape> is pressed on selection is reversed", -> + expect(editor.getSelectedText()).toBe("t") + keydown("h") + keydown("h") + expect(editor.getSelectedText()).toBe("e t") + expect(editor.getLastSelection().isReversed()).toBe(true) + keydown('escape') + expect(editorElement.classList.contains('command-mode')).toBe(true) + expect(editor.getCursorBufferPositions()).toEqual [[0, 2]] + + describe "motions", -> + it "transforms the selection", -> + keydown('w') + expect(editor.getLastSelection().getText()).toEqual 'two t' + + it "always leaves the initially selected character selected", -> + keydown("h") + expect(editor.getSelectedText()).toBe(" t") + + keydown("l") + expect(editor.getSelectedText()).toBe("t") + + keydown("l") + keydown("l") + expect(editor.getSelectedText()).toBe("tw") + + describe "operators", -> + beforeEach -> + editor.setText("012345\n\nabcdef") + editor.setCursorScreenPosition([0, 0]) + editor.selectLinesContainingCursors() + keydown('d') + + it "operate on the current selection", -> + expect(editor.getText()).toEqual "\nabcdef" + + describe "returning to command-mode", -> + beforeEach -> + editor.setText("012345\n\nabcdef") + editor.selectLinesContainingCursors() + keydown('escape') + + it "operate on the current selection", -> + expect(editor.getLastSelection().getText()).toEqual '' + + describe "the o keybinding", -> + it "reversed each selection", -> + editor.addCursorAtBufferPosition([0, Infinity]) + keydown("i") + keydown("w") + + expect(editor.getSelectedBufferRanges()).toEqual([ + [[0, 4], [0, 7]], + [[0, 8], [0, 13]] + ]) + expect(editor.getCursorBufferPositions()).toEqual([ + [0, 7] + [0, 13] + ]) + + keydown("o") + + expect(editor.getSelectedBufferRanges()).toEqual([ + [[0, 4], [0, 7]], + [[0, 8], [0, 13]] + ]) + expect(editor.getCursorBufferPositions()).toEqual([ + [0, 4] + [0, 8] + ]) + + describe "activate visualmode witin visualmode", -> + beforeEach -> + keydown('escape') + expect(vimState.mode).toEqual 'command' + expect(editorElement.classList.contains('command-mode')).toBe(true) + + it "activateVisualMode with same type puts the editor into command mode", -> + keydown('v') + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'characterwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('v') + expect(vimState.mode).toEqual 'command' + expect(editorElement.classList.contains('command-mode')).toBe(true) + + keydown('V', shift: true) + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'linewise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('V', shift: true) + expect(vimState.mode).toEqual 'command' + expect(editorElement.classList.contains('command-mode')).toBe(true) + + keydown('v', ctrl: true) + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'blockwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('v', ctrl: true) + expect(vimState.mode).toEqual 'command' + expect(editorElement.classList.contains('command-mode')).toBe(true) + + describe "change submode within visualmode", -> + beforeEach -> + editor.setText("line one\nline two\nline three\n") + editor.setCursorBufferPosition([0, 5]) + editor.addCursorAtBufferPosition([2, 5]) + + it "can change submode within visual mode", -> + keydown('v') + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'characterwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('V', shift: true) + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'linewise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('v', ctrl: true) + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'blockwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + keydown('v') + expect(editorElement.classList.contains('visual-mode')).toBe(true) + expect(vimState.submode).toEqual 'characterwise' + expect(editorElement.classList.contains('command-mode')).toBe(false) + + + it "recover original range when shift from linewse to characterwise", -> + keydown('v') + keydown('i') + keydown('w') + + expect(_.map(editor.getSelections(), (selection) -> + selection.getText()) + ).toEqual(['one', 'three']) + + keydown('V', shift: true) + expect(_.map(editor.getSelections(), (selection) -> + selection.getText()) + ).toEqual(["line one\n", "line three\n"]) + + keydown('v', ctrl: true) + expect(_.map(editor.getSelections(), (selection) -> + selection.getText()) + ).toEqual(['one', 'three']) + + describe "marks", -> + beforeEach -> editor.setText("text in line 1\ntext in line 2\ntext in line 3") + + it "basic marking functionality", -> + editor.setCursorScreenPosition([1, 1]) + keydown('m') + commandModeInputKeydown('t') + expect(editor.getText()).toEqual "text in line 1\ntext in line 2\ntext in line 3" + editor.setCursorScreenPosition([2, 2]) + keydown('`') + commandModeInputKeydown('t') + expect(editor.getCursorScreenPosition()).toEqual [1, 1] + + it "real (tracking) marking functionality", -> + editor.setCursorScreenPosition([2, 2]) + keydown('m') + commandModeInputKeydown('q') + editor.setCursorScreenPosition([1, 2]) + keydown('o') + keydown('escape') + keydown('`') + commandModeInputKeydown('q') + expect(editor.getCursorScreenPosition()).toEqual [3, 2] + + it "real (tracking) marking functionality", -> + editor.setCursorScreenPosition([2, 2]) + keydown('m') + commandModeInputKeydown('q') + editor.setCursorScreenPosition([1, 2]) + keydown('d') + keydown('d') + keydown('escape') + keydown('`') + commandModeInputKeydown('q') + expect(editor.getCursorScreenPosition()).toEqual [1, 2] |