diff --git a/spec/app/display-buffer-spec.coffee b/spec/app/display-buffer-spec.coffee index f1a0e54b0..b991e60cc 100644 --- a/spec/app/display-buffer-spec.coffee +++ b/spec/app/display-buffer-spec.coffee @@ -530,14 +530,22 @@ describe "DisplayBuffer", -> expect(displayBuffer.bufferPositionForScreenPosition([9, 2])).toEqual [12, 2] describe ".destroyFoldsContainingBufferRow(row)", -> - describe "when two folds start on the given buffer row", -> - it "destroys both folds", -> + it "destroys all folds containing the given row", -> displayBuffer.createFold(2, 4) displayBuffer.createFold(2, 6) + displayBuffer.createFold(7, 8) + displayBuffer.createFold(1, 9) + displayBuffer.createFold(11, 12) + + expect(displayBuffer.lineForRow(1).text).toBe '1' + expect(displayBuffer.lineForRow(2).text).toBe '10' - expect(displayBuffer.lineForRow(3).text).toBe '7' displayBuffer.destroyFoldsContainingBufferRow(2) - expect(displayBuffer.lineForRow(3).text).toBe '3' + expect(displayBuffer.lineForRow(1).text).toBe '1' + expect(displayBuffer.lineForRow(2).text).toBe '2' + expect(displayBuffer.lineForRow(7).fold).toBeDefined() + expect(displayBuffer.lineForRow(8).text).toMatch /^9-+/ + expect(displayBuffer.lineForRow(10).fold).toBeDefined() describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", -> beforeEach -> diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 8a816ff4c..6b519b24f 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -856,8 +856,6 @@ describe "EditSession", -> editSession.setSelectedBufferRange([[3,0], [4,0]]) editSession.backspace() - buffer.logLines() - expect(buffer.lineForRow(3)).toBe " return sort(left).concat(pivot).concat(sort(right));" expect(buffer.lineForRow(4)).toBe " };" expect(editSession.getCursorScreenPosition()).toEqual [3, 0] diff --git a/spec/extensions/command-interpreter-spec.coffee b/spec/extensions/command-interpreter-spec.coffee index af083393f..e68795ebe 100644 --- a/spec/extensions/command-interpreter-spec.coffee +++ b/spec/extensions/command-interpreter-spec.coffee @@ -105,6 +105,11 @@ describe "CommandInterpreter", -> interpreter.eval(editor, '/mike tyson') expect(editor.getSelection().getBufferRange()).toEqual [[3,8], [3,13]] + it "searches in reverse when prefixed with a -", -> + editor.setSelectedBufferRange([[6, 16], [6, 22]]) + interpreter.eval(editor, '-/pivot') + expect(editor.getSelection().getBufferRange()).toEqual [[3,8], [3,13]] + describe "address range", -> describe "when two addresses are specified", -> it "selects from the begining of the left address to the end of the right address", -> @@ -229,26 +234,14 @@ describe "CommandInterpreter", -> expect(buffer.lineForRow(5)).toBe ' foo = items.shift();' expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);' - describe ".repeatRelativeAddress()", -> - it "repeats the last search command if there is one", -> - interpreter.repeatRelativeAddress(editor) # don't raise an exception - - editor.setCursorScreenPosition([4, 0]) - - interpreter.eval(editor, '/current') - expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]] - - interpreter.repeatRelativeAddress(editor) - expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]] - - interpreter.eval(editor, 's/r/R/g') - - interpreter.repeatRelativeAddress(editor) - expect(editor.getSelection().getBufferRange()).toEqual [[6,34], [6,41]] - - interpreter.eval(editor, '0') - interpreter.eval(editor, '/sort/ s/r/R/') # this contains a substitution... won't be repeated - - interpreter.repeatRelativeAddress(editor) - expect(editor.getSelection().getBufferRange()).toEqual [[3,31], [3,38]] + describe "when command selects folded text", -> + it "unfolds lines that command selects", -> + editor.createFold(1, 9) + editor.createFold(5, 8) + editor.setSelectedBufferRange([[0,0], [0,0]]) + interpreter.eval(editor, '/push/') + expect(editor.getSelection().getBufferRange()).toEqual [[6,29], [6,33]] + expect(editor.lineForScreenRow(1).fold).toBeUndefined() + expect(editor.lineForScreenRow(5).fold).toBeUndefined() + expect(editor.lineForScreenRow(6).text).toBe buffer.lineForRow(6) diff --git a/spec/extensions/command-panel-spec.coffee b/spec/extensions/command-panel-spec.coffee index 98e2ccdc6..6127f5e18 100644 --- a/spec/extensions/command-panel-spec.coffee +++ b/spec/extensions/command-panel-spec.coffee @@ -2,12 +2,13 @@ RootView = require 'root-view' CommandPanel = require 'command-panel' describe "CommandPanel", -> - [rootView, commandPanel] = [] + [rootView, editor, commandPanel] = [] beforeEach -> rootView = new RootView - rootView.open() + rootView.open(require.resolve 'fixtures/sample.js') rootView.enableKeymap() + editor = rootView.activeEditor() commandPanel = rootView.activateExtension(CommandPanel) describe "serialization", -> @@ -46,10 +47,39 @@ describe "CommandPanel", -> expect(commandPanel.miniEditor.getCursorScreenPosition()).toEqual [0, 0] describe "when command-panel:repeat-relative-address is triggered on the root view", -> - it "calls .repeatRelativeAddress on the command interpreter with the active editor", -> - spyOn(commandPanel.commandInterpreter, 'repeatRelativeAddress') + it "repeats the last search command if there is one", -> rootView.trigger 'command-panel:repeat-relative-address' - expect(commandPanel.commandInterpreter.repeatRelativeAddress).toHaveBeenCalledWith(rootView.activeEditor()) + + editor.setCursorScreenPosition([4, 0]) + + commandPanel.execute("/current") + expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]] + + rootView.trigger 'command-panel:repeat-relative-address' + expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]] + + commandPanel.execute('s/r/R/g') + + rootView.trigger 'command-panel:repeat-relative-address' + expect(editor.getSelection().getBufferRange()).toEqual [[6,34], [6,41]] + + commandPanel.execute('0') + commandPanel.execute('/sort/ s/r/R/') # this contains a substitution... won't be repeated + + rootView.trigger 'command-panel:repeat-relative-address' + expect(editor.getSelection().getBufferRange()).toEqual [[3,31], [3,38]] + + describe "when command-pane:repeat-relative-address-in-reverse is triggered on the root view", -> + it "it repeats the last relative address in the reverse direction", -> + rootView.trigger 'command-panel:repeat-relative-address-in-reverse' + + editor.setCursorScreenPosition([6, 0]) + + commandPanel.execute("/current") + expect(editor.getSelection().getBufferRange()).toEqual [[6,6], [6,13]] + + rootView.trigger 'command-panel:repeat-relative-address-in-reverse' + expect(editor.getSelection().getBufferRange()).toEqual [[5,6], [5,13]] describe "when command-panel:set-selection-as-regex-address is triggered on the root view", -> it "sets the @lastRelativeAddress to a RegexAddress of the current selection", -> diff --git a/src/app/display-buffer.coffee b/src/app/display-buffer.coffee index b75a482a5..5466ca582 100644 --- a/src/app/display-buffer.coffee +++ b/src/app/display-buffer.coffee @@ -108,8 +108,9 @@ class DisplayBuffer @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange, lineNumbersChanged: true destroyFoldsContainingBufferRow: (bufferRow) -> - folds = @activeFolds[bufferRow] ? [] - fold.destroy() for fold in new Array(folds...) + for row, folds of @activeFolds + for fold in new Array(folds...) + fold.destroy() if fold.getBufferRange().containsRow(bufferRow) registerFold: (fold) -> @activeFolds[fold.startRow] ?= [] diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 751d6aefe..a11b98801 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -193,7 +193,7 @@ class EditSession destroyFoldsContainingBufferRow: (bufferRow) -> @displayBuffer.destroyFoldsContainingBufferRow(bufferRow) - unfoldCurrentRow: (row) -> + unfoldCurrentRow: -> @displayBuffer.largestFoldStartingAtBufferRow(@getLastCursor().getCurrentBufferRow())?.destroy() destroyFold: (foldId) -> diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 3c2fc9923..2f37d7c8a 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -217,6 +217,7 @@ class Editor extends View softWrapColumn ?= @calcSoftWrapColumn() @activeEditSession.setSoftWrapColumn(softWrapColumn) if softWrapColumn + lineForScreenRow: (screenRow) -> @activeEditSession.lineForScreenRow(screenRow) linesForScreenRows: (start, end) -> @activeEditSession.linesForScreenRows(start, end) screenLineCount: -> @activeEditSession.screenLineCount() maxScreenLineLength: -> @activeEditSession.maxScreenLineLength() diff --git a/src/app/keymaps/command-panel.coffee b/src/app/keymaps/command-panel.coffee index 46ae2c3d3..0f7d1a7be 100644 --- a/src/app/keymaps/command-panel.coffee +++ b/src/app/keymaps/command-panel.coffee @@ -7,5 +7,6 @@ window.keymap.bindKeys '.command-panel .editor', window.keymap.bindKeys '.editor', 'meta-g': 'command-panel:repeat-relative-address' + 'meta-G': 'command-panel:repeat-relative-address-in-reverse' 'meta-e': 'command-panel:set-selection-as-regex-address' 'meta-f': 'command-panel:find-in-file' diff --git a/src/app/range.coffee b/src/app/range.coffee index 43ee43aa6..41f1136c7 100644 --- a/src/app/range.coffee +++ b/src/app/range.coffee @@ -50,6 +50,9 @@ class Range point = Point.fromObject(point) point.isGreaterThanOrEqual(@start) and point.isLessThanOrEqual(@end) + containsRow: (row) -> + @start.row <= row <= @end.row + union: (otherRange) -> start = if @start.isLessThan(otherRange.start) then @start else otherRange.start end = if @end.isGreaterThan(otherRange.end) then @end else otherRange.end diff --git a/src/extensions/command-interpreter.coffee b/src/extensions/command-interpreter.coffee index 7e0c91e2c..18dd8d114 100644 --- a/src/extensions/command-interpreter.coffee +++ b/src/extensions/command-interpreter.coffee @@ -14,3 +14,5 @@ class CommandInterpreter repeatRelativeAddress: (editor) -> @lastRelativeAddress?.execute(editor) + repeatRelativeAddressInReverse: (editor) -> + @lastRelativeAddress?.reverse().execute(editor) diff --git a/src/extensions/command-interpreter/address-range.coffee b/src/extensions/command-interpreter/address-range.coffee index 72fd46554..01aaab1f5 100644 --- a/src/extensions/command-interpreter/address-range.coffee +++ b/src/extensions/command-interpreter/address-range.coffee @@ -9,4 +9,4 @@ class AddressRange extends Address new Range(@startAddress.getRange(editor, currentRange).start, @endAddress.getRange(editor, currentRange).end) isRelative: -> - @startAddress.isRelative() or @endAddress.isRelative() + @startAddress.isRelative() and @endAddress.isRelative() diff --git a/src/extensions/command-interpreter/commands.pegjs b/src/extensions/command-interpreter/commands.pegjs index 8bc1d1c42..925845f8a 100644 --- a/src/extensions/command-interpreter/commands.pegjs +++ b/src/extensions/command-interpreter/commands.pegjs @@ -28,7 +28,10 @@ primitiveAddress = lineNumber:integer { return new LineAddress(lineNumber) } / '$' { return new EofAddress() } / '.' { return new CurrentSelectionAddress() } - / '/' pattern:pattern '/'? { return new RegexAddress(pattern)} + / regexAddress + +regexAddress + = reverse:'-'? '/' pattern:pattern '/'? { return new RegexAddress(pattern, reverse.length > 0)} command = substitution / selectAllMatches diff --git a/src/extensions/command-interpreter/composite-command.coffee b/src/extensions/command-interpreter/composite-command.coffee index f2deb6141..0dc9ceed2 100644 --- a/src/extensions/command-interpreter/composite-command.coffee +++ b/src/extensions/command-interpreter/composite-command.coffee @@ -10,8 +10,16 @@ class CompositeCommand currentRanges = editor.getSelectionsOrderedByBufferPosition().map (selection) -> selection.getBufferRange() for currentRange in currentRanges newRanges.push(command.execute(editor, currentRange)...) + + for range in newRanges + for row in [range.start.row..range.end.row] + editor.destroyFoldsContainingBufferRow(row) + editor.setSelectedBufferRanges(newRanges) + reverse: -> + new CompositeCommand(@subcommands.map (command) -> command.reverse()) + isRelativeAddress: -> _.all(@subcommands, (command) -> command.isAddress() and command.isRelative()) diff --git a/src/extensions/command-interpreter/regex-address.coffee b/src/extensions/command-interpreter/regex-address.coffee index 6ba6aa475..83cae9bb3 100644 --- a/src/extensions/command-interpreter/regex-address.coffee +++ b/src/extensions/command-interpreter/regex-address.coffee @@ -4,24 +4,33 @@ Range = require 'range' module.exports = class RegexAddress extends Address regex: null + reverse: null - constructor: (pattern) -> + constructor: (pattern, isReversed) -> + @isReversed = isReversed @regex = new RegExp(pattern) getRange: (editor, currentRange) -> - rangeToSearch = new Range(currentRange.end, editor.getEofPosition()) + rangeBefore = new Range([0, 0], currentRange.start) + rangeAfter = new Range(currentRange.end, editor.getEofPosition()) + + rangeToSearch = if @isReversed then rangeBefore else rangeAfter rangeToReturn = null - editor.buffer.scanInRange @regex, rangeToSearch, (match, range) -> + scanMethodName = if @isReversed then "backwardsScanInRange" else "scanInRange" + editor[scanMethodName] @regex, rangeToSearch, (match, range) -> rangeToReturn = range if rangeToReturn rangeToReturn else - rangeToSearch = new Range([0, 0], rangeToSearch.start) - editor.buffer.scanInRange @regex, rangeToSearch, (match, range) -> + rangeToSearch = if @isReversed then rangeAfter else rangeBefore + editor[scanMethodName] @regex, rangeToSearch, (match, range) -> rangeToReturn = range rangeToReturn or currentRange isRelative: -> true + + reverse: -> + new RegexAddress(@regex, !@isReversed) \ No newline at end of file diff --git a/src/extensions/command-panel.coffee b/src/extensions/command-panel.coffee index a4e712e47..cb0fb1e16 100644 --- a/src/extensions/command-panel.coffee +++ b/src/extensions/command-panel.coffee @@ -42,6 +42,7 @@ class CommandPanel extends View @rootView.on 'command-panel:execute', => @execute() @rootView.on 'command-panel:find-in-file', => @show("/") @rootView.on 'command-panel:repeat-relative-address', => @repeatRelativeAddress() + @rootView.on 'command-panel:repeat-relative-address-in-reverse', => @repeatRelativeAddressInReverse() @rootView.on 'command-panel:set-selection-as-regex-address', => @setSelectionAsLastRelativeAddress() @miniEditor.off 'move-up move-down' @@ -88,6 +89,9 @@ class CommandPanel extends View repeatRelativeAddress: -> @commandInterpreter.repeatRelativeAddress(@rootView.activeEditor()) + repeatRelativeAddressInReverse: -> + @commandInterpreter.repeatRelativeAddressInReverse(@rootView.activeEditor()) + setSelectionAsLastRelativeAddress: -> selection = @rootView.activeEditor().getSelectedText() regex = _.escapeRegExp(selection)