From 5e89c44477a72d542af9052fa91f8520b79e7688 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 27 Mar 2012 15:39:07 -0700 Subject: [PATCH] Match $ in command regexes as end of line. Don't infinitely loop on substituting zero-width matches --- spec/atom/command-interpreter-spec.coffee | 23 +++++++++++++++++++ src/atom/command-interpreter/address.coffee | 4 +++- src/atom/command-interpreter/command.coffee | 3 +++ .../command-interpreter/regex-address.coffee | 2 +- .../select-all-matches.coffee | 2 +- .../command-interpreter/substitution.coffee | 9 +++++--- 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/spec/atom/command-interpreter-spec.coffee b/spec/atom/command-interpreter-spec.coffee index 3d99616c6..97a16da41 100644 --- a/spec/atom/command-interpreter-spec.coffee +++ b/spec/atom/command-interpreter-spec.coffee @@ -11,25 +11,34 @@ describe "CommandInterpreter", -> interpreter = new CommandInterpreter() describe "addresses", -> + beforeEach -> + editor.addSelectionForBufferRange([[7,0], [7,11]]) + editor.addSelectionForBufferRange([[8,0], [8,11]]) + describe "a line address", -> it "selects the specified line", -> interpreter.eval(editor, '4') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [4, 0]] describe "0", -> it "selects the zero-length string at the start of the file", -> interpreter.eval(editor, '0') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [0,0]] interpreter.eval(editor, '0,1') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [1,0]] describe "$", -> it "selects EOF", -> interpreter.eval(editor, '$') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[12,2], [12,2]] interpreter.eval(editor, '1,$') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[0,0], [12,2]] describe ".", -> @@ -50,32 +59,38 @@ describe "CommandInterpreter", -> it 'selects text matching regex after current selection', -> editor.getSelection().setBufferRange([[4,16], [4,20]]) interpreter.eval(editor, '/pivot/') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[6,16], [6,21]] it 'does not require the trailing slash', -> editor.getSelection().setBufferRange([[4,16], [4,20]]) interpreter.eval(editor, '/pivot') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[6,16], [6,21]] 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", -> interpreter.eval(editor, '4,7') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [7, 0]] describe "when the left address is unspecified", -> it "selects from the begining of buffer to the end of the right address", -> interpreter.eval(editor, ',7') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[0, 0], [7, 0]] describe "when the right address is unspecified", -> it "selects from the begining of left address to the end file", -> interpreter.eval(editor, '4,') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[3, 0], [12, 2]] describe "when the neither address is specified", -> it "selects the entire file", -> interpreter.eval(editor, ',') + expect(editor.getSelections().length).toBe 1 expect(editor.getSelection().getBufferRange()).toEqual [[0, 0], [12, 2]] describe "x/regex/", -> @@ -130,6 +145,14 @@ describe "CommandInterpreter", -> expect(buffer.lineForRow(5)).toBe '!!!!!!current!=!items.shift();' expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);' + describe "when the regex matches a zero-width string", -> + it "does not infinitely loop when looking for the next match", -> + interpreter.eval(editor, ',s/$/!!!/g') + expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {!!!' + expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return items;!!!' + expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(current) : right.push(current);!!!' + expect(buffer.lineForRow(12)).toBe '};!!!' + describe ".repeatRelativeAddress()", -> it "repeats the last search command if there is one", -> interpreter.repeatRelativeAddress(editor) # don't raise an exception diff --git a/src/atom/command-interpreter/address.coffee b/src/atom/command-interpreter/address.coffee index c587e2486..db2e33a48 100644 --- a/src/atom/command-interpreter/address.coffee +++ b/src/atom/command-interpreter/address.coffee @@ -3,6 +3,8 @@ Command = require 'command-interpreter/command' module.exports = class Address extends Command execute: (editor) -> - editor.getSelection().setBufferRange(@getRange(editor)) + range = @getRange(editor) + editor.clearSelections() + editor.setSelectionBufferRange(range) isAddress: -> true diff --git a/src/atom/command-interpreter/command.coffee b/src/atom/command-interpreter/command.coffee index c60b685b0..32c35f7df 100644 --- a/src/atom/command-interpreter/command.coffee +++ b/src/atom/command-interpreter/command.coffee @@ -3,3 +3,6 @@ _ = require 'underscore' module.exports = class Command isAddress: -> false + + regexForPattern: (pattern) -> + new RegExp(pattern, 'm') diff --git a/src/atom/command-interpreter/regex-address.coffee b/src/atom/command-interpreter/regex-address.coffee index bec05c71b..599b2973f 100644 --- a/src/atom/command-interpreter/regex-address.coffee +++ b/src/atom/command-interpreter/regex-address.coffee @@ -6,7 +6,7 @@ class RegexAddress extends Address regex: null constructor: (pattern) -> - @regex = new RegExp(pattern) + @regex = @regexForPattern(pattern) getRange: (editor) -> selectedRange = editor.getSelection().getBufferRange() diff --git a/src/atom/command-interpreter/select-all-matches.coffee b/src/atom/command-interpreter/select-all-matches.coffee index 44d05de7e..2f5793280 100644 --- a/src/atom/command-interpreter/select-all-matches.coffee +++ b/src/atom/command-interpreter/select-all-matches.coffee @@ -6,7 +6,7 @@ class SelectAllMatches extends Command @regex: null constructor: (pattern) -> - @regex = new RegExp(pattern) + @regex = @regexForPattern(pattern) execute: (editor) -> rangesToSelect = [] diff --git a/src/atom/command-interpreter/substitution.coffee b/src/atom/command-interpreter/substitution.coffee index 86a93b7b0..c08535321 100644 --- a/src/atom/command-interpreter/substitution.coffee +++ b/src/atom/command-interpreter/substitution.coffee @@ -5,7 +5,7 @@ class Substitution extends Command global: false constructor: (@findText, @replaceText, @options) -> - @findRegex = new RegExp(@findText) + @findRegex = @regexForPattern(@findText) @global = 'g' in @options execute: (editor) -> @@ -27,7 +27,10 @@ class Substitution extends Command buffer.change([startPosition, endPosition], @replaceText) if @global - text = text[(match.index + match[0].length)..] - startIndex = matchStartIndex + @replaceText.length + offset = if match[0].length then 0 else 1 + startNextStringFragmentAt = match.index + match[0].length + offset + return if startNextStringFragmentAt >= text.length + text = text[startNextStringFragmentAt..] + startIndex = matchStartIndex + offset + @replaceText.length @replace(editor, text, startIndex)