diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 2009ae721..fc1b46043 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -65,13 +65,13 @@ describe "Editor", -> expect(editor.lines.find('pre:eq(3)').text()).toBe " var pivot = items.shift(), current, left = [], " expect(editor.lines.find('pre:eq(4)').text()).toBe "right = [];" - editor.cursor.setScreenPosition([3, 51]) + editor.cursor.setBufferPosition([3, 51]) expect(editor.cursor.position()).toEqual(editor.lines.find('pre:eq(4)').position()) - editor.cursor.setScreenPosition([4, 0]) + editor.cursor.setBufferPosition([4, 0]) expect(editor.cursor.position()).toEqual(editor.lines.find('pre:eq(5)').position()) - editor.selection.setRange(new Range([6, 30], [6, 55])) + editor.selection.setBufferRange(new Range([6, 30], [6, 55])) [region1, region2] = editor.selection.regions expect(region1.position().top).toBe(editor.lines.find('.line:eq(7)').position().top) expect(region2.position().top).toBe(editor.lines.find('.line:eq(8)').position().top) @@ -98,6 +98,31 @@ describe "Editor", -> $(window).trigger 'resize' expect(editor.setMaxLineLength).not.toHaveBeenCalled() + it "allows the cursor to move down to the last line", -> + _.times editor.lastScreenRow(), -> editor.moveCursorDown() + expect(editor.getCursorScreenPosition()).toEqual [editor.lastScreenRow(), 0] + editor.moveCursorDown() + expect(editor.getCursorScreenPosition()).toEqual [editor.lastScreenRow(), 2] + + it "allows the cursor to move up to a shorter soft wrapped line", -> + editor.setCursorScreenPosition([11, 15]) + editor.moveCursorUp() + expect(editor.getCursorScreenPosition()).toEqual [10, 10] + editor.moveCursorUp() + editor.moveCursorUp() + expect(editor.getCursorScreenPosition()).toEqual [8, 15] + + it "it allows the cursor to wrap when moving horizontally past the beginning / end of a wrapped line", -> + editor.setCursorScreenPosition([11, 0]) + editor.moveCursorLeft() + expect(editor.getCursorScreenPosition()).toEqual [10, 10] + + editor.moveCursorRight() + expect(editor.getCursorScreenPosition()).toEqual [11, 0] + + + + describe "cursor movement", -> describe ".setCursorScreenPosition({row, column})", -> beforeEach -> @@ -333,7 +358,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [4, 7]) editor.lines.trigger mousedownEvent({pageX, pageY}) - expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 58) + expect(editor.getCursorBufferPosition()).toEqual(row: 3, column: 58) describe "when soft-wrap is disabled", -> describe "when it is a single click", -> @@ -394,33 +419,33 @@ describe "Editor", -> editor.trigger keydownEvent('right', shiftKey: true) expect(selection.isEmpty()).toBeFalsy() - range = selection.getRange() + range = selection.getScreenRange() expect(range.start).toEqual(row: 1, column: 6) expect(range.end).toEqual(row: 1, column: 7) editor.trigger keydownEvent('right', shiftKey: true) - range = selection.getRange() + range = selection.getScreenRange() expect(range.start).toEqual(row: 1, column: 6) expect(range.end).toEqual(row: 1, column: 8) editor.trigger keydownEvent('down', shiftKey: true) - range = selection.getRange() + range = selection.getScreenRange() expect(range.start).toEqual(row: 1, column: 6) expect(range.end).toEqual(row: 2, column: 8) editor.trigger keydownEvent('left', shiftKey: true) - range = selection.getRange() + range = selection.getScreenRange() expect(range.start).toEqual(row: 1, column: 6) expect(range.end).toEqual(row: 2, column: 7) editor.trigger keydownEvent('up', shiftKey: true) - range = selection.getRange() + range = selection.getScreenRange() expect(range.start).toEqual(row: 1, column: 6) expect(range.end).toEqual(row: 1, column: 7) describe "when the arrow keys are pressed without the shift modifier", -> makeNonEmpty = -> - selection.setRange(new Range({row: 1, column: 2}, {row: 1, column: 5})) + selection.setBufferRange(new Range({row: 1, column: 2}, {row: 1, column: 5})) expect(selection.isEmpty()).toBeFalsy() it "clears the selection", -> @@ -453,7 +478,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [5, 27]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 10}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -465,7 +490,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [8, 8]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 10}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -484,7 +509,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [5, 27]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 4}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -496,7 +521,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [8, 8]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 4}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -518,7 +543,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [5, 27]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 0}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -530,7 +555,7 @@ describe "Editor", -> [pageX, pageY] = window.pixelPositionForPoint(editor, [8, 8]) editor.lines.trigger mousemoveEvent({pageX, pageY}) - range = editor.selection.getRange() + range = editor.selection.getScreenRange() expect(range.start).toEqual({row: 4, column: 0}) expect(range.end).toEqual({row: 5, column: 27}) expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27) @@ -551,7 +576,7 @@ describe "Editor", -> describe "when there is a selection", -> it "replaces the selected text with the typed text", -> - editor.selection.setRange(new Range([1, 6], [2, 4])) + editor.selection.setBufferRange(new Range([1, 6], [2, 4])) editor.hiddenInput.textInput 'q' expect(buffer.getLine(1)).toBe ' var qif (items.length <= 1) return items;' @@ -625,7 +650,7 @@ describe "Editor", -> describe "when there is a selection", -> it "deletes the selection, but not the character before it", -> - editor.selection.setRange(new Range([0,5], [0,9])) + editor.selection.setBufferRange(new Range([0,5], [0,9])) editor.trigger keydownEvent('backspace') expect(editor.buffer.getLine(0)).toBe 'var qsort = function () {' @@ -644,7 +669,7 @@ describe "Editor", -> describe "when there is a selection", -> it "deletes the selection, but not the character following it", -> - editor.selection.setRange(new Range([1,6], [1,8])) + editor.selection.setBufferRange(new Range([1,6], [1,8])) editor.trigger keydownEvent 'delete' expect(buffer.getLine(1)).toBe ' var rt = function(items) {' @@ -697,12 +722,12 @@ describe "Editor", -> it "sets the cursor to the beginning of the file", -> expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0) - describe ".clipPosition(point)", -> + describe ".clipScreenPosition(point)", -> it "selects the nearest valid position to the given point", -> - expect(editor.clipPosition(row: 1000, column: 0)).toEqual(row: buffer.lastRow(), column: buffer.getLine(buffer.lastRow()).length) - expect(editor.clipPosition(row: -5, column: 0)).toEqual(row: 0, column: 0) - expect(editor.clipPosition(row: 1, column: 10000)).toEqual(row: 1, column: buffer.getLine(1).length) - expect(editor.clipPosition(row: 1, column: -5)).toEqual(row: 1, column: 0) + expect(editor.clipScreenPosition(row: 1000, column: 0)).toEqual(row: buffer.lastRow(), column: buffer.getLine(buffer.lastRow()).length) + expect(editor.clipScreenPosition(row: -5, column: 0)).toEqual(row: 0, column: 0) + expect(editor.clipScreenPosition(row: 1, column: 10000)).toEqual(row: 1, column: buffer.getLine(1).length) + expect(editor.clipScreenPosition(row: 1, column: -5)).toEqual(row: 1, column: 0) describe "cut, copy & paste", -> beforeEach -> @@ -711,14 +736,14 @@ describe "Editor", -> describe "when a cut event is triggered", -> it "removes the selected text from the buffer and places it on the pasteboard", -> - editor.getSelection().setRange new Range([0,4], [0,9]) + editor.getSelection().setBufferRange new Range([0,4], [0,9]) editor.trigger "cut" expect(editor.buffer.getLine(0)).toBe "var sort = function () {" expect(atom.native.readFromPasteboard()).toBe 'quick' describe "when a copy event is triggered", -> it "copies selected text onto the clipboard", -> - editor.getSelection().setRange new Range([0,4], [0, 13]) + editor.getSelection().setBufferRange new Range([0,4], [0, 13]) editor.trigger "copy" expect(atom.native.readFromPasteboard()).toBe 'quicksort' @@ -729,14 +754,30 @@ describe "Editor", -> expect(editor.buffer.getLine(0)).toBe "var firstquicksort = function () {" expect(editor.buffer.getLine(1)).toBe " var sort = function(items) {" - editor.getSelection().setRange new Range([1,6], [1,10]) + editor.getSelection().setBufferRange new Range([1,6], [1,10]) editor.trigger "paste" expect(editor.buffer.getLine(1)).toBe " var first = function(items) {" describe "folding", -> describe "when a fold-selection event is triggered", -> - it "folds the selected text and renders a placeholder for it", -> - editor.selection.setRange(new Range([4, 29], [7, 4])) + it "folds the selected text and moves the cursor to just after the placeholder", -> + editor.selection.setBufferRange(new Range([4, 29], [7, 4])) + editor.trigger 'fold-selection' expect(editor.lines.find('.line:eq(4)').text()).toBe ' while(items.length > 0) {...}' + expect(editor.selection.isEmpty()).toBeTruthy() + expect(editor.getCursorScreenPosition()).toEqual [4, 32] + + editor.setCursorScreenPosition([9, 2]) + expect(editor.getCursorScreenPosition()).toEqual [9, 2] + + buffer.insert([9, 4], 'x') + expect(editor.getCursorScreenPosition()).toEqual [6, 5] + expect(editor.getCursorBufferPosition()).toEqual [9, 5] + + editor.setCursorScreenPosition([4, 30]) + expect(editor.getCursorScreenPosition()).toEqual [4, 29] + editor.moveCursorRight() + expect(editor.getCursorScreenPosition()).toEqual [4, 32] + diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 7c9d26f90..af0d453f3 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -312,22 +312,33 @@ describe "LineFolder", -> expect(folder.bufferPositionForScreenPosition([4, 5])).toEqual [4, 5] expect(folder.bufferPositionForScreenPosition([4, 13])).toEqual [4, 15] expect(folder.bufferPositionForScreenPosition([4, 18])).toEqual [4, 20] - describe ".clipScreenPosition(screenPosition)", -> + + describe ".clipScreenPosition(screenPosition, eagerWrap=false)", -> beforeEach -> folder.createFold(new Range([4, 29], [7, 4])) it "returns the nearest valid position based on the current screen lines", -> expect(folder.clipScreenPosition([-1, -1])).toEqual [0, 0] expect(folder.clipScreenPosition([0, -1])).toEqual [0, 0] + expect(folder.clipScreenPosition([-1, 5])).toEqual [0, 0] expect(folder.clipScreenPosition([1, 10000])).toEqual [1, 30] expect(folder.clipScreenPosition([2, 15])).toEqual [2, 15] expect(folder.clipScreenPosition([4, 32])).toEqual [4, 32] expect(folder.clipScreenPosition([4, 1000])).toEqual [4, 33] - expect(folder.clipScreenPosition([1000, 1000])).toEqual [10, 2] + expect(folder.clipScreenPosition([1000, 1000])).toEqual [9, 2] - it "clips positions inside a placeholder to the beginning of the placeholder", -> - expect(folder.clipScreenPosition([4, 30])).toEqual [4, 29] - expect(folder.clipScreenPosition([4, 31])).toEqual [4, 29] + describe "when skipAtomicTokens is false (the default)", -> + it "clips positions inside a placeholder to the beginning of the placeholder", -> + expect(folder.clipScreenPosition([4, 30])).toEqual [4, 29] + expect(folder.clipScreenPosition([4, 31])).toEqual [4, 29] + expect(folder.clipScreenPosition([4, 32])).toEqual [4, 32] + + describe "when skipAtomicTokens is true", -> + it "clips positions inside a placeholder to the end of the placeholder", -> + expect(folder.clipScreenPosition([4, 29], skipAtomicTokens: true)).toEqual [4, 29] + expect(folder.clipScreenPosition([4, 30], skipAtomicTokens: true)).toEqual [4, 32] + expect(folder.clipScreenPosition([4, 31], skipAtomicTokens: true)).toEqual [4, 32] + expect(folder.clipScreenPosition([4, 32], skipAtomicTokens: true)).toEqual [4, 32] diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 49e86284c..8669b3048 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -129,6 +129,7 @@ describe "LineWrapper", -> it "adjusts the position to account for the fold", -> fold = folder.createFold(new Range([4, 29], [7, 4])) expect(wrapper.screenPositionForBufferPosition([7, 4])).toEqual [5, 32] + expect(wrapper.screenPositionForBufferPosition([8, 12])).toEqual [6, 12] describe ".bufferPositionForScreenPosition(point)", -> it "translates the given screen position to a buffer position, account for wrapped lines", -> @@ -147,6 +148,7 @@ describe "LineWrapper", -> it "adjusts the position to account for the fold", -> fold = folder.createFold(new Range([4, 29], [7, 4])) expect(wrapper.bufferPositionForScreenPosition([5, 32])).toEqual [7, 4] + expect(wrapper.bufferPositionForScreenPosition([6, 12])).toEqual [8, 12] describe ".wrapScreenLine(screenLine)", -> makeTokens = (tokenValues...) -> @@ -235,3 +237,56 @@ describe "LineWrapper", -> expect(line2.endColumn).toBe 14 expect(line2.text.length).toBe 3 + describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", -> + it "allows valid positions", -> + expect(wrapper.clipScreenPosition([4, 5])).toEqual [4, 5] + expect(wrapper.clipScreenPosition([4, 11])).toEqual [4, 11] + + it "disallows negative positions", -> + expect(wrapper.clipScreenPosition([-1, -1])).toEqual [0, 0] + expect(wrapper.clipScreenPosition([-1, 10])).toEqual [0, 0] + expect(wrapper.clipScreenPosition([0, -1])).toEqual [0, 0] + + it "disallows positions beyond the last row", -> + expect(wrapper.clipScreenPosition([1000, 0])).toEqual [15, 2] + expect(wrapper.clipScreenPosition([1000, 1000])).toEqual [15, 2] + + describe "when wrapBeyondNewlines is false (the default)", -> + it "wraps positions beyond the end of hard newlines to the end of the line", -> + expect(wrapper.clipScreenPosition([1, 10000])).toEqual [1, 30] + expect(wrapper.clipScreenPosition([4, 30])).toEqual [4, 11] + expect(wrapper.clipScreenPosition([4, 1000])).toEqual [4, 11] + + describe "when wrapBeyondNewlines is true", -> + it "wraps positions past the end of hard newlines to the next line", -> + expect(wrapper.clipScreenPosition([0, 29], wrapBeyondNewlines: true)).toEqual [0, 29] + expect(wrapper.clipScreenPosition([0, 30], wrapBeyondNewlines: true)).toEqual [1, 0] + expect(wrapper.clipScreenPosition([0, 1000], wrapBeyondNewlines: true)).toEqual [1, 0] + + describe "when wrapAtSoftNewlines is false (the default)", -> + it "wraps positions at the end of soft-wrapped lines to the character preceding the end of the line", -> + expect(wrapper.clipScreenPosition([3, 50])).toEqual [3, 50] + expect(wrapper.clipScreenPosition([3, 51])).toEqual [3, 50] + expect(wrapper.clipScreenPosition([3, 58])).toEqual [3, 50] + expect(wrapper.clipScreenPosition([3, 1000])).toEqual [3, 50] + + describe "when wrapAtSoftNewlines is true", -> + it "wraps positions at the end of soft-wrapped lines to the next screen line", -> + expect(wrapper.clipScreenPosition([3, 50], wrapAtSoftNewlines: true)).toEqual [3, 50] + expect(wrapper.clipScreenPosition([3, 51], wrapAtSoftNewlines: true)).toEqual [4, 0] + expect(wrapper.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 0] + expect(wrapper.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 0] + + describe "when skipAtomicTokens is false (the default)", -> + it "clips screen positions in the middle of fold placeholders to the to the beginning of fold placeholders", -> + folder.createFold(new Range([3, 55], [3, 59])) + expect(wrapper.clipScreenPosition([4, 5])).toEqual [4, 4] + expect(wrapper.clipScreenPosition([4, 6])).toEqual [4, 4] + expect(wrapper.clipScreenPosition([4, 7])).toEqual [4, 7] + + describe "when skipAtomicTokens is true", -> + it "wraps the screen positions in the middle of fold placeholders to the end of the placeholder", -> + folder.createFold(new Range([3, 55], [3, 59])) + expect(wrapper.clipScreenPosition([4, 4], skipAtomicTokens: true)).toEqual [4, 4] + expect(wrapper.clipScreenPosition([4, 5], skipAtomicTokens: true)).toEqual [4, 7] + expect(wrapper.clipScreenPosition([4, 6], skipAtomicTokens: true)).toEqual [4, 7] diff --git a/spec/atom/selection-spec.coffee b/spec/atom/selection-spec.coffee index ee060b3f7..886c97d6f 100644 --- a/spec/atom/selection-spec.coffee +++ b/spec/atom/selection-spec.coffee @@ -12,34 +12,34 @@ describe "Selection", -> editor.setBuffer(buffer) selection = editor.selection - describe ".setRange(range)", -> + describe ".setBufferRange(range)", -> it "places the anchor at the start of the range and the cursor at the end", -> range = new Range({row: 2, column: 7}, {row: 3, column: 18}) - selection.setRange(range) + selection.setBufferRange(range) expect(selection.anchor.getScreenPosition()).toEqual range.start expect(selection.cursor.getScreenPosition()).toEqual range.end describe ".delete()", -> describe "when nothing is selected", -> it "deletes nothing", -> - selection.setRange new Range([0,3], [0,3]) + selection.setBufferRange new Range([0,3], [0,3]) selection.delete() expect(editor.buffer.getLine(0)).toBe "var quicksort = function () {" describe "when one line is selected", -> it "deletes selected text", -> - selection.setRange new Range([0,4], [0,14]) + selection.setBufferRange new Range([0,4], [0,14]) selection.delete() expect(editor.buffer.getLine(0)).toBe "var = function () {" endOfLine = editor.buffer.getLine(0).length - selection.setRange new Range([0,0], [0, endOfLine]) + selection.setBufferRange new Range([0,0], [0, endOfLine]) selection.delete() expect(editor.buffer.getLine(0)).toBe "" describe "when multiple lines are selected", -> it "deletes selected text", -> - selection.setRange new Range([0,1], [2,39]) + selection.setBufferRange new Range([0,1], [2,39]) selection.delete() expect(editor.buffer.getLine(0)).toBe "v;" @@ -53,7 +53,7 @@ describe "Selection", -> describe "when the selection is within a single line", -> it "covers the selection's range with a single region", -> - selection.setRange(new Range({row: 2, column: 7}, {row: 2, column: 25})) + selection.setBufferRange(new Range({row: 2, column: 7}, {row: 2, column: 25})) expect(selection.regions.length).toBe 1 region = selection.regions[0] @@ -64,7 +64,7 @@ describe "Selection", -> describe "when the selection spans 2 lines", -> it "covers the selection's range with 2 regions", -> - selection.setRange(new Range({row: 2, column: 7}, {row: 3, column: 25})) + selection.setBufferRange(new Range({row: 2, column: 7}, {row: 3, column: 25})) expect(selection.regions.length).toBe 2 @@ -82,7 +82,7 @@ describe "Selection", -> describe "when the selection spans more than 2 lines", -> it "covers the selection's range with 3 regions", -> - selection.setRange(new Range({row: 2, column: 7}, {row: 6, column: 25})) + selection.setBufferRange(new Range({row: 2, column: 7}, {row: 6, column: 25})) expect(selection.regions.length).toBe 3 @@ -110,7 +110,7 @@ describe "Selection", -> expect(region3.width()).toBe(25 * charWidth) it "clears previously drawn regions before creating new ones", -> - selection.setRange(new Range({row: 2, column: 7}, {row: 4, column: 25})) + selection.setBufferRange(new Range({row: 2, column: 7}, {row: 4, column: 25})) expect(selection.regions.length).toBe 3 expect(selection.find('.selection').length).toBe 3 @@ -124,19 +124,19 @@ describe "Selection", -> expect(atom.native.readFromPasteboard()).toBe 'first' it "removes selected text from the buffer and places it on the clipboard", -> - selection.setRange new Range([0,4], [0,13]) + selection.setBufferRange new Range([0,4], [0,13]) selection.cut() expect(atom.native.readFromPasteboard()).toBe 'quicksort' expect(editor.buffer.getLine(0)).toBe "var = function () {" expect(selection.isEmpty()).toBeTruthy() - selection.setRange new Range([1,6], [3,8]) + selection.setBufferRange new Range([1,6], [3,8]) selection.cut() expect(atom.native.readFromPasteboard()).toBe "sort = function(items) {\n if (items.length <= 1) return items;\n var " expect(editor.buffer.getLine(1)).toBe " var pivot = items.shift(), current, left = [], right = [];" it "places nothing on the clipboard when there is no selection", -> - selection.setRange new Range([0,4], [0,4]) + selection.setBufferRange new Range([0,4], [0,4]) selection.copy() expect(atom.native.readFromPasteboard()).toBe 'first' @@ -146,16 +146,16 @@ describe "Selection", -> expect(atom.native.readFromPasteboard()).toBe 'first' it "places selected text on the clipboard", -> - selection.setRange new Range([0,4], [0,13]) + selection.setBufferRange new Range([0,4], [0,13]) selection.copy() expect(atom.native.readFromPasteboard()).toBe 'quicksort' - selection.setRange new Range([0,4], [3,13]) + selection.setBufferRange new Range([0,4], [3,13]) selection.copy() expect(atom.native.readFromPasteboard()).toBe "quicksort = function () {\n var sort = function(items) {\n if (items.length <= 1) return items;\n var pivot" it "places nothing on the clipboard when there is no selection", -> - selection.setRange new Range([0,4], [0,4]) + selection.setBufferRange new Range([0,4], [0,4]) selection.copy() expect(atom.native.readFromPasteboard()).toBe 'first' diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index ee4e8f6d5..b3f1ab9b8 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -13,11 +13,11 @@ class Cursor extends View @one 'attach', => @updateAppearance() bufferChanged: (e) -> - @setScreenPosition(e.newRange.end) + @setBufferPosition(e.newRange.end) - setScreenPosition: (point) -> - point = Point.fromObject(point) - @$position = @editor.clipPosition(point) + setScreenPosition: (position) -> + position = Point.fromObject(position) + @screenPosition = @editor.clipScreenPosition(position) @goalColumn = null @updateAppearance() @trigger 'cursor:position-changed' @@ -26,7 +26,13 @@ class Cursor extends View window.clearTimeout(@idleTimeout) if @idleTimeout @idleTimeout = window.setTimeout (=> @addClass 'idle'), 200 - getScreenPosition: -> _.clone(@$position) + setBufferPosition: (bufferPosition) -> + @setScreenPosition(@editor.screenPositionForBufferPosition(bufferPosition)) + + getBufferPosition: -> + @editor.bufferPositionForScreenPosition(@getScreenPosition()) + + getScreenPosition: -> _.clone(@screenPosition) getColumn: -> @getScreenPosition().column @@ -44,21 +50,13 @@ class Cursor extends View moveUp: -> { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - if row > 0 - @setScreenPosition({row: row - 1, column: column}) - else - @moveToLineStart() - + @setScreenPosition({row: row - 1, column: column}) @goalColumn = column moveDown: -> { row, column } = @getScreenPosition() column = @goalColumn if @goalColumn? - if row < @editor.buffer.numLines() - 1 - @setScreenPosition({row: row + 1, column: column}) - else - @moveToLineEnd() - + @setScreenPosition({row: row + 1, column: column}) @goalColumn = column moveToLineEnd: -> @@ -71,20 +69,16 @@ class Cursor extends View moveRight: -> { row, column } = @getScreenPosition() - if column < @editor.buffer.getLine(row).length - column++ - else if row < @editor.buffer.numLines() - 1 - row++ - column = 0 - @setScreenPosition({row, column}) + @setScreenPosition(@editor.clipScreenPosition([row, column + 1], skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)) moveLeft: -> { row, column } = @getScreenPosition() + if column > 0 column-- - else if row > 0 + else row-- - column = @editor.buffer.getLine(row).length + column = Infinity @setScreenPosition({row, column}) @@ -111,7 +105,7 @@ class Cursor extends View @setScreenPosition [row, column + offset] updateAppearance: -> - position = @editor.pixelPositionFromPoint(@getScreenPosition()) + position = @editor.pixelPositionForScreenPosition(@getScreenPosition()) @css(position) @autoScrollVertically(position) @autoScrollHorizontally(position) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index cbcb6e551..b822d41af 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -95,7 +95,7 @@ class Editor extends View clickCount = e.originalEvent.detail if clickCount == 1 - @setCursorScreenPosition @pointFromMouseEvent(e) + @setCursorScreenPosition @screenPositionFromMouseEvent(e) else if clickCount == 2 @selection.selectWord() else if clickCount >= 3 @@ -107,7 +107,7 @@ class Editor extends View @insertText(e.originalEvent.data) @on 'cursor:position-changed', => - @hiddenInput.css(@pixelPositionFromPoint(@cursor.getScreenPosition())) + @hiddenInput.css(@pixelPositionForScreenPosition(@cursor.getScreenPosition())) @one 'attach', => @calculateDimensions() @@ -116,7 +116,7 @@ class Editor extends View @focus() selectTextOnMouseMovement: -> - moveHandler = (e) => @selectToPosition(@pointFromMouseEvent(e)) + moveHandler = (e) => @selectToScreenPosition(@screenPositionFromMouseEvent(e)) @on 'mousemove', moveHandler $(document).one 'mouseup', => @off 'mousemove', moveHandler @@ -132,9 +132,21 @@ class Editor extends View renderLines: -> @lines.empty() - for screenLine in @lineWrapper.getLines() + for screenLine in @getScreenLines() @lines.append @buildLineElement(screenLine) + getScreenLines: -> + @lineWrapper.getLines() + + linesForScreenRows: (start, end) -> + @lineWrapper.linesForScreenRows(start, end) + + screenLineCount: -> + @lineWrapper.lineCount() + + lastScreenRow: -> + @screenLineCount() - 1 + setBuffer: (@buffer) -> @highlighter = new Highlighter(@buffer) @lineFolder = new LineFolder(@highlighter) @@ -146,9 +158,12 @@ class Editor extends View @buffer.on 'change', (e) => @cursor.bufferChanged(e) + @lineFolder.on 'fold', (range) => + @setCursorBufferPosition(range.end) + @lineWrapper.on 'change', (e) => { oldRange, newRange } = e - screenLines = @lineWrapper.linesForScreenRows(newRange.start.row, newRange.end.row) + screenLines = @linesForScreenRows(newRange.start.row, newRange.end.row) if newRange.end.row > oldRange.end.row # update, then insert elements for row in [newRange.start.row..newRange.end.row] @@ -202,27 +217,30 @@ class Editor extends View else $(window).off 'resize', @_setMaxLineLength - clipPosition: ({row, column}) -> - if row > @buffer.lastRow() - row = @buffer.lastRow() - column = @buffer.getLine(row).length - else - row = Math.min(Math.max(0, row), @buffer.numLines() - 1) - column = Math.min(Math.max(0, column), @buffer.getLine(row).length) + clipScreenPosition: (screenPosition, eagerWrap=false) -> + @lineWrapper.clipScreenPosition(screenPosition, eagerWrap) - new Point(row, column) - - pixelPositionFromPoint: (position) -> - { row, column } = @lineWrapper.screenPositionForBufferPosition(position) + pixelPositionForScreenPosition: ({row, column}) -> { top: row * @lineHeight, left: column * @charWidth } - pointFromPixelPosition: ({top, left}) -> + screenPositionFromPixelPosition: ({top, left}) -> screenPosition = new Point(Math.floor(top / @lineHeight), Math.floor(left / @charWidth)) - @lineWrapper.bufferPositionForScreenPosition screenPosition - pointFromMouseEvent: (e) -> + screenPositionForBufferPosition: (position) -> + @lineWrapper.screenPositionForBufferPosition(position) + + bufferPositionForScreenPosition: (position) -> + @lineWrapper.bufferPositionForScreenPosition(position) + + screenRangeForBufferRange: (range) -> + @lineWrapper.screenRangeForBufferRange(range) + + bufferRangeForScreenRange: (range) -> + @lineWrapper.bufferRangeForScreenRange(range) + + screenPositionFromMouseEvent: (e) -> { pageX, pageY } = e - @pointFromPixelPosition + @screenPositionFromPixelPosition top: pageY - @lines.offset().top left: pageX - @lines.offset().left @@ -254,8 +272,10 @@ class Editor extends View moveCursorDown: -> @cursor.moveDown() moveCursorRight: -> @cursor.moveRight() moveCursorLeft: -> @cursor.moveLeft() - setCursorScreenPosition: (point) -> @cursor.setScreenPosition(point) + setCursorScreenPosition: (position) -> @cursor.setScreenPosition(position) getCursorScreenPosition: -> @cursor.getScreenPosition() + setCursorBufferPosition: (position) -> @cursor.setBufferPosition(position) + getCursorBufferPosition: -> @cursor.getBufferPosition() setCursorRow: (row) -> @cursor.setRow(row) getCursorRow: -> @cursor.getRow() setCursorColumn: (column) -> @cursor.setColumn(column) @@ -265,8 +285,10 @@ class Editor extends View selectLeft: -> @selection.selectLeft() selectUp: -> @selection.selectUp() selectDown: -> @selection.selectDown() - selectToPosition: (position) -> - @selection.selectToPosition(position) + selectToScreenPosition: (position) -> + @selection.selectToScreenPosition(position) + selectToBufferPosition: (position) -> + @selection.selectToBufferPosition(position) insertText: (text) -> @selection.insertText(text) insertNewline: -> @selection.insertNewline() diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index d098df11a..e2c42d14e 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -21,9 +21,7 @@ class LineFolder @lineMap.insertAtBufferRow(0, @highlighter.screenLines) logLines: (start=0, end=@lastRow())-> - for row in [start..end] - line = @lineForScreenRow(row).text - console.log row, line, line.length + @lineMap.logLines(start, end) createFold: (bufferRange) -> fold = new Fold(this, bufferRange) @@ -116,7 +114,7 @@ class LineFolder @lineMap.lineForScreenRow(screenRow) getLines: -> - @lineMap.getScreenLines() + @lineMap.screenLinesForRows(0, @lastRow()) lineCount: -> @lineMap.screenLineCount() @@ -136,12 +134,15 @@ class LineFolder bufferPositionForScreenPosition: (screenPosition) -> @lineMap.bufferPositionForScreenPosition(screenPosition) - clipScreenPosition: (screenPosition) -> - @lineMap.clipScreenPosition(screenPosition) + clipScreenPosition: (screenPosition, options={}) -> + @lineMap.clipScreenPosition(screenPosition, options) screenRangeForBufferRange: (bufferRange) -> @lineMap.screenRangeForBufferRange(bufferRange) + bufferRangeForScreenRange: (screenRange) -> + @lineMap.bufferRangeForScreenRange(screenRange) + expandScreenRangeToLineEnds: (screenRange) -> { start, end } = screenRange new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length]) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index b63b14b7d..3cd9f0298 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -50,14 +50,10 @@ class LineMap replaceScreenRows: (start, end, screenLines) -> @spliceAtScreenRow(start, end - start + 1, screenLines) - getScreenLines: -> - return @screenLines - lineForScreenRow: (row) -> @linesForScreenRows(row, row)[0] linesForScreenRows: (startRow, endRow) -> - lastLine = null lines = [] delta = new Point @@ -67,11 +63,13 @@ class LineMap if pendingFragment pendingFragment = pendingFragment.concat(fragment) else - pendingFragment = fragment + pendingFragment = _.clone(fragment) if pendingFragment.screenDelta.row > 0 + pendingFragment.bufferDelta = new Point(1, 0) lines.push pendingFragment pendingFragment = null delta = delta.add(fragment.screenDelta) + lines lineForBufferRow: (row) -> @@ -99,6 +97,9 @@ class LineMap delta = delta.add(screenLine.screenDelta) delta.row + lastScreenRow: -> + @screenLineCount() - 1 + screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) -> bufferPosition = Point.fromObject(bufferPosition) bufferDelta = new Point @@ -135,18 +136,52 @@ class LineMap end = @screenPositionForBufferPosition(bufferRange.end) new Range(start, end) - clipScreenPosition: (screenPosition) -> + bufferRangeForScreenRange: (screenRange) -> + start = @bufferPositionForScreenPosition(screenRange.start) + end = @bufferPositionForScreenPosition(screenRange.end) + new Range(start, end) + + clipScreenPosition: (screenPosition, options) -> + wrapBeyondNewlines = options.wrapBeyondNewlines ? false + wrapAtSoftNewlines = options.wrapAtSoftNewlines ? false + skipAtomicTokens = options.skipAtomicTokens ? false screenPosition = Point.fromObject(screenPosition) - screenPosition = new Point(Math.max(0, screenPosition.row), Math.max(0, screenPosition.column)) + + screenPosition.column = Math.max(0, screenPosition.column) + + if screenPosition.row < 0 + screenPosition.row = 0 + screenPosition.column = 0 + + if screenPosition.row > @lastScreenRow() + screenPosition.row = @lastScreenRow() + screenPosition.column = Infinity screenDelta = new Point - for screenLine in @screenLines - nextDelta = screenDelta.add(screenLine.screenDelta) + for lineFragment in @screenLines + nextDelta = screenDelta.add(lineFragment.screenDelta) break if nextDelta.isGreaterThan(screenPosition) screenDelta = nextDelta - maxColumn = screenDelta.column + screenLine.lengthForClipping() - screenDelta.column = Math.min(maxColumn, screenPosition.column) + if lineFragment.isAtomic + if skipAtomicTokens and screenPosition.column > screenDelta.column + return new Point(screenDelta.row, screenDelta.column + lineFragment.text.length) + else + return screenDelta - screenDelta + maxColumn = screenDelta.column + lineFragment.text.length + if lineFragment.isSoftWrapped() and screenPosition.column >= maxColumn + if wrapAtSoftNewlines + return new Point(screenDelta.row + 1, 0) + else + return new Point(screenDelta.row, maxColumn - 1) + if screenPosition.column > maxColumn and wrapBeyondNewlines + return new Point(screenDelta.row + 1, 0) + + new Point(screenDelta.row, Math.min(maxColumn, screenPosition.column)) + + logLines: (start=0, end=@screenLineCount() - 1)-> + for row in [start..end] + line = @lineForScreenRow(row).text + console.log row, line, line.length diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 2faa8b730..f945e30b6 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -76,9 +76,6 @@ class LineWrapper return column + 1 if /\s/.test(line[column]) return @maxLength - screenRangeForBufferRange: (bufferRange) -> - @lineMap.screenRangeForBufferRange(bufferRange) - screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) -> @lineMap.screenPositionForBufferPosition( @lineFolder.screenPositionForBufferPosition(bufferPosition), @@ -88,6 +85,22 @@ class LineWrapper @lineFolder.bufferPositionForScreenPosition( @lineMap.bufferPositionForScreenPosition(screenPosition)) + screenRangeForBufferRange: (bufferRange) -> + @lineMap.screenRangeForBufferRange( + @lineFolder.screenRangeForBufferRange(bufferRange)) + + bufferRangeForScreenRange: (screenRange) -> + @lineFolder.bufferRangeForScreenRange( + @lineMap.bufferRangeForScreenRange(screenRange)) + + clipScreenPosition: (screenPosition, options={}) -> + @lineMap.screenPositionForBufferPosition( + @lineFolder.clipScreenPosition( + @lineMap.bufferPositionForScreenPosition(@lineMap.clipScreenPosition(screenPosition, options)), + options + ) + ) + lineForScreenRow: (screenRow) -> @linesForScreenRows(screenRow, screenRow)[0] @@ -95,9 +108,15 @@ class LineWrapper @lineMap.linesForScreenRows(startRow, endRow) getLines: -> - @linesForScreenRows(0, @lineCount() - 1) + @linesForScreenRows(0, @lastRow()) lineCount: -> @lineMap.screenLineCount() + lastRow: -> + @lineCount() - 1 + + logLines: (start=0, end=@lineCount() - 1)-> + @lineMap.logLines(start, end) + _.extend(LineWrapper.prototype, EventEmitter) diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 7ac5d9987..e197c8e07 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -52,5 +52,8 @@ class ScreenLineFragment else @text.length + isSoftWrapped: -> + @screenDelta.row == 1 and @bufferDelta.row == 0 + isEqual: (other) -> _.isEqual(@tokens, other.tokens) and @screenDelta.isEqual(other.screenDelta) and @bufferDelta.isEqual(other.bufferDelta) diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index e2c51c0f1..5db89b167 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -60,35 +60,38 @@ class Selection extends View region.remove() for region in @regions @regions = [] - getRange: -> + getScreenRange: -> if @anchor new Range(@anchor.getScreenPosition(), @cursor.getScreenPosition()) else new Range(@cursor.getScreenPosition(), @cursor.getScreenPosition()) - setRange: (range) -> + setScreenRange: (range) -> @cursor.setScreenPosition(range.start) @modifySelection => @cursor.setScreenPosition(range.end) - getScreenRange: -> - @editor.lineWrapper.screenRangeForBufferRange(@getRange()) + getBufferRange: -> + @editor.bufferRangeForScreenRange(@getScreenRange()) + + setBufferRange: (bufferRange) -> + @setScreenRange(@editor.screenRangeForBufferRange(bufferRange)) getText: -> - @editor.buffer.getTextInRange @getRange() + @editor.buffer.getTextInRange @getBufferRange() insertText: (text) -> - @editor.buffer.change(@getRange(), text) + @editor.buffer.change(@getBufferRange(), text) insertNewline: -> @insertText('\n') delete: -> - range = @getRange() + range = @getBufferRange() @editor.buffer.change(range, '') unless range.isEmpty() isEmpty: -> - @getRange().isEmpty() + @getBufferRange().isEmpty() modifySelection: (fn) -> @placeAnchor() @@ -114,11 +117,11 @@ class Selection extends View endOffset = regex.exec(rightSide)?[0]?.length or 0 range = new Range([row, column + startOffset], [row, column + endOffset]) - @setRange range + @setBufferRange range selectLine: (row) -> rowLength = @editor.buffer.getLine(row).length - @setRange new Range([row, 0], [row, rowLength]) + @setBufferRange new Range([row, 0], [row, rowLength]) selectRight: -> @modifySelection => @@ -140,10 +143,14 @@ class Selection extends View @modifySelection => @cursor.moveLeftUntilMatch(regex) - selectToPosition: (position) -> + selectToScreenPosition: (position) -> @modifySelection => @cursor.setScreenPosition(position) + selectToBufferPosition: (position) -> + @modifySelection => + @cursor.setBufferPosition(position) + moveCursorToLineEnd: -> @cursor.moveToLineEnd() @@ -156,8 +163,8 @@ class Selection extends View copy: -> return if @isEmpty() - text = @editor.buffer.getTextInRange @getRange() + text = @editor.buffer.getTextInRange(@getBufferRange()) atom.native.writeToPasteboard text fold: -> - @editor.lineFolder.createFold(@getRange()) + @editor.lineFolder.createFold(@getBufferRange()) diff --git a/src/atom/vim-mode/motions.coffee b/src/atom/vim-mode/motions.coffee index 70c33ae1b..e2694c0b7 100644 --- a/src/atom/vim-mode/motions.coffee +++ b/src/atom/vim-mode/motions.coffee @@ -13,7 +13,7 @@ class MoveLeft extends Motion select: -> position = @editor.getCursorScreenPosition() position.column-- if position.column > 0 - @editor.selectToPosition position + @editor.selectToBufferPosition(position) class MoveRight extends Motion execute: -> @@ -42,7 +42,7 @@ class MoveToNextWord extends Motion @editor.setCursorScreenPosition(@nextWordPosition()) select: -> - @editor.selectToPosition(@nextWordPosition()) + @editor.selectToBufferPosition(@nextWordPosition()) nextWordPosition: -> regex = getWordRegex()