Merge pull request #160 from jazzpi/visual-marks

Basic support for visual marks ('<,'>)
This commit is contained in:
Jasper v. B 2016-08-16 00:24:58 +02:00 committed by GitHub
commit 6c22b3b701
5 changed files with 167 additions and 107 deletions

View file

@ -5,7 +5,8 @@ CommandError = require './command-error'
class Command
constructor: (@editor, @exState) ->
@viewModel = new ExViewModel(@)
@selections = @exState.getSelections()
@viewModel = new ExViewModel(@, Object.keys(@selections).length > 0)
parseAddr: (str, curPos) ->
if str is '.'
@ -97,32 +98,39 @@ class Command
curPos = @editor.getCursorBufferPosition()
if addr1?
address1 = @parseAddr(addr1, curPos)
# Special case: run command on selection. This can't be handled by simply
# parsing the mark since vim-mode doesn't set it (and it would be fairly
# useless with multiple selections)
if addr1 is "'<" and addr2 is "'>"
runOverSelections = true
else
# If no addr1 is given (,+3), assume it is '.'
address1 = curPos.row
if off1?
address1 += @parseOffset(off1)
runOverSelections = false
if addr1?
address1 = @parseAddr(addr1, curPos)
else
# If no addr1 is given (,+3), assume it is '.'
address1 = curPos.row
if off1?
address1 += @parseOffset(off1)
address1 = 0 if address1 is -1
address1 = 0 if address1 is -1
if address1 < 0 or address1 > lastLine
throw new CommandError('Invalid range')
if address1 < 0 or address1 > lastLine
throw new CommandError('Invalid range')
if addr2?
address2 = @parseAddr(addr2, curPos)
if off2?
address2 += @parseOffset(off2)
if addr2?
address2 = @parseAddr(addr2, curPos)
if off2?
address2 += @parseOffset(off2)
if address2 < 0 or address2 > lastLine
throw new CommandError('Invalid range')
if address2 < 0 or address2 > lastLine
throw new CommandError('Invalid range')
if address2 < address1
throw new CommandError('Backwards range given')
if address2 < address1
throw new CommandError('Backwards range given')
range = [address1, if address2? then address2 else address1]
cl = cl[match?.length..]
cl = cl[match?.length..]
# Step 5: Leading blanks are ignored
cl = cl.trimLeft()
@ -149,9 +157,7 @@ class Command
[m, command, args] = cl.match(/^(\w+)(.*)/)
# If the command matches an existing one exactly, execute that one
if (func = Ex.singleton()[command])?
func({ range, args, @vimState, @exState, @editor })
else
unless (func = Ex.singleton()[command])?
# Step 8: Match command against existing commands
matching = (name for name, val of Ex.singleton() when \
name.indexOf(command) is 0)
@ -161,9 +167,16 @@ class Command
command = matching[0]
func = Ex.singleton()[command]
if func?
func({ range, args, @vimState, @exState, @editor })
if func?
if runOverSelections
for id, selection of @selections
bufferRange = selection.getBufferRange()
range = [bufferRange.start.row, bufferRange.end.row]
func({ range, args, @vimState, @exState, @editor })
else
throw new CommandError("Not an editor command: #{input.characters}")
func({ range, args, @vimState, @exState, @editor })
else
throw new CommandError("Not an editor command: #{input.characters}")
module.exports = Command

View file

@ -60,4 +60,13 @@ class ExState
@clearOpStack()
@emitter.emit('processed-op-stack')
# Returns all non-empty selections
getSelections: ->
filtered = {}
for id, selection of @editor.getSelections()
unless selection.isEmpty()
filtered[id] = selection
return filtered
module.exports = ExState

View file

@ -4,10 +4,13 @@ Ex = require './ex'
module.exports =
class ExViewModel extends ViewModel
constructor: (@exCommand) ->
constructor: (@exCommand, withSelection) ->
super(@exCommand, class: 'command')
@historyIndex = -1
if withSelection
@view.editorElement.getModel().setText("'<,'>")
@view.editorElement.addEventListener('keydown', @tabAutocomplete)
atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx)
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)

View file

@ -36,7 +36,8 @@ describe "the commands", ->
helpers.getEditorElement (element) ->
atom.commands.dispatch(element, "ex-mode:open")
keydown('escape')
atom.commands.dispatch(element.getModel().normalModeInputView.editorElement,
"core:cancel")
editorElement = element
editor = editorElement.getModel()
vimState = vimMode.mainModule.getEditorState(editor)
@ -61,6 +62,9 @@ describe "the commands", ->
commandEditor.getModel().setText(text)
atom.commands.dispatch(commandEditor, "core:confirm")
openEx = ->
atom.commands.dispatch(editorElement, "ex-mode:open")
describe ":write", ->
describe "when editing a new file", ->
beforeEach ->
@ -68,14 +72,14 @@ describe "the commands", ->
it "opens the save dialog", ->
spyOn(atom, 'showSaveDialogSync')
keydown(':')
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)
keydown(':')
openEx()
submitNormalModeInputText('write')
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef')
@ -84,7 +88,7 @@ describe "the commands", ->
it "saves when a path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
spyOn(fs, 'writeFileSync')
keydown(':')
openEx()
submitNormalModeInputText('write')
expect(fs.writeFileSync.calls.length).toBe(0)
@ -100,7 +104,7 @@ describe "the commands", ->
it "saves the file", ->
editor.setText('abc')
keydown(':')
openEx()
submitNormalModeInputText('write')
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc')
expect(editor.isModified()).toBe(false)
@ -111,7 +115,7 @@ describe "the commands", ->
beforeEach ->
newPath = path.relative(dir, "#{filePath}.new")
editor.getBuffer().setText('abc')
keydown(':')
openEx()
afterEach ->
submitNormalModeInputText("write #{newPath}")
@ -133,7 +137,7 @@ describe "the commands", ->
newPath = path.join('~', newPath)
it "throws an error with more than one path", ->
keydown(':')
openEx()
submitNormalModeInputText('write path1 path2')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Only one file name allowed'
@ -150,7 +154,7 @@ describe "the commands", ->
fs.removeSync(existsPath)
it "throws an error if the file already exists", ->
keydown(':')
openEx()
submitNormalModeInputText("write #{existsPath}")
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: File exists (add ! to override)'
@ -158,7 +162,7 @@ describe "the commands", ->
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
it "writes if forced with :write!", ->
keydown(':')
openEx()
submitNormalModeInputText("write! #{existsPath}")
expect(atom.notifications.notifications).toEqual([])
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
@ -166,7 +170,7 @@ describe "the commands", ->
describe ":wall", ->
it "saves all", ->
spyOn(atom.workspace, 'saveAll')
keydown(':')
openEx()
submitNormalModeInputText('wall')
expect(atom.workspace.saveAll).toHaveBeenCalled()
@ -177,14 +181,14 @@ describe "the commands", ->
it "opens the save dialog", ->
spyOn(atom, 'showSaveDialogSync')
keydown(':')
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)
keydown(':')
openEx()
submitNormalModeInputText('saveas')
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf-8')).toEqual('abc\ndef')
@ -192,7 +196,7 @@ describe "the commands", ->
it "saves when a path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
spyOn(fs, 'writeFileSync')
keydown(':')
openEx()
submitNormalModeInputText('saveas')
expect(fs.writeFileSync.calls.length).toBe(0)
@ -208,7 +212,7 @@ describe "the commands", ->
it "complains if no path given", ->
editor.setText('abc')
keydown(':')
openEx()
submitNormalModeInputText('saveas')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Argument required'
@ -220,7 +224,7 @@ describe "the commands", ->
beforeEach ->
newPath = path.relative(dir, "#{filePath}.new")
editor.getBuffer().setText('abc')
keydown(':')
openEx()
afterEach ->
submitNormalModeInputText("saveas #{newPath}")
@ -242,7 +246,7 @@ describe "the commands", ->
newPath = path.join('~', newPath)
it "throws an error with more than one path", ->
keydown(':')
openEx()
submitNormalModeInputText('saveas path1 path2')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: Only one file name allowed'
@ -259,7 +263,7 @@ describe "the commands", ->
fs.removeSync(existsPath)
it "throws an error if the file already exists", ->
keydown(':')
openEx()
submitNormalModeInputText("saveas #{existsPath}")
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: File exists (add ! to override)'
@ -267,7 +271,7 @@ describe "the commands", ->
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc')
it "writes if forced with :saveas!", ->
keydown(':')
openEx()
submitNormalModeInputText("saveas! #{existsPath}")
expect(atom.notifications.notifications).toEqual([])
expect(fs.readFileSync(existsPath, 'utf-8')).toEqual('abc\ndef')
@ -281,7 +285,7 @@ describe "the commands", ->
atom.workspace.open()
it "closes the active pane item if not modified", ->
keydown(':')
openEx()
submitNormalModeInputText('quit')
expect(pane.destroyActiveItem).toHaveBeenCalled()
expect(pane.getItems().length).toBe(1)
@ -292,14 +296,14 @@ describe "the commands", ->
it "opens the prompt to save", ->
spyOn(pane, 'promptToSaveItem')
keydown(':')
openEx()
submitNormalModeInputText('quit')
expect(pane.promptToSaveItem).toHaveBeenCalled()
describe ":quitall", ->
it "closes Atom", ->
spyOn(atom, 'close')
keydown(':')
openEx()
submitNormalModeInputText('quitall')
expect(atom.close).toHaveBeenCalled()
@ -307,7 +311,7 @@ describe "the commands", ->
it "acts as an alias to :quit", ->
spyOn(Ex, 'tabclose').andCallThrough()
spyOn(Ex, 'quit').andCallThrough()
keydown(':')
openEx()
submitNormalModeInputText('tabclose')
expect(Ex.quit).toHaveBeenCalledWith(Ex.tabclose.calls[0].args...)
@ -321,13 +325,13 @@ describe "the commands", ->
it "switches to the next tab", ->
pane.activateItemAtIndex(1)
keydown(':')
openEx()
submitNormalModeInputText('tabnext')
expect(pane.getActiveItemIndex()).toBe(2)
it "wraps around", ->
pane.activateItemAtIndex(pane.getItems().length - 1)
keydown(':')
openEx()
submitNormalModeInputText('tabnext')
expect(pane.getActiveItemIndex()).toBe(0)
@ -341,13 +345,13 @@ describe "the commands", ->
it "switches to the previous tab", ->
pane.activateItemAtIndex(1)
keydown(':')
openEx()
submitNormalModeInputText('tabprevious')
expect(pane.getActiveItemIndex()).toBe(0)
it "wraps around", ->
pane.activateItemAtIndex(0)
keydown(':')
openEx()
submitNormalModeInputText('tabprevious')
expect(pane.getActiveItemIndex()).toBe(pane.getItems().length - 1)
@ -358,7 +362,7 @@ describe "the commands", ->
it "writes the file, then quits", ->
spyOn(atom, 'showSaveDialogSync').andReturn(projectPath('wq-1'))
keydown(':')
openEx()
submitNormalModeInputText('wq')
expect(Ex.write).toHaveBeenCalled()
# Since `:wq` only calls `:quit` after `:write` is finished, we need to
@ -367,7 +371,7 @@ describe "the commands", ->
it "doesn't quit when the file is new and no path is specified in the save dialog", ->
spyOn(atom, 'showSaveDialogSync').andReturn(undefined)
keydown(':')
openEx()
submitNormalModeInputText('wq')
expect(Ex.write).toHaveBeenCalled()
wasNotCalled = false
@ -377,7 +381,7 @@ describe "the commands", ->
waitsFor((-> wasNotCalled), 100)
it "passes the file name", ->
keydown(':')
openEx()
submitNormalModeInputText('wq wq-2')
expect(Ex.write)
.toHaveBeenCalled()
@ -387,7 +391,7 @@ describe "the commands", ->
describe ":xit", ->
it "acts as an alias to :wq", ->
spyOn(Ex, 'wq')
keydown(':')
openEx()
submitNormalModeInputText('xit')
expect(Ex.wq).toHaveBeenCalled()
@ -395,7 +399,7 @@ describe "the commands", ->
it "calls :wall, then :quitall", ->
spyOn(Ex, 'wall')
spyOn(Ex, 'quitall')
keydown(':')
openEx()
submitNormalModeInputText('wqall')
expect(Ex.wall).toHaveBeenCalled()
expect(Ex.quitall).toHaveBeenCalled()
@ -407,7 +411,7 @@ describe "the commands", ->
editor.getBuffer().setText('abc')
editor.saveAs(filePath)
fs.writeFileSync(filePath, 'def')
keydown(':')
openEx()
submitNormalModeInputText('edit')
# Reloading takes a bit
waitsFor((-> editor.getText() is 'def'),
@ -419,7 +423,7 @@ describe "the commands", ->
editor.saveAs(filePath)
editor.getBuffer().setText('abcd')
fs.writeFileSync(filePath, 'def')
keydown(':')
openEx()
submitNormalModeInputText('edit')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No write since last change (add ! to override)')
@ -433,7 +437,7 @@ describe "the commands", ->
editor.saveAs(filePath)
editor.getBuffer().setText('abcd')
fs.writeFileSync(filePath, 'def')
keydown(':')
openEx()
submitNormalModeInputText('edit!')
expect(atom.notifications.notifications.length).toBe(0)
waitsFor((-> editor.getText() is 'def')
@ -441,7 +445,7 @@ describe "the commands", ->
it "throws an error when editing a new file", ->
editor.getBuffer().reload()
keydown(':')
openEx()
submitNormalModeInputText('edit')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No file name')
@ -457,18 +461,18 @@ describe "the commands", ->
it "opens the specified path", ->
filePath = projectPath('edit-new-test')
keydown(':')
openEx()
submitNormalModeInputText("edit #{filePath}")
expect(atom.workspace.open).toHaveBeenCalledWith(filePath)
it "opens a relative path", ->
keydown(':')
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", ->
keydown(':')
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(
@ -478,14 +482,14 @@ describe "the commands", ->
it "acts as an alias to :edit if supplied with a path", ->
spyOn(Ex, 'tabedit').andCallThrough()
spyOn(Ex, 'edit')
keydown(':')
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')
keydown(':')
openEx()
submitNormalModeInputText('tabedit ')
expect(Ex.tabnew)
.toHaveBeenCalledWith(Ex.tabedit.calls[0].args...)
@ -493,14 +497,14 @@ describe "the commands", ->
describe ":tabnew", ->
it "opens a new tab", ->
spyOn(atom.workspace, 'open')
keydown(':')
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')
keydown(':')
openEx()
submitNormalModeInputText('tabnew tabnew-test')
expect(Ex.tabedit)
.toHaveBeenCalledWith(Ex.tabnew.calls[0].args...)
@ -512,14 +516,14 @@ describe "the commands", ->
spyOn(pane, 'splitDown').andCallThrough()
filePath = projectPath('split')
editor.saveAs(filePath)
keydown(':')
openEx()
submitNormalModeInputText('split')
expect(pane.splitDown).toHaveBeenCalled()
else
spyOn(pane, 'splitUp').andCallThrough()
filePath = projectPath('split')
editor.saveAs(filePath)
keydown(':')
openEx()
submitNormalModeInputText('split')
expect(pane.splitUp).toHaveBeenCalled()
# FIXME: Should test whether the new pane contains a TextEditor
@ -532,7 +536,7 @@ describe "the commands", ->
spyOn(pane, 'splitRight').andCallThrough()
filePath = projectPath('vsplit')
editor.saveAs(filePath)
keydown(':')
openEx()
submitNormalModeInputText('vsplit')
expect(pane.splitLeft).toHaveBeenCalled()
else
@ -540,7 +544,7 @@ describe "the commands", ->
spyOn(pane, 'splitLeft').andCallThrough()
filePath = projectPath('vsplit')
editor.saveAs(filePath)
keydown(':')
openEx()
submitNormalModeInputText('vsplit')
expect(pane.splitLeft).toHaveBeenCalled()
# FIXME: Should test whether the new pane contains a TextEditor
@ -552,19 +556,19 @@ describe "the commands", ->
editor.setCursorBufferPosition([2, 0])
it "deletes the current line", ->
keydown(':')
openEx()
submitNormalModeInputText('delete')
expect(editor.getText()).toEqual('abc\ndef\njkl')
it "copies the deleted text", ->
keydown(':')
openEx()
submitNormalModeInputText('delete')
expect(atom.clipboard.read()).toEqual('ghi\n')
it "deletes the lines in the given range", ->
processedOpStack = false
exState.onDidProcessOpStack -> processedOpStack = true
keydown(':')
openEx()
submitNormalModeInputText('1,2delete')
expect(editor.getText()).toEqual('ghi\njkl')
@ -577,7 +581,7 @@ describe "the commands", ->
expect(editor.getText()).toEqual('abc\n')
it "undos deleting several lines at once", ->
keydown(':')
openEx()
submitNormalModeInputText('-1,.delete')
expect(editor.getText()).toEqual('abc\njkl')
atom.commands.dispatch(editorElement, 'core:undo')
@ -589,17 +593,17 @@ describe "the commands", ->
editor.setCursorBufferPosition([0, 0])
it "replaces a character on the current line", ->
keydown(':')
openEx()
submitNormalModeInputText(':substitute /a/x')
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
it "doesn't need a space before the arguments", ->
keydown(':')
openEx()
submitNormalModeInputText(':substitute/a/x')
expect(editor.getText()).toEqual('xbcaABC\ndefdDEF\nabcaABC')
it "respects modifiers passed to it", ->
keydown(':')
openEx()
submitNormalModeInputText(':substitute/a/x/g')
expect(editor.getText()).toEqual('xbcxABC\ndefdDEF\nabcaABC')
@ -608,7 +612,7 @@ describe "the commands", ->
expect(editor.getText()).toEqual('xbcxxBC\ndefdDEF\nabcaABC')
it "replaces on multiple lines", ->
keydown(':')
openEx()
submitNormalModeInputText(':%substitute/abc/ghi')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nghiaABC')
@ -622,18 +626,18 @@ describe "the commands", ->
editor.setCursorBufferPosition([2, 0])
it "yanks the current line", ->
keydown(':')
openEx()
submitNormalModeInputText('yank')
expect(atom.clipboard.read()).toEqual('ghi\n')
it "yanks the lines in the given range", ->
keydown(':')
openEx()
submitNormalModeInputText('1,2yank')
expect(atom.clipboard.read()).toEqual('abc\ndef\n')
describe "illegal delimiters", ->
test = (delim) ->
keydown(':')
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 '|'")
@ -650,12 +654,12 @@ describe "the commands", ->
editor.setText('abcabc\nabcabc')
it "removes the pattern without modifiers", ->
keydown(':')
openEx()
submitNormalModeInputText(":substitute/abc//")
expect(editor.getText()).toEqual('abc\nabcabc')
it "removes the pattern with modifiers", ->
keydown(':')
openEx()
submitNormalModeInputText(":substitute/abc//g")
expect(editor.getText()).toEqual('\nabcabc')
@ -664,7 +668,7 @@ describe "the commands", ->
editor.setText('abc,def,ghi')
test = (escapeChar, escaped) ->
keydown(':')
openEx()
submitNormalModeInputText(":substitute/,/\\#{escapeChar}/g")
expect(editor.getText()).toEqual("abc#{escaped}def#{escaped}ghi")
@ -679,26 +683,26 @@ describe "the commands", ->
it "uses case sensitive search if smartcase is off and the pattern is lowercase", ->
atom.config.set('vim-mode.useSmartcaseForSearch', false)
keydown(':')
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')
keydown(':')
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)
keydown(':')
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')
keydown(':')
openEx()
submitNormalModeInputText(':substitute/ABC/ghi/g')
expect(editor.getText()).toEqual('abcaghi\ndefdDEF\nabcaABC')
@ -708,37 +712,37 @@ describe "the commands", ->
it "uses case insensitive search if smartcase is off and \c is in the pattern", ->
atom.config.set('vim-mode.useSmartcaseForSearch', false)
keydown(':')
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)
keydown(':')
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)
keydown(':')
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)
keydown(':')
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)
keydown(':')
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)
keydown(':')
openEx()
submitNormalModeInputText(':substitute/ab\\Cc/ghi/gi')
expect(editor.getText()).toEqual('ghiaABC\ndefdDEF\nabcaABC')
@ -747,23 +751,23 @@ describe "the commands", ->
editor.setText('abcaABC\ndefdDEF\nabcaABC')
it "replaces \\1 with the first group", ->
keydown(':')
openEx()
submitNormalModeInputText(':substitute/bc(.{2})/X\\1X')
expect(editor.getText()).toEqual('aXaAXBC\ndefdDEF\nabcaABC')
it "replaces multiple groups", ->
keydown(':')
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", ->
keydown(':')
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", ->
keydown(':')
openEx()
submitNormalModeInputText(':set')
expect(atom.notifications.notifications[0].message).toEqual(
'Command error: No option specified')
@ -771,7 +775,7 @@ describe "the commands", ->
it "sets multiple options at once", ->
atom.config.set('editor.showInvisibles', false)
atom.config.set('editor.showLineNumbers', false)
keydown(':')
openEx()
submitNormalModeInputText(':set list number')
expect(atom.config.get('editor.showInvisibles')).toBe(true)
expect(atom.config.get('editor.showLineNumbers')).toBe(true)
@ -782,7 +786,7 @@ describe "the commands", ->
atom.config.set('editor.showLineNumbers', false)
it "sets (no)list", ->
keydown(':')
openEx()
submitNormalModeInputText(':set list')
expect(atom.config.get('editor.showInvisibles')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -790,7 +794,7 @@ describe "the commands", ->
expect(atom.config.get('editor.showInvisibles')).toBe(false)
it "sets (no)nu(mber)", ->
keydown(':')
openEx()
submitNormalModeInputText(':set nu')
expect(atom.config.get('editor.showLineNumbers')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -804,7 +808,7 @@ describe "the commands", ->
expect(atom.config.get('editor.showLineNumbers')).toBe(false)
it "sets (no)sp(lit)r(ight)", ->
keydown(':')
openEx()
submitNormalModeInputText(':set spr')
expect(atom.config.get('ex-mode.splitright')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -818,7 +822,7 @@ describe "the commands", ->
expect(atom.config.get('ex-mode.splitright')).toBe(false)
it "sets (no)s(plit)b(elow)", ->
keydown(':')
openEx()
submitNormalModeInputText(':set sb')
expect(atom.config.get('ex-mode.splitbelow')).toBe(true)
atom.commands.dispatch(editorElement, 'ex-mode:open')
@ -835,7 +839,7 @@ describe "the commands", ->
it "calls the aliased function without arguments", ->
ExClass.registerAlias('W', 'w')
spyOn(Ex, 'write')
keydown(':')
openEx()
submitNormalModeInputText('W')
expect(Ex.write).toHaveBeenCalled()
@ -843,8 +847,30 @@ describe "the commands", ->
ExClass.registerAlias('W', 'write')
spyOn(Ex, 'W').andCallThrough()
spyOn(Ex, 'write')
keydown(':')
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]

View file

@ -70,6 +70,7 @@ describe "the input element", ->
expect(getVisibility()).toBe true
commandEditor = getCommandEditor()
model = commandEditor.getModel()
expect(model.getText()).toBe ''
model.setText('abc')
atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe true
@ -82,3 +83,11 @@ describe "the input element", ->
expect(model.getText()).toBe ''
atom.commands.dispatch(commandEditor, "core:backspace")
expect(getVisibility()).toBe false
it "contains '<,'> when opened while there are selections", ->
editor.setCursorBufferPosition([0, 0])
editor.selectToBufferPosition([0, 1])
editor.addCursorAtBufferPosition([2, 0])
editor.selectToBufferPosition([2, 1])
atom.commands.dispatch(editorElement, "ex-mode:open")
expect(getCommandEditor().getModel().getText()).toBe "'<,'>"