985 lines
35 KiB
CoffeeScript
985 lines
35 KiB
CoffeeScript
fs = require 'fs-plus'
|
|
path = require 'path'
|
|
os = require 'os'
|
|
uuid = require 'node-uuid'
|
|
helpers = require './spec-helper'
|
|
|
|
ExClass = require('../lib/ex')
|
|
Ex = ExClass.singleton()
|
|
|
|
describe "the commands", ->
|
|
[editor, editorElement, vimState, exState, dir, dir2] = []
|
|
projectPath = (fileName) -> path.join(dir, fileName)
|
|
beforeEach ->
|
|
vimMode = atom.packages.loadPackage('vim-mode-plus')
|
|
exMode = atom.packages.loadPackage('ex-mode')
|
|
waitsForPromise ->
|
|
activationPromise = exMode.activate()
|
|
helpers.activateExMode()
|
|
activationPromise
|
|
|
|
runs ->
|
|
spyOn(exMode.mainModule.globalExState, 'setVim').andCallThrough()
|
|
|
|
waitsForPromise ->
|
|
vimMode.activate()
|
|
|
|
waitsFor ->
|
|
exMode.mainModule.globalExState.setVim.calls.length > 0
|
|
|
|
runs ->
|
|
dir = path.join(os.tmpdir(), "atom-ex-mode-spec-#{uuid.v4()}")
|
|
dir2 = path.join(os.tmpdir(), "atom-ex-mode-spec-#{uuid.v4()}")
|
|
fs.makeTreeSync(dir)
|
|
fs.makeTreeSync(dir2)
|
|
atom.project.setPaths([dir, dir2])
|
|
|
|
helpers.getEditorElement (element) ->
|
|
atom.commands.dispatch(element, "ex-mode:open")
|
|
atom.commands.dispatch(element.getModel().normalModeInputView.editorElement,
|
|
"core:cancel")
|
|
editorElement = element
|
|
editor = editorElement.getModel()
|
|
vimState = vimMode.mainModule.getEditorState(editor)
|
|
exState = exMode.mainModule.exStates.get(editor)
|
|
vimState.resetNormalMode()
|
|
editor.setText("abc\ndef\nabc\ndef")
|
|
|
|
afterEach ->
|
|
fs.removeSync(dir)
|
|
fs.removeSync(dir2)
|
|
|
|
keydown = (key, options={}) ->
|
|
options.element ?= editorElement
|
|
helpers.keydown(key, options)
|
|
|
|
normalModeInputKeydown = (key, opts = {}) ->
|
|
editor.normalModeInputView.editorElement.getModel().setText(key)
|
|
|
|
submitNormalModeInputText = (text) ->
|
|
commandEditor = editor.normalModeInputView.editorElement
|
|
commandEditor.getModel().setText(text)
|
|
atom.commands.dispatch(commandEditor, "core:confirm")
|
|
|
|
openEx = ->
|
|
atom.commands.dispatch(editorElement, "ex-mode:open")
|
|
|
|
describe "as a motion", ->
|
|
beforeEach ->
|
|
editor.setCursorBufferPosition([0, 0])
|
|
|
|
it "moves the cursor to a specific line", ->
|
|
openEx()
|
|
submitNormalModeInputText '2'
|
|
|
|
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
|
|
|
|
it "moves to the second address", ->
|
|
openEx()
|
|
submitNormalModeInputText '1,3'
|
|
|
|
expect(editor.getCursorBufferPosition()).toEqual [2, 0]
|
|
|
|
it "works with offsets", ->
|
|
openEx()
|
|
submitNormalModeInputText '2+1'
|
|
expect(editor.getCursorBufferPosition()).toEqual [2, 0]
|
|
|
|
openEx()
|
|
submitNormalModeInputText '-2'
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
it "limits to the last line", ->
|
|
openEx()
|
|
submitNormalModeInputText '10'
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
editor.setCursorBufferPosition([0, 0])
|
|
|
|
openEx()
|
|
submitNormalModeInputText '3,10'
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
editor.setCursorBufferPosition([0, 0])
|
|
|
|
openEx()
|
|
submitNormalModeInputText '$+1000'
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
editor.setCursorBufferPosition([0, 0])
|
|
|
|
it "goes to the first line with address 0", ->
|
|
editor.setCursorBufferPosition([2, 0])
|
|
openEx()
|
|
submitNormalModeInputText '0'
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
editor.setCursorBufferPosition([2, 0])
|
|
openEx()
|
|
submitNormalModeInputText '0,0'
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
it "doesn't move when the address is the current line", ->
|
|
openEx()
|
|
submitNormalModeInputText '.'
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
openEx()
|
|
submitNormalModeInputText ','
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
it "moves to the last line", ->
|
|
openEx()
|
|
submitNormalModeInputText '$'
|
|
expect(editor.getCursorBufferPosition()).toEqual [3, 0]
|
|
|
|
it "moves to a mark's line", ->
|
|
keydown('l')
|
|
keydown('m')
|
|
normalModeInputKeydown 'a'
|
|
keydown('j')
|
|
openEx()
|
|
submitNormalModeInputText "'a"
|
|
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
|
|
|
it "moves to a specified search", ->
|
|
openEx()
|
|
submitNormalModeInputText '/def'
|
|
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
|
|
|
|
editor.setCursorBufferPosition([2, 0])
|
|
openEx()
|
|
submitNormalModeInputText '?def'
|
|
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
|
|
|
|
editor.setCursorBufferPosition([3, 0])
|
|
openEx()
|
|
submitNormalModeInputText '/ef'
|
|
expect(editor.getCursorBufferPosition()).toEqual [1, 0]
|
|
|
|
describe ":write", ->
|
|
describe "when editing a new file", ->
|
|
beforeEach ->
|
|
editor.getBuffer().setText('abc\ndef')
|
|
|
|
it "opens the save dialog", ->
|
|
spyOn(atom, 'showSaveDialogSync')
|
|
openEx()
|
|
submitNormalModeInputText('write')
|
|
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
|
|
|
it "saves when a path is specified in the save dialog", ->
|
|
filePath = projectPath('write-from-save-dialog')
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('write')
|
|
expect(fs.existsSync(filePath)).toBe(true)
|
|
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef')
|
|
expect(editor.isModified()).toBe(false)
|
|
|
|
it "saves when a path is specified in the save dialog", ->
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
|
|
spyOn(fs, 'writeFileSync')
|
|
openEx()
|
|
submitNormalModeInputText('write')
|
|
expect(fs.writeFileSync.calls.length).toBe(0)
|
|
|
|
describe "when editing an existing file", ->
|
|
filePath = ''
|
|
i = 0
|
|
|
|
beforeEach ->
|
|
i++
|
|
filePath = projectPath("write-#{i}")
|
|
editor.setText('abc\ndef')
|
|
editor.saveAs(filePath)
|
|
|
|
it "saves the file", ->
|
|
editor.setText('abc')
|
|
openEx()
|
|
submitNormalModeInputText('write')
|
|
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc')
|
|
expect(editor.isModified()).toBe(false)
|
|
|
|
describe "with a specified path", ->
|
|
newPath = ''
|
|
|
|
beforeEach ->
|
|
newPath = path.relative(dir, "#{filePath}.new")
|
|
editor.getBuffer().setText('abc')
|
|
openEx()
|
|
|
|
afterEach ->
|
|
submitNormalModeInputText("write #{newPath}")
|
|
newPath = path.resolve(dir, fs.normalize(newPath))
|
|
expect(fs.existsSync(newPath)).toBe(true)
|
|
expect(fs.readFileSync(newPath, 'utf-8')).toEqual('abc')
|
|
expect(editor.isModified()).toBe(true)
|
|
fs.removeSync(newPath)
|
|
|
|
it "saves to the path", ->
|
|
|
|
it "expands .", ->
|
|
newPath = path.join('.', newPath)
|
|
|
|
it "expands ..", ->
|
|
newPath = path.join('..', newPath)
|
|
|
|
it "expands ~", ->
|
|
newPath = path.join('~', newPath)
|
|
|
|
it "throws an error with more than one path", ->
|
|
openEx()
|
|
submitNormalModeInputText('write path1 path2')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: Only one file name allowed'
|
|
)
|
|
|
|
describe "when the file already exists", ->
|
|
existsPath = ''
|
|
|
|
beforeEach ->
|
|
existsPath = projectPath('write-exists')
|
|
fs.writeFileSync(existsPath, 'abc')
|
|
|
|
afterEach ->
|
|
fs.removeSync(existsPath)
|
|
|
|
it "throws an error if the file already exists", ->
|
|
openEx()
|
|
submitNormalModeInputText("write #{existsPath}")
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: File exists (add ! to override)'
|
|
)
|
|
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
|
|
|
|
it "writes if forced with :write!", ->
|
|
openEx()
|
|
submitNormalModeInputText("write! #{existsPath}")
|
|
expect(atom.notifications.notifications).toEqual([])
|
|
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
|
|
|
|
describe ":wall", ->
|
|
it "saves all", ->
|
|
spyOn(atom.workspace, 'saveAll')
|
|
openEx()
|
|
submitNormalModeInputText('wall')
|
|
expect(atom.workspace.saveAll).toHaveBeenCalled()
|
|
|
|
describe ":saveas", ->
|
|
describe "when editing a new file", ->
|
|
beforeEach ->
|
|
editor.getBuffer().setText('abc\ndef')
|
|
|
|
it "opens the save dialog", ->
|
|
spyOn(atom, 'showSaveDialogSync')
|
|
openEx()
|
|
submitNormalModeInputText('saveas')
|
|
expect(atom.showSaveDialogSync).toHaveBeenCalled()
|
|
|
|
it "saves when a path is specified in the save dialog", ->
|
|
filePath = projectPath('saveas-from-save-dialog')
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('saveas')
|
|
expect(fs.existsSync(filePath)).toBe(true)
|
|
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef')
|
|
|
|
it "saves when a path is specified in the save dialog", ->
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
|
|
spyOn(fs, 'writeFileSync')
|
|
openEx()
|
|
submitNormalModeInputText('saveas')
|
|
expect(fs.writeFileSync.calls.length).toBe(0)
|
|
|
|
describe "when editing an existing file", ->
|
|
filePath = ''
|
|
i = 0
|
|
|
|
beforeEach ->
|
|
i++
|
|
filePath = projectPath("saveas-#{i}")
|
|
editor.setText('abc\ndef')
|
|
editor.saveAs(filePath)
|
|
|
|
it "complains if no path given", ->
|
|
editor.setText('abc')
|
|
openEx()
|
|
submitNormalModeInputText('saveas')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: Argument required'
|
|
)
|
|
|
|
describe "with a specified path", ->
|
|
newPath = ''
|
|
|
|
beforeEach ->
|
|
newPath = path.relative(dir, "#{filePath}.new")
|
|
editor.getBuffer().setText('abc')
|
|
openEx()
|
|
|
|
afterEach ->
|
|
submitNormalModeInputText("saveas #{newPath}")
|
|
newPath = path.resolve(dir, fs.normalize(newPath))
|
|
expect(fs.existsSync(newPath)).toBe(true)
|
|
expect(fs.readFileSync(newPath, 'utf-8')).toEqual('abc')
|
|
expect(editor.isModified()).toBe(false)
|
|
fs.removeSync(newPath)
|
|
|
|
it "saves to the path", ->
|
|
|
|
it "expands .", ->
|
|
newPath = path.join('.', newPath)
|
|
|
|
it "expands ..", ->
|
|
newPath = path.join('..', newPath)
|
|
|
|
it "expands ~", ->
|
|
newPath = path.join('~', newPath)
|
|
|
|
it "throws an error with more than one path", ->
|
|
openEx()
|
|
submitNormalModeInputText('saveas path1 path2')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: Only one file name allowed'
|
|
)
|
|
|
|
describe "when the file already exists", ->
|
|
existsPath = ''
|
|
|
|
beforeEach ->
|
|
existsPath = projectPath('saveas-exists')
|
|
fs.writeFileSync(existsPath, 'abc')
|
|
|
|
afterEach ->
|
|
fs.removeSync(existsPath)
|
|
|
|
it "throws an error if the file already exists", ->
|
|
openEx()
|
|
submitNormalModeInputText("saveas #{existsPath}")
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: File exists (add ! to override)'
|
|
)
|
|
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
|
|
|
|
it "writes if forced with :saveas!", ->
|
|
openEx()
|
|
submitNormalModeInputText("saveas! #{existsPath}")
|
|
expect(atom.notifications.notifications).toEqual([])
|
|
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
|
|
|
|
describe ":quit", ->
|
|
pane = null
|
|
beforeEach ->
|
|
waitsForPromise ->
|
|
pane = atom.workspace.getActivePane()
|
|
spyOn(pane, 'destroyActiveItem').andCallThrough()
|
|
atom.workspace.open()
|
|
|
|
it "closes the active pane item if not modified", ->
|
|
openEx()
|
|
submitNormalModeInputText('quit')
|
|
expect(pane.destroyActiveItem).toHaveBeenCalled()
|
|
expect(pane.getItems().length).toBe(1)
|
|
|
|
describe "when the active pane item is modified", ->
|
|
beforeEach ->
|
|
editor.getBuffer().setText('def')
|
|
|
|
it "opens the prompt to save", ->
|
|
spyOn(pane, 'promptToSaveItem')
|
|
openEx()
|
|
submitNormalModeInputText('quit')
|
|
expect(pane.promptToSaveItem).toHaveBeenCalled()
|
|
|
|
describe ":quitall", ->
|
|
it "closes Atom", ->
|
|
spyOn(atom, 'close')
|
|
openEx()
|
|
submitNormalModeInputText('quitall')
|
|
expect(atom.close).toHaveBeenCalled()
|
|
|
|
describe ":tabclose", ->
|
|
it "acts as an alias to :quit", ->
|
|
spyOn(Ex, 'tabclose').andCallThrough()
|
|
spyOn(Ex, 'quit').andCallThrough()
|
|
openEx()
|
|
submitNormalModeInputText('tabclose')
|
|
expect(Ex.quit).toHaveBeenCalledWith(Ex.tabclose.calls[0].args...)
|
|
|
|
describe ":tabnext", ->
|
|
pane = null
|
|
beforeEach ->
|
|
waitsForPromise ->
|
|
pane = atom.workspace.getActivePane()
|
|
atom.workspace.open().then -> atom.workspace.open()
|
|
.then -> atom.workspace.open()
|
|
|
|
it "switches to the next tab", ->
|
|
pane.activateItemAtIndex(1)
|
|
openEx()
|
|
submitNormalModeInputText('tabnext')
|
|
expect(pane.getActiveItemIndex()).toBe(2)
|
|
|
|
it "wraps around", ->
|
|
pane.activateItemAtIndex(pane.getItems().length - 1)
|
|
openEx()
|
|
submitNormalModeInputText('tabnext')
|
|
expect(pane.getActiveItemIndex()).toBe(0)
|
|
|
|
describe ":tabprevious", ->
|
|
pane = null
|
|
beforeEach ->
|
|
waitsForPromise ->
|
|
pane = atom.workspace.getActivePane()
|
|
atom.workspace.open().then -> atom.workspace.open()
|
|
.then -> atom.workspace.open()
|
|
|
|
it "switches to the previous tab", ->
|
|
pane.activateItemAtIndex(1)
|
|
openEx()
|
|
submitNormalModeInputText('tabprevious')
|
|
expect(pane.getActiveItemIndex()).toBe(0)
|
|
|
|
it "wraps around", ->
|
|
pane.activateItemAtIndex(0)
|
|
openEx()
|
|
submitNormalModeInputText('tabprevious')
|
|
expect(pane.getActiveItemIndex()).toBe(pane.getItems().length - 1)
|
|
|
|
describe ":wq", ->
|
|
beforeEach ->
|
|
spyOn(Ex, 'write').andCallThrough()
|
|
spyOn(Ex, 'quit')
|
|
|
|
it "writes the file, then quits", ->
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(projectPath('wq-1'))
|
|
openEx()
|
|
submitNormalModeInputText('wq')
|
|
expect(Ex.write).toHaveBeenCalled()
|
|
# Since `:wq` only calls `:quit` after `:write` is finished, we need to
|
|
# wait a bit for the `:quit` call to occur
|
|
waitsFor((-> Ex.quit.wasCalled), "the :quit command to be called", 100)
|
|
|
|
it "doesn't quit when the file is new and no path is specified in the save dialog", ->
|
|
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
|
|
openEx()
|
|
submitNormalModeInputText('wq')
|
|
expect(Ex.write).toHaveBeenCalled()
|
|
wasNotCalled = false
|
|
# FIXME: This seems dangerous, but setTimeout somehow doesn't work.
|
|
setImmediate((->
|
|
wasNotCalled = not Ex.quit.wasCalled))
|
|
waitsFor((-> wasNotCalled), 100)
|
|
|
|
it "passes the file name", ->
|
|
openEx()
|
|
submitNormalModeInputText('wq wq-2')
|
|
expect(Ex.write)
|
|
.toHaveBeenCalled()
|
|
expect(Ex.write.calls[0].args[0].args.trim()).toEqual('wq-2')
|
|
waitsFor((-> Ex.quit.wasCalled), "the :quit command to be called", 100)
|
|
|
|
describe ":xit", ->
|
|
it "acts as an alias to :wq", ->
|
|
spyOn(Ex, 'wq')
|
|
openEx()
|
|
submitNormalModeInputText('xit')
|
|
expect(Ex.wq).toHaveBeenCalled()
|
|
|
|
describe ":x", ->
|
|
it "acts as an alias to :xit", ->
|
|
spyOn(Ex, 'xit')
|
|
openEx()
|
|
submitNormalModeInputText('x')
|
|
expect(Ex.xit).toHaveBeenCalled()
|
|
|
|
describe ":wqall", ->
|
|
it "calls :wall, then :quitall", ->
|
|
spyOn(Ex, 'wall')
|
|
spyOn(Ex, 'quitall')
|
|
openEx()
|
|
submitNormalModeInputText('wqall')
|
|
expect(Ex.wall).toHaveBeenCalled()
|
|
expect(Ex.quitall).toHaveBeenCalled()
|
|
|
|
describe ":edit", ->
|
|
describe "without a file name", ->
|
|
it "reloads the file from the disk", ->
|
|
filePath = projectPath("edit-1")
|
|
editor.getBuffer().setText('abc')
|
|
editor.saveAs(filePath)
|
|
fs.writeFileSync(filePath, 'def')
|
|
openEx()
|
|
submitNormalModeInputText('edit')
|
|
# Reloading takes a bit
|
|
waitsFor((-> editor.getText() is 'def'),
|
|
"the editor's content to change", 100)
|
|
|
|
it "doesn't reload when the file has been modified", ->
|
|
filePath = projectPath("edit-2")
|
|
editor.getBuffer().setText('abc')
|
|
editor.saveAs(filePath)
|
|
editor.getBuffer().setText('abcd')
|
|
fs.writeFileSync(filePath, 'def')
|
|
openEx()
|
|
submitNormalModeInputText('edit')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: No write since last change (add ! to override)')
|
|
isntDef = false
|
|
setImmediate(-> isntDef = editor.getText() isnt 'def')
|
|
waitsFor((-> isntDef), "the editor's content not to change", 50)
|
|
|
|
it "reloads when the file has been modified and it is forced", ->
|
|
filePath = projectPath("edit-3")
|
|
editor.getBuffer().setText('abc')
|
|
editor.saveAs(filePath)
|
|
editor.getBuffer().setText('abcd')
|
|
fs.writeFileSync(filePath, 'def')
|
|
openEx()
|
|
submitNormalModeInputText('edit!')
|
|
expect(atom.notifications.notifications.length).toBe(0)
|
|
waitsFor((-> editor.getText() is 'def')
|
|
"the editor's content to change", 50)
|
|
|
|
it "throws an error when editing a new file", ->
|
|
editor.getBuffer().reload()
|
|
openEx()
|
|
submitNormalModeInputText('edit')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: No file name')
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText('edit!')
|
|
expect(atom.notifications.notifications[1].message).toEqual(
|
|
'Command error: No file name')
|
|
|
|
describe "with a file name", ->
|
|
beforeEach ->
|
|
spyOn(atom.workspace, 'open')
|
|
editor.getBuffer().reload()
|
|
|
|
it "opens the specified path", ->
|
|
filePath = projectPath('edit-new-test')
|
|
openEx()
|
|
submitNormalModeInputText("edit #{filePath}")
|
|
expect(atom.workspace.open).toHaveBeenCalledWith(filePath)
|
|
|
|
it "opens a relative path", ->
|
|
openEx()
|
|
submitNormalModeInputText('edit edit-relative-test')
|
|
expect(atom.workspace.open).toHaveBeenCalledWith(
|
|
projectPath('edit-relative-test'))
|
|
|
|
it "throws an error if trying to open more than one file", ->
|
|
openEx()
|
|
submitNormalModeInputText('edit edit-new-test-1 edit-new-test-2')
|
|
expect(atom.workspace.open.callCount).toBe(0)
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: Only one file name allowed')
|
|
|
|
describe ":tabedit", ->
|
|
it "acts as an alias to :edit if supplied with a path", ->
|
|
spyOn(Ex, 'tabedit').andCallThrough()
|
|
spyOn(Ex, 'edit')
|
|
openEx()
|
|
submitNormalModeInputText('tabedit tabedit-test')
|
|
expect(Ex.edit).toHaveBeenCalledWith(Ex.tabedit.calls[0].args...)
|
|
|
|
it "acts as an alias to :tabnew if not supplied with a path", ->
|
|
spyOn(Ex, 'tabedit').andCallThrough()
|
|
spyOn(Ex, 'tabnew')
|
|
openEx()
|
|
submitNormalModeInputText('tabedit ')
|
|
expect(Ex.tabnew)
|
|
.toHaveBeenCalledWith(Ex.tabedit.calls[0].args...)
|
|
|
|
describe ":tabnew", ->
|
|
it "opens a new tab", ->
|
|
spyOn(atom.workspace, 'open')
|
|
openEx()
|
|
submitNormalModeInputText('tabnew')
|
|
expect(atom.workspace.open).toHaveBeenCalled()
|
|
|
|
it "opens a new tab for editing when provided an argument", ->
|
|
spyOn(Ex, 'tabnew').andCallThrough()
|
|
spyOn(Ex, 'tabedit')
|
|
openEx()
|
|
submitNormalModeInputText('tabnew tabnew-test')
|
|
expect(Ex.tabedit)
|
|
.toHaveBeenCalledWith(Ex.tabnew.calls[0].args...)
|
|
|
|
describe ":split", ->
|
|
it "splits the current file upwards/downward", ->
|
|
pane = atom.workspace.getActivePane()
|
|
if atom.config.get('ex-mode.splitbelow')
|
|
spyOn(pane, 'splitDown').andCallThrough()
|
|
filePath = projectPath('split')
|
|
editor.saveAs(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('split')
|
|
expect(pane.splitDown).toHaveBeenCalled()
|
|
else
|
|
spyOn(pane, 'splitUp').andCallThrough()
|
|
filePath = projectPath('split')
|
|
editor.saveAs(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('split')
|
|
expect(pane.splitUp).toHaveBeenCalled()
|
|
# FIXME: Should test whether the new pane contains a TextEditor
|
|
# pointing to the same path
|
|
|
|
describe ":vsplit", ->
|
|
it "splits the current file to the left/right", ->
|
|
if atom.config.get('ex-mode.splitright')
|
|
pane = atom.workspace.getActivePane()
|
|
spyOn(pane, 'splitRight').andCallThrough()
|
|
filePath = projectPath('vsplit')
|
|
editor.saveAs(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('vsplit')
|
|
expect(pane.splitLeft).toHaveBeenCalled()
|
|
else
|
|
pane = atom.workspace.getActivePane()
|
|
spyOn(pane, 'splitLeft').andCallThrough()
|
|
filePath = projectPath('vsplit')
|
|
editor.saveAs(filePath)
|
|
openEx()
|
|
submitNormalModeInputText('vsplit')
|
|
expect(pane.splitLeft).toHaveBeenCalled()
|
|
# FIXME: Should test whether the new pane contains a TextEditor
|
|
# pointing to the same path
|
|
|
|
describe ":delete", ->
|
|
beforeEach ->
|
|
editor.setText('abc\ndef\nghi\njkl')
|
|
editor.setCursorBufferPosition([2, 0])
|
|
|
|
it "deletes the current line", ->
|
|
openEx()
|
|
submitNormalModeInputText('delete')
|
|
expect(editor.getText()).toEqual('abc\ndef\njkl')
|
|
|
|
it "copies the deleted text", ->
|
|
openEx()
|
|
submitNormalModeInputText('delete')
|
|
expect(atom.clipboard.read()).toEqual('ghi\n')
|
|
|
|
it "deletes the lines in the given range", ->
|
|
processedOpStack = false
|
|
exState.onDidProcessOpStack -> processedOpStack = true
|
|
openEx()
|
|
submitNormalModeInputText('1,2delete')
|
|
expect(editor.getText()).toEqual('ghi\njkl')
|
|
|
|
waitsFor -> processedOpStack
|
|
editor.setText('abc\ndef\nghi\njkl')
|
|
editor.setCursorBufferPosition([1, 1])
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(',/k/delete')
|
|
expect(editor.getText()).toEqual('abc\n')
|
|
|
|
it "undos deleting several lines at once", ->
|
|
openEx()
|
|
submitNormalModeInputText('-1,.delete')
|
|
expect(editor.getText()).toEqual('abc\njkl')
|
|
atom.commands.dispatch(editorElement, 'core:undo')
|
|
expect(editor.getText()).toEqual('abc\ndef\nghi\njkl')
|
|
|
|
describe ":substitute", ->
|
|
beforeEach ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
editor.setCursorBufferPosition([0, 0])
|
|
|
|
it "replaces a character on the current line", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute /a/x')
|
|
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "doesn't need a space before the arguments", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a/x')
|
|
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "respects modifiers passed to it", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a/x/g')
|
|
expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC')
|
|
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':substitute/a/x/gi')
|
|
expect(editor.getText()).toEqual('xbcxxBC\ndefdDEF\nabcaABC')
|
|
|
|
it "replaces on multiple lines", ->
|
|
openEx()
|
|
submitNormalModeInputText(':%substitute/abc/ghi')
|
|
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nghiaABC')
|
|
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':%substitute/abc/ghi/ig')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nghiaghi')
|
|
|
|
describe ":yank", ->
|
|
beforeEach ->
|
|
editor.setText('abc\ndef\nghi\njkl')
|
|
editor.setCursorBufferPosition([2, 0])
|
|
|
|
it "yanks the current line", ->
|
|
openEx()
|
|
submitNormalModeInputText('yank')
|
|
expect(atom.clipboard.read()).toEqual('ghi\n')
|
|
|
|
it "yanks the lines in the given range", ->
|
|
openEx()
|
|
submitNormalModeInputText('1,2yank')
|
|
expect(atom.clipboard.read()).toEqual('abc\ndef\n')
|
|
|
|
describe "illegal delimiters", ->
|
|
test = (delim) ->
|
|
openEx()
|
|
submitNormalModeInputText(":substitute #{delim}a#{delim}x#{delim}gi")
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
"Command error: Regular expressions can't be delimited by alphanumeric characters, '\\', '\"' or '|'")
|
|
expect(editor.getText()).toEqual('abcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "can't be delimited by letters", -> test 'n'
|
|
it "can't be delimited by numbers", -> test '3'
|
|
it "can't be delimited by '\\'", -> test '\\'
|
|
it "can't be delimited by '\"'", -> test '"'
|
|
it "can't be delimited by '|'", -> test '|'
|
|
|
|
describe "empty replacement", ->
|
|
beforeEach ->
|
|
editor.setText('abcabc\nabcabc')
|
|
|
|
it "removes the pattern without modifiers", ->
|
|
openEx()
|
|
submitNormalModeInputText(":substitute/abc//")
|
|
expect(editor.getText()).toEqual('abc\nabcabc')
|
|
|
|
it "removes the pattern with modifiers", ->
|
|
openEx()
|
|
submitNormalModeInputText(":substitute/abc//g")
|
|
expect(editor.getText()).toEqual('\nabcabc')
|
|
|
|
describe "replacing with escape sequences", ->
|
|
beforeEach ->
|
|
editor.setText('abc,def,ghi')
|
|
|
|
test = (escapeChar, escaped) ->
|
|
openEx()
|
|
submitNormalModeInputText(":substitute/,/#{escapeChar}/g")
|
|
expect(editor.getText()).toEqual("abc#{escaped}def#{escaped}ghi")
|
|
|
|
it "replaces with a tab", -> test('\\t', '\t')
|
|
it "replaces with a linefeed", -> test('\\n', '\n')
|
|
it "replaces with a carriage return", -> test('\\r', '\r')
|
|
|
|
describe "case sensitivity", ->
|
|
describe "respects the smartcase setting", ->
|
|
beforeEach ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case sensitive search if smartcase is off and the pattern is lowercase", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', false)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/abc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case sensitive search if smartcase is off and the pattern is uppercase", ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/ABC/ghi/g')
|
|
expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case insensitive search if smartcase is on and the pattern is lowercase", ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', true)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/abc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case sensitive search if smartcase is on and the pattern is uppercase", ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/ABC/ghi/g')
|
|
expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC')
|
|
|
|
describe "\\c and \\C in the pattern", ->
|
|
beforeEach ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case insensitive search if smartcase is off and \c is in the pattern", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', false)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/abc\\c/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "doesn't matter where in the pattern \\c is", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', false)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a\\cbc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "uses case sensitive search if smartcase is on, \\C is in the pattern and the pattern is lowercase", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', true)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a\\Cbc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "overrides \\C with \\c if \\C comes first", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', true)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a\\Cb\\cc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "overrides \\C with \\c if \\c comes first", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', true)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a\\cb\\Cc/ghi/g')
|
|
expect(editor.getText()).toEqual('ghiaghi\ndefdDEF\nabcaABC')
|
|
|
|
it "overrides an appended /i flag with \\C", ->
|
|
atom.config.set('vim-mode.useSmartcaseForSearch', true)
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/ab\\Cc/ghi/gi')
|
|
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
|
|
|
|
describe "capturing groups", ->
|
|
beforeEach ->
|
|
editor.setText('abcaABC\ndefdDEF\nabcaABC')
|
|
|
|
it "replaces \\1 with the first group", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/bc(.{2})/X\\1X')
|
|
expect(editor.getText()).toEqual('aXaAXBC\ndefdDEF\nabcaABC')
|
|
|
|
it "replaces multiple groups", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/a([a-z]*)aA([A-Z]*)/X\\1XY\\2Y')
|
|
expect(editor.getText()).toEqual('XbcXYBCY\ndefdDEF\nabcaABC')
|
|
|
|
it "replaces \\0 with the entire match", ->
|
|
openEx()
|
|
submitNormalModeInputText(':substitute/ab(ca)AB/X\\0X')
|
|
expect(editor.getText()).toEqual('XabcaABXC\ndefdDEF\nabcaABC')
|
|
|
|
describe ":set", ->
|
|
it "throws an error without a specified option", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set')
|
|
expect(atom.notifications.notifications[0].message).toEqual(
|
|
'Command error: No option specified')
|
|
|
|
it "sets multiple options at once", ->
|
|
atom.config.set('editor.showInvisibles', false)
|
|
atom.config.set('editor.showLineNumbers', false)
|
|
openEx()
|
|
submitNormalModeInputText(':set list number')
|
|
expect(atom.config.get('editor.showInvisibles')).toBe(true)
|
|
expect(atom.config.get('editor.showLineNumbers')).toBe(true)
|
|
|
|
describe "the options", ->
|
|
beforeEach ->
|
|
atom.config.set('editor.showInvisibles', false)
|
|
atom.config.set('editor.showLineNumbers', false)
|
|
|
|
it "sets (no)list", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set list')
|
|
expect(atom.config.get('editor.showInvisibles')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nolist')
|
|
expect(atom.config.get('editor.showInvisibles')).toBe(false)
|
|
|
|
it "sets (no)nu(mber)", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set nu')
|
|
expect(atom.config.get('editor.showLineNumbers')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nonu')
|
|
expect(atom.config.get('editor.showLineNumbers')).toBe(false)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set number')
|
|
expect(atom.config.get('editor.showLineNumbers')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nonumber')
|
|
expect(atom.config.get('editor.showLineNumbers')).toBe(false)
|
|
|
|
it "sets (no)sp(lit)r(ight)", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set spr')
|
|
expect(atom.config.get('ex-mode.splitright')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nospr')
|
|
expect(atom.config.get('ex-mode.splitright')).toBe(false)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set splitright')
|
|
expect(atom.config.get('ex-mode.splitright')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nosplitright')
|
|
expect(atom.config.get('ex-mode.splitright')).toBe(false)
|
|
|
|
it "sets (no)s(plit)b(elow)", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set sb')
|
|
expect(atom.config.get('ex-mode.splitbelow')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nosb')
|
|
expect(atom.config.get('ex-mode.splitbelow')).toBe(false)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set splitbelow')
|
|
expect(atom.config.get('ex-mode.splitbelow')).toBe(true)
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText(':set nosplitbelow')
|
|
expect(atom.config.get('ex-mode.splitbelow')).toBe(false)
|
|
|
|
it "sets (no)s(mart)c(a)s(e)", ->
|
|
openEx()
|
|
submitNormalModeInputText(':set scs')
|
|
expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(true)
|
|
openEx()
|
|
submitNormalModeInputText(':set noscs')
|
|
expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(false)
|
|
openEx()
|
|
submitNormalModeInputText(':set smartcase')
|
|
expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(true)
|
|
openEx()
|
|
submitNormalModeInputText(':set nosmartcase')
|
|
expect(atom.config.get('vim-mode.useSmartcaseForSearch')).toBe(false)
|
|
|
|
describe "aliases", ->
|
|
it "calls the aliased function without arguments", ->
|
|
ExClass.registerAlias('W', 'w')
|
|
spyOn(Ex, 'write')
|
|
openEx()
|
|
submitNormalModeInputText('W')
|
|
expect(Ex.write).toHaveBeenCalled()
|
|
|
|
it "calls the aliased function with arguments", ->
|
|
ExClass.registerAlias('W', 'write')
|
|
spyOn(Ex, 'W').andCallThrough()
|
|
spyOn(Ex, 'write')
|
|
openEx()
|
|
submitNormalModeInputText('W')
|
|
WArgs = Ex.W.calls[0].args[0]
|
|
writeArgs = Ex.write.calls[0].args[0]
|
|
expect(WArgs).toBe writeArgs
|
|
|
|
describe "with selections", ->
|
|
it "executes on the selected range", ->
|
|
spyOn(Ex, 's')
|
|
editor.setCursorBufferPosition([0, 0])
|
|
editor.selectToBufferPosition([2, 1])
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText("'<,'>s/abc/def")
|
|
expect(Ex.s.calls[0].args[0].range).toEqual [0, 2]
|
|
|
|
it "calls the functions multiple times if there are multiple selections", ->
|
|
spyOn(Ex, 's')
|
|
editor.setCursorBufferPosition([0, 0])
|
|
editor.selectToBufferPosition([2, 1])
|
|
editor.addCursorAtBufferPosition([3, 0])
|
|
editor.selectToBufferPosition([3, 2])
|
|
atom.commands.dispatch(editorElement, 'ex-mode:open')
|
|
submitNormalModeInputText("'<,'>s/abc/def")
|
|
calls = Ex.s.calls
|
|
expect(calls.length).toEqual 2
|
|
expect(calls[0].args[0].range).toEqual [0, 2]
|
|
expect(calls[1].args[0].range).toEqual [3, 3]
|