diff --git a/spec/app/edit-session-spec.coffee b/spec/app/edit-session-spec.coffee index 6f72cd8bc..c77ddd15c 100644 --- a/spec/app/edit-session-spec.coffee +++ b/spec/app/edit-session-spec.coffee @@ -276,17 +276,22 @@ describe "EditSession", -> editSession.moveCursorToBeginningOfWord() expect(cursor1.getBufferPosition()).toEqual [0, 4] - expect(cursor2.getBufferPosition()).toEqual [1, 10] + expect(cursor2.getBufferPosition()).toEqual [1, 11] expect(cursor3.getBufferPosition()).toEqual [2, 39] it "does not fail at position [0, 0]", -> editSession.setCursorBufferPosition([0, 0]) editSession.moveCursorToBeginningOfWord() - it "works when the preceding line is blank", -> + it "treats lines with only whitespace as a word", -> + editSession.setCursorBufferPosition([11, 0]) + editSession.moveCursorToBeginningOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [10, 0] + + it "works when the current line is blank", -> editSession.setCursorBufferPosition([10, 0]) editSession.moveCursorToBeginningOfWord() - expect(editSession.getCursorBufferPosition()).toEqual [9, 0] + expect(editSession.getCursorBufferPosition()).toEqual [9, 2] describe ".moveCursorToEndOfWord()", -> it "moves the cursor to the end of the word", -> @@ -298,8 +303,8 @@ describe "EditSession", -> editSession.moveCursorToEndOfWord() expect(cursor1.getBufferPosition()).toEqual [0, 13] - expect(cursor2.getBufferPosition()).toEqual [1, 13] - expect(cursor3.getBufferPosition()).toEqual [3, 4] + expect(cursor2.getBufferPosition()).toEqual [1, 12] + expect(cursor3.getBufferPosition()).toEqual [3, 7] it "does not blow up when there is no next word", -> editSession.setCursorBufferPosition [Infinity, Infinity] @@ -307,6 +312,17 @@ describe "EditSession", -> editSession.moveCursorToEndOfWord() expect(editSession.getCursorBufferPosition()).toEqual endPosition + it "treats lines with only whitespace as a word", -> + editSession.setCursorBufferPosition([9, 4]) + editSession.moveCursorToEndOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [10, 0] + + it "works when the current line is blank", -> + editSession.setCursorBufferPosition([10, 0]) + editSession.moveCursorToEndOfWord() + expect(editSession.getCursorBufferPosition()).toEqual [11, 8] + + describe ".getCurrentParagraphBufferRange()", -> it "returns the buffer range of the current paragraph, delimited by blank lines or the beginning / end of the file", -> buffer.setText """ @@ -518,13 +534,13 @@ describe "EditSession", -> expect(editSession.getCursors().length).toBe 2 [cursor1, cursor2] = editSession.getCursors() expect(cursor1.getBufferPosition()).toEqual [0,4] - expect(cursor2.getBufferPosition()).toEqual [3,44] + expect(cursor2.getBufferPosition()).toEqual [3,47] expect(editSession.getSelections().length).toBe 2 [selection1, selection2] = editSession.getSelections() expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]] expect(selection1.isReversed()).toBeTruthy() - expect(selection2.getBufferRange()).toEqual [[3,44], [3,49]] + expect(selection2.getBufferRange()).toEqual [[3,47], [3,49]] expect(selection2.isReversed()).toBeTruthy() describe ".selectToEndOfWord()", -> @@ -537,40 +553,49 @@ describe "EditSession", -> expect(editSession.getCursors().length).toBe 2 [cursor1, cursor2] = editSession.getCursors() expect(cursor1.getBufferPosition()).toEqual [0,13] - expect(cursor2.getBufferPosition()).toEqual [3,51] + expect(cursor2.getBufferPosition()).toEqual [3,50] expect(editSession.getSelections().length).toBe 2 [selection1, selection2] = editSession.getSelections() expect(selection1.getBufferRange()).toEqual [[0,4], [0,13]] expect(selection1.isReversed()).toBeFalsy() - expect(selection2.getBufferRange()).toEqual [[3,48], [3,51]] + expect(selection2.getBufferRange()).toEqual [[3,48], [3,50]] expect(selection2.isReversed()).toBeFalsy() describe ".selectWord()", -> - describe "when the cursor is inside a word", -> - it "selects the entire word", -> - editSession.setCursorScreenPosition([0, 8]) - editSession.selectWord() - expect(editSession.getSelectedText()).toBe 'quicksort' + describe "when the cursor is inside a word", -> + it "selects the entire word", -> + editSession.setCursorScreenPosition([0, 8]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'quicksort' - describe "when the cursor is between two words", -> - it "selects both words", -> - editSession.setCursorScreenPosition([0, 4]) - editSession.selectWord() - expect(editSession.getSelectedText()).toBe ' quicksort' + describe "when the cursor is between two words", -> + it "selects the word the cursor is on", -> + editSession.setCursorScreenPosition([0, 4]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'quicksort' - describe "when the cursor is inside a region of whitespace", -> - it "selects the whitespace region", -> - editSession.setCursorScreenPosition([5, 2]) - editSession.selectWord() - expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + editSession.setCursorScreenPosition([0, 3]) + editSession.selectWord() + expect(editSession.getSelectedText()).toBe 'var' - describe "when the cursor is at the end of the text", -> - it "select the previous word", -> - editSession.buffer.append 'word' - editSession.moveCursorToBottom() - editSession.selectWord() - expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]] + + describe "when the cursor is inside a region of whitespace", -> + it "selects the whitespace region", -> + editSession.setCursorScreenPosition([5, 2]) + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + + editSession.setCursorScreenPosition([5, 0]) + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[5, 0], [5, 6]] + + describe "when the cursor is at the end of the text", -> + it "select the previous word", -> + editSession.buffer.append 'word' + editSession.moveCursorToBottom() + editSession.selectWord() + expect(editSession.getSelectedBufferRange()).toEqual [[12, 2], [12, 6]] describe ".setSelectedBufferRanges(ranges)", -> it "clears existing selections and creates selections for each of the given ranges", -> @@ -1110,25 +1135,26 @@ describe "EditSession", -> describe "when no text is selected", -> it "deletes all text between the cursor and the beginning of the word", -> editSession.setCursorBufferPosition([1, 24]) - editSession.addCursorAtBufferPosition([2, 5]) + editSession.addCursorAtBufferPosition([3, 5]) [cursor1, cursor2] = editSession.getCursors() editSession.backspaceToBeginningOfWord() expect(buffer.lineForRow(1)).toBe ' var sort = function(ems) {' - expect(buffer.lineForRow(2)).toBe ' f (items.length <= 1) return items;' + expect(buffer.lineForRow(3)).toBe ' ar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 22] - expect(cursor2.getBufferPosition()).toEqual [2, 4] + expect(cursor2.getBufferPosition()).toEqual [3, 4] editSession.backspaceToBeginningOfWord() expect(buffer.lineForRow(1)).toBe ' var sort = functionems) {' - expect(buffer.lineForRow(2)).toBe 'f (items.length <= 1) return items;' + expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return itemsar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 21] - expect(cursor2.getBufferPosition()).toEqual [2, 0] + expect(cursor2.getBufferPosition()).toEqual [2, 39] editSession.backspaceToBeginningOfWord() - expect(buffer.lineForRow(1)).toBe ' var sort = emsf (items.length <= 1) return items;' + expect(buffer.lineForRow(1)).toBe ' var sort = ems) {' + expect(buffer.lineForRow(2)).toBe ' if (items.length <= 1) return ar pivot = items.shift(), current, left = [], right = [];' expect(cursor1.getBufferPosition()).toEqual [1, 13] - expect(cursor2.getBufferPosition()).toEqual [1, 16] + expect(cursor2.getBufferPosition()).toEqual [2, 34] describe "when text is selected", -> it "deletes only selected text", -> @@ -1296,7 +1322,7 @@ describe "EditSession", -> expect(cursor2.getBufferPosition()).toEqual [2, 5] editSession.deleteToEndOfWord() - expect(buffer.lineForRow(1)).toBe ' var sort = function(it' + expect(buffer.lineForRow(1)).toBe ' var sort = function(it {' expect(buffer.lineForRow(2)).toBe ' iitems.length <= 1) return items;' expect(cursor1.getBufferPosition()).toEqual [1, 24] expect(cursor2.getBufferPosition()).toEqual [2, 5] diff --git a/src/app/cursor.coffee b/src/app/cursor.coffee index 4599705f3..8ac44739e 100644 --- a/src/app/cursor.coffee +++ b/src/app/cursor.coffee @@ -9,7 +9,6 @@ class Cursor screenPosition: null bufferPosition: null goalColumn: null - wordRegex: /(\w+)|([^\w\n]+)/g visible: true needsAutoscroll: false @@ -56,9 +55,18 @@ class Cursor isVisible: -> @visible + wordRegExp: -> + nonWordCharacters = config.get("editor.nonWordCharacters") + new RegExp("^[\t ]*$|[^\\s#{_.escapeRegExp(nonWordCharacters)}]+|[#{_.escapeRegExp(nonWordCharacters)}]+", "g") + isLastCursor: -> this == @editSession.getCursor() + isSurroundedByWhitespace: -> + {row, column} = @getBufferPosition() + range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]] + /^\s+$/.test @editSession.getTextInBufferRange(range) + autoscrolled: -> @needsAutoscroll = false @@ -147,14 +155,16 @@ class Cursor allowPrevious = options.allowPrevious ? true currentBufferPosition = @getBufferPosition() previousNonBlankRow = @editSession.buffer.previousNonBlankRow(currentBufferPosition.row) - previousLinesRange = [[previousNonBlankRow, 0], currentBufferPosition] + range = [[previousNonBlankRow, 0], currentBufferPosition] - beginningOfWordPosition = currentBufferPosition - @editSession.backwardsScanInRange (options.wordRegex || @wordRegex), previousLinesRange, (match, matchRange, { stop }) => + beginningOfWordPosition = null + @editSession.backwardsScanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) => if matchRange.end.isGreaterThanOrEqual(currentBufferPosition) or allowPrevious beginningOfWordPosition = matchRange.start - stop() - beginningOfWordPosition + if not beginningOfWordPosition?.isEqual(currentBufferPosition) + stop() + + beginningOfWordPosition or currentBufferPosition getEndOfCurrentWordBufferPosition: (options = {}) -> allowNext = options.allowNext ? true @@ -162,11 +172,12 @@ class Cursor range = [currentBufferPosition, @editSession.getEofBufferPosition()] endOfWordPosition = null - @editSession.scanInRange (options.wordRegex || @wordRegex), range, (match, matchRange, { stop }) => - endOfWordPosition = matchRange.end - if not allowNext and matchRange.start.isGreaterThan(currentBufferPosition) - endOfWordPosition = currentBufferPosition - stop() + @editSession.scanInRange (options.wordRegex ? @wordRegExp()), range, (match, matchRange, { stop }) => + if matchRange.start.isLessThanOrEqual(currentBufferPosition) or allowNext + endOfWordPosition = matchRange.end + if not endOfWordPosition?.isEqual(currentBufferPosition) + stop() + endOfWordPosition or currentBufferPosition getCurrentWordBufferRange: (options={}) -> diff --git a/src/app/editor.coffee b/src/app/editor.coffee index 0fb628d4d..7d4ee0382 100644 --- a/src/app/editor.coffee +++ b/src/app/editor.coffee @@ -19,6 +19,7 @@ class Editor extends View autosave: false autoIndent: true autoIndentOnPaste: false + nonWordCharacters: "./\\()\"’-_:,.;<>~!@#$%^&*|+=[]{}`~?" @content: (params) -> @div class: @classes(params), tabindex: -1, => diff --git a/src/app/selection.coffee b/src/app/selection.coffee index 8123d1157..abc702103 100644 --- a/src/app/selection.coffee +++ b/src/app/selection.coffee @@ -96,7 +96,10 @@ class Selection @screenRangeChanged() selectWord: -> - @setBufferRange(@cursor.getCurrentWordBufferRange()) + options = {} + options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace() + + @setBufferRange(@cursor.getCurrentWordBufferRange(options)) @wordwise = true @initialScreenRange = @getScreenRange()