diff --git a/lib/command.coffee b/lib/command.coffee index 172cefb..9097c6c 100644 --- a/lib/command.coffee +++ b/lib/command.coffee @@ -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 diff --git a/lib/ex-state.coffee b/lib/ex-state.coffee index 7c0f37c..89fbeeb 100644 --- a/lib/ex-state.coffee +++ b/lib/ex-state.coffee @@ -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 diff --git a/lib/ex-view-model.coffee b/lib/ex-view-model.coffee index c8f1202..08dd9f6 100644 --- a/lib/ex-view-model.coffee +++ b/lib/ex-view-model.coffee @@ -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) diff --git a/spec/ex-commands-spec.coffee b/spec/ex-commands-spec.coffee index b58c31a..1dd24a6 100644 --- a/spec/ex-commands-spec.coffee +++ b/spec/ex-commands-spec.coffee @@ -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) @@ -848,3 +849,25 @@ describe "the commands", -> 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] diff --git a/spec/ex-input-spec.coffee b/spec/ex-input-spec.coffee index 4555c6f..3d076b9 100644 --- a/spec/ex-input-spec.coffee +++ b/spec/ex-input-spec.coffee @@ -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 "'<,'>"