1 _ = require 'underscore-plus'
2 helpers = require './spec-helper'
3 VimState = require '../lib/vim-state'
4 StatusBarManager = require '../lib/status-bar-manager'
6 describe "VimState", ->
7 [editor, editorElement, vimState] = []
10 vimMode = atom.packages.loadPackage('vim-mode')
11 vimMode.activateResources()
13 helpers.getEditorElement (element) ->
14 editorElement = element
15 editor = editorElement.getModel()
16 vimState = editorElement.vimState
17 vimState.activateNormalMode()
18 vimState.resetNormalMode()
20 keydown = (key, options={}) ->
21 options.element ?= editorElement
22 helpers.keydown(key, options)
24 normalModeInputKeydown = (key, opts = {}) ->
25 editor.normalModeInputView.editorElement.getModel().setText(key)
27 describe "initialization", ->
28 it "puts the editor in normal-mode initially by default", ->
29 expect(editorElement.classList.contains('vim-mode')).toBe(true)
30 expect(editorElement.classList.contains('normal-mode')).toBe(true)
32 it "puts the editor in insert-mode if startInInsertMode is true", ->
33 atom.config.set 'vim-mode.startInInsertMode', true
34 editor.vimState = new VimState(editorElement, new StatusBarManager)
35 expect(editorElement.classList.contains('insert-mode')).toBe(true)
37 describe "::destroy", ->
38 it "re-enables text input on the editor", ->
39 expect(editorElement.component.isInputEnabled()).toBeFalsy()
41 expect(editorElement.component.isInputEnabled()).toBeTruthy()
43 it "removes the mode classes from the editor", ->
44 expect(editorElement.classList.contains("normal-mode")).toBeTruthy()
46 expect(editorElement.classList.contains("normal-mode")).toBeFalsy()
48 it "is a noop when the editor is already destroyed", ->
49 editorElement.getModel().destroy()
52 describe "normal-mode", ->
53 describe "when entering an insertable character", ->
54 beforeEach -> keydown('\\')
56 it "stops propagation", ->
57 expect(editor.getText()).toEqual ''
59 describe "when entering an operator", ->
60 beforeEach -> keydown('d')
62 describe "with an operator that can't be composed", ->
63 beforeEach -> keydown('x')
65 it "clears the operator stack", ->
66 expect(vimState.opStack.length).toBe 0
68 describe "the escape keybinding", ->
69 beforeEach -> keydown('escape')
71 it "clears the operator stack", ->
72 expect(vimState.opStack.length).toBe 0
74 describe "the ctrl-c keybinding", ->
75 beforeEach -> keydown('c', ctrl: true)
77 it "clears the operator stack", ->
78 expect(vimState.opStack.length).toBe 0
80 describe "the escape keybinding", ->
81 it "clears any extra cursors", ->
82 editor.setText("one-two-three")
83 editor.addCursorAtBufferPosition([0, 3])
84 expect(editor.getCursors().length).toBe 2
86 expect(editor.getCursors().length).toBe 1
88 describe "the v keybinding", ->
90 editor.setText("012345\nabcdef")
91 editor.setCursorScreenPosition([0, 0])
94 it "puts the editor into visual characterwise mode", ->
95 expect(editorElement.classList.contains('visual-mode')).toBe(true)
96 expect(vimState.submode).toEqual 'characterwise'
97 expect(editorElement.classList.contains('normal-mode')).toBe(false)
99 it "selects the current character", ->
100 expect(editor.getLastSelection().getText()).toEqual '0'
102 describe "the V keybinding", ->
104 editor.setText("012345\nabcdef")
105 editor.setCursorScreenPosition([0, 0])
106 keydown('V', shift: true)
108 it "puts the editor into visual linewise mode", ->
109 expect(editorElement.classList.contains('visual-mode')).toBe(true)
110 expect(vimState.submode).toEqual 'linewise'
111 expect(editorElement.classList.contains('normal-mode')).toBe(false)
113 it "selects the current line", ->
114 expect(editor.getLastSelection().getText()).toEqual '012345\n'
116 describe "the ctrl-v keybinding", ->
118 editor.setText("012345\nabcdef")
119 editor.setCursorScreenPosition([0, 0])
120 keydown('v', ctrl: true)
122 it "puts the editor into visual blockwise mode", ->
123 expect(editorElement.classList.contains('visual-mode')).toBe(true)
124 expect(vimState.submode).toEqual 'blockwise'
125 expect(editorElement.classList.contains('normal-mode')).toBe(false)
127 describe "selecting text", ->
129 editor.setText("abc def")
130 editor.setCursorScreenPosition([0, 0])
132 it "puts the editor into visual mode", ->
133 expect(vimState.mode).toEqual 'normal'
134 atom.commands.dispatch(editorElement, "core:select-right")
136 expect(vimState.mode).toEqual 'visual'
137 expect(vimState.submode).toEqual 'characterwise'
138 expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [0, 1]]])
140 it "handles the editor being destroyed shortly after selecting text", ->
141 editor.setSelectedBufferRanges([[[0, 0], [0, 3]]])
146 it "handles native selection such as core:select-all", ->
147 atom.commands.dispatch(editorElement, "core:select-all")
148 expect(editor.getSelectedBufferRanges()).toEqual([[[0, 0], [0, 7]]])
150 describe "the i keybinding", ->
151 beforeEach -> keydown('i')
153 it "puts the editor into insert mode", ->
154 expect(editorElement.classList.contains('insert-mode')).toBe(true)
155 expect(editorElement.classList.contains('normal-mode')).toBe(false)
157 describe "the R keybinding", ->
158 beforeEach -> keydown('R', shift: true)
160 it "puts the editor into replace mode", ->
161 expect(editorElement.classList.contains('insert-mode')).toBe(true)
162 expect(editorElement.classList.contains('replace-mode')).toBe(true)
163 expect(editorElement.classList.contains('normal-mode')).toBe(false)
165 describe "with content", ->
167 editor.setText("012345\n\nabcdef")
168 editor.setCursorScreenPosition([0, 0])
170 describe "on a line with content", ->
171 it "does not allow atom commands to place the cursor on the \\n character", ->
172 atom.commands.dispatch(editorElement, "editor:move-to-end-of-line")
173 expect(editor.getCursorScreenPosition()).toEqual [0, 5]
175 describe "on an empty line", ->
177 editor.setCursorScreenPosition([1, 0])
178 atom.commands.dispatch(editorElement, "editor:move-to-end-of-line")
180 it "allows the cursor to be placed on the \\n character", ->
181 expect(editor.getCursorScreenPosition()).toEqual [1, 0]
183 describe 'with character-input operations', ->
184 beforeEach -> editor.setText('012345\nabcdef')
186 it 'properly clears the opStack', ->
189 expect(vimState.mode).toBe 'normal'
190 expect(vimState.opStack.length).toBe 0
191 atom.commands.dispatch(editor.normalModeInputView.editorElement, "core:cancel")
193 expect(editor.getText()).toBe '012345\nabcdef'
195 describe "insert-mode", ->
199 describe "with content", ->
200 beforeEach -> editor.setText("012345\n\nabcdef")
202 describe "when cursor is in the middle of the line", ->
203 beforeEach -> editor.setCursorScreenPosition([0, 3])
205 it "moves the cursor to the left when exiting insert mode", ->
207 expect(editor.getCursorScreenPosition()).toEqual [0, 2]
209 describe "when cursor is at the beginning of line", ->
210 beforeEach -> editor.setCursorScreenPosition([1, 0])
212 it "leaves the cursor at the beginning of line", ->
214 expect(editor.getCursorScreenPosition()).toEqual [1, 0]
216 describe "on a line with content", ->
218 editor.setCursorScreenPosition([0, 0])
219 atom.commands.dispatch(editorElement, "editor:move-to-end-of-line")
221 it "allows the cursor to be placed on the \\n character", ->
222 expect(editor.getCursorScreenPosition()).toEqual [0, 6]
224 it "puts the editor into normal mode when <escape> is pressed", ->
227 expect(editorElement.classList.contains('normal-mode')).toBe(true)
228 expect(editorElement.classList.contains('insert-mode')).toBe(false)
229 expect(editorElement.classList.contains('visual-mode')).toBe(false)
231 it "puts the editor into normal mode when <ctrl-c> is pressed", ->
232 helpers.mockPlatform(editorElement, 'platform-darwin')
233 keydown('c', ctrl: true)
234 helpers.unmockPlatform(editorElement)
236 expect(editorElement.classList.contains('normal-mode')).toBe(true)
237 expect(editorElement.classList.contains('insert-mode')).toBe(false)
238 expect(editorElement.classList.contains('visual-mode')).toBe(false)
240 describe "replace-mode", ->
241 describe "with content", ->
242 beforeEach -> editor.setText("012345\n\nabcdef")
244 describe "when cursor is in the middle of the line", ->
246 editor.setCursorScreenPosition([0, 3])
247 keydown('R', shift: true)
249 it "moves the cursor to the left when exiting replace mode", ->
251 expect(editor.getCursorScreenPosition()).toEqual [0, 2]
253 describe "when cursor is at the beginning of line", ->
255 editor.setCursorScreenPosition([1, 0])
256 keydown('R', shift: true)
258 it "leaves the cursor at the beginning of line", ->
260 expect(editor.getCursorScreenPosition()).toEqual [1, 0]
262 describe "on a line with content", ->
264 keydown('R', shift: true)
265 editor.setCursorScreenPosition([0, 0])
266 atom.commands.dispatch(editorElement, "editor:move-to-end-of-line")
268 it "allows the cursor to be placed on the \\n character", ->
269 expect(editor.getCursorScreenPosition()).toEqual [0, 6]
271 it "puts the editor into normal mode when <escape> is pressed", ->
272 keydown('R', shift: true)
275 expect(editorElement.classList.contains('normal-mode')).toBe(true)
276 expect(editorElement.classList.contains('insert-mode')).toBe(false)
277 expect(editorElement.classList.contains('replace-mode')).toBe(false)
278 expect(editorElement.classList.contains('visual-mode')).toBe(false)
280 it "puts the editor into normal mode when <ctrl-c> is pressed", ->
281 keydown('R', shift: true)
282 helpers.mockPlatform(editorElement, 'platform-darwin')
283 keydown('c', ctrl: true)
284 helpers.unmockPlatform(editorElement)
286 expect(editorElement.classList.contains('normal-mode')).toBe(true)
287 expect(editorElement.classList.contains('insert-mode')).toBe(false)
288 expect(editorElement.classList.contains('replace-mode')).toBe(false)
289 expect(editorElement.classList.contains('visual-mode')).toBe(false)
291 describe "visual-mode", ->
293 editor.setText("one two three")
294 editor.setCursorBufferPosition([0, 4])
297 it "selects the character under the cursor", ->
298 expect(editor.getSelectedBufferRanges()).toEqual [[[0, 4], [0, 5]]]
299 expect(editor.getSelectedText()).toBe("t")
301 it "puts the editor into normal mode when <escape> is pressed", ->
304 expect(editor.getCursorBufferPositions()).toEqual [[0, 4]]
305 expect(editorElement.classList.contains('normal-mode')).toBe(true)
306 expect(editorElement.classList.contains('visual-mode')).toBe(false)
308 it "puts the editor into normal mode when <escape> is pressed on selection is reversed", ->
309 expect(editor.getSelectedText()).toBe("t")
312 expect(editor.getSelectedText()).toBe("e t")
313 expect(editor.getLastSelection().isReversed()).toBe(true)
315 expect(editorElement.classList.contains('normal-mode')).toBe(true)
316 expect(editor.getCursorBufferPositions()).toEqual [[0, 2]]
318 describe "motions", ->
319 it "transforms the selection", ->
321 expect(editor.getLastSelection().getText()).toEqual 'two t'
323 it "always leaves the initially selected character selected", ->
325 expect(editor.getSelectedText()).toBe(" t")
328 expect(editor.getSelectedText()).toBe("t")
331 expect(editor.getSelectedText()).toBe("tw")
333 describe "operators", ->
335 editor.setText("012345\n\nabcdef")
336 editor.setCursorScreenPosition([0, 0])
337 editor.selectLinesContainingCursors()
340 it "operate on the current selection", ->
341 expect(editor.getText()).toEqual "\nabcdef"
343 describe "returning to normal-mode", ->
345 editor.setText("012345\n\nabcdef")
346 editor.selectLinesContainingCursors()
349 it "operate on the current selection", ->
350 expect(editor.getLastSelection().getText()).toEqual ''
352 describe "the o keybinding", ->
353 it "reversed each selection", ->
354 editor.addCursorAtBufferPosition([0, Infinity])
358 expect(editor.getSelectedBufferRanges()).toEqual([
362 expect(editor.getCursorBufferPositions()).toEqual([
369 expect(editor.getSelectedBufferRanges()).toEqual([
373 expect(editor.getCursorBufferPositions()).toEqual([
378 it "harmonizes selection directions", ->
380 editor.addCursorAtBufferPosition([0, Infinity])
384 expect(editor.getSelectedBufferRanges()).toEqual([
388 expect(editor.getCursorBufferPositions()).toEqual([
395 expect(editor.getSelectedBufferRanges()).toEqual([
399 expect(editor.getCursorBufferPositions()).toEqual([
404 describe "activate visualmode witin visualmode", ->
407 expect(vimState.mode).toEqual 'normal'
408 expect(editorElement.classList.contains('normal-mode')).toBe(true)
410 it "activateVisualMode with same type puts the editor into normal mode", ->
412 expect(editorElement.classList.contains('visual-mode')).toBe(true)
413 expect(vimState.submode).toEqual 'characterwise'
414 expect(editorElement.classList.contains('normal-mode')).toBe(false)
417 expect(vimState.mode).toEqual 'normal'
418 expect(editorElement.classList.contains('normal-mode')).toBe(true)
420 keydown('V', shift: true)
421 expect(editorElement.classList.contains('visual-mode')).toBe(true)
422 expect(vimState.submode).toEqual 'linewise'
423 expect(editorElement.classList.contains('normal-mode')).toBe(false)
425 keydown('V', shift: true)
426 expect(vimState.mode).toEqual 'normal'
427 expect(editorElement.classList.contains('normal-mode')).toBe(true)
429 keydown('v', ctrl: true)
430 expect(editorElement.classList.contains('visual-mode')).toBe(true)
431 expect(vimState.submode).toEqual 'blockwise'
432 expect(editorElement.classList.contains('normal-mode')).toBe(false)
434 keydown('v', ctrl: true)
435 expect(vimState.mode).toEqual 'normal'
436 expect(editorElement.classList.contains('normal-mode')).toBe(true)
438 describe "change submode within visualmode", ->
440 editor.setText("line one\nline two\nline three\n")
441 editor.setCursorBufferPosition([0, 5])
442 editor.addCursorAtBufferPosition([2, 5])
444 it "can change submode within visual mode", ->
446 expect(editorElement.classList.contains('visual-mode')).toBe(true)
447 expect(vimState.submode).toEqual 'characterwise'
448 expect(editorElement.classList.contains('normal-mode')).toBe(false)
450 keydown('V', shift: true)
451 expect(editorElement.classList.contains('visual-mode')).toBe(true)
452 expect(vimState.submode).toEqual 'linewise'
453 expect(editorElement.classList.contains('normal-mode')).toBe(false)
455 keydown('v', ctrl: true)
456 expect(editorElement.classList.contains('visual-mode')).toBe(true)
457 expect(vimState.submode).toEqual 'blockwise'
458 expect(editorElement.classList.contains('normal-mode')).toBe(false)
461 expect(editorElement.classList.contains('visual-mode')).toBe(true)
462 expect(vimState.submode).toEqual 'characterwise'
463 expect(editorElement.classList.contains('normal-mode')).toBe(false)
466 it "recover original range when shift from linewse to characterwise", ->
471 expect(_.map(editor.getSelections(), (selection) ->
473 ).toEqual(['one', 'three'])
475 keydown('V', shift: true)
476 expect(_.map(editor.getSelections(), (selection) ->
478 ).toEqual(["line one\n", "line three\n"])
480 keydown('v', ctrl: true)
481 expect(_.map(editor.getSelections(), (selection) ->
483 ).toEqual(['one', 'three'])
486 beforeEach -> editor.setText("text in line 1\ntext in line 2\ntext in line 3")
488 it "basic marking functionality", ->
489 editor.setCursorScreenPosition([1, 1])
491 normalModeInputKeydown('t')
492 expect(editor.getText()).toEqual "text in line 1\ntext in line 2\ntext in line 3"
493 editor.setCursorScreenPosition([2, 2])
495 normalModeInputKeydown('t')
496 expect(editor.getCursorScreenPosition()).toEqual [1, 1]
498 it "real (tracking) marking functionality", ->
499 editor.setCursorScreenPosition([2, 2])
501 normalModeInputKeydown('q')
502 editor.setCursorScreenPosition([1, 2])
506 normalModeInputKeydown('q')
507 expect(editor.getCursorScreenPosition()).toEqual [3, 2]
509 it "real (tracking) marking functionality", ->
510 editor.setCursorScreenPosition([2, 2])
512 normalModeInputKeydown('q')
513 editor.setCursorScreenPosition([1, 2])
518 normalModeInputKeydown('q')
519 expect(editor.getCursorScreenPosition()).toEqual [1, 2]