From 17205cae3f2d81b9de7578c5dbe111c1e47fd497 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 21:05:12 -0700 Subject: [PATCH 01/10] WIP: Working on position translation. Pretty broken right now. The layered relationship between the line wrapper and the folder is still not quite ironed out yet. The editor behaves pretty erratically when text is folded. --- spec/atom/editor-spec.coffee | 61 ++++++++++++++++-------------- spec/atom/line-folder-spec.coffee | 6 ++- spec/atom/line-wrapper-spec.coffee | 18 +++++++++ spec/atom/selection-spec.coffee | 32 ++++++++-------- src/atom/cursor.coffee | 16 +++++--- src/atom/editor.coffee | 52 +++++++++++++++---------- src/atom/line-folder.coffee | 7 ++-- src/atom/line-map.coffee | 19 +++++++++- src/atom/line-wrapper.coffee | 23 +++++++++-- src/atom/selection.coffee | 33 +++++++++------- src/atom/vim-mode/motions.coffee | 4 +- 11 files changed, 176 insertions(+), 95 deletions(-) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 2009ae721..71201f16e 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) @@ -333,7 +333,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 +394,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 +453,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 +465,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 +484,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 +496,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 +518,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 +530,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 +551,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 +625,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 +644,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 +697,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 +711,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 +729,17 @@ 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] diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 7c9d26f90..5cd5457cb 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -312,6 +312,7 @@ 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)", -> beforeEach -> folder.createFold(new Range([4, 29], [7, 4])) @@ -323,11 +324,12 @@ describe "LineFolder", -> 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] + # expect(folder.clipScreenPosition([4, 31])).toEqual [4, 29] + # expect(folder.clipScreenPosition([4, 32])).toEqual [4, 32] diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 49e86284c..e2ba4ee9c 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -235,3 +235,21 @@ describe "LineWrapper", -> expect(line2.endColumn).toBe 14 expect(line2.text.length).toBe 3 + describe ".clipScreenPosition(screenPosition)", -> + it "returns the nearest valid position based on the current screen lines", -> + expect(wrapper.clipScreenPosition([-1, -1])).toEqual [0, 0] + expect(wrapper.clipScreenPosition([0, -1])).toEqual [0, 0] + expect(wrapper.clipScreenPosition([1, 10000])).toEqual [1, 30] + expect(wrapper.clipScreenPosition([3, 51])).toEqual [4, 0] + expect(wrapper.clipScreenPosition([3, 58])).toEqual [4, 0] + expect(wrapper.clipScreenPosition([4, 5])).toEqual [4, 5] + expect(wrapper.clipScreenPosition([4, 11])).toEqual [4, 11] + expect(wrapper.clipScreenPosition([4, 30])).toEqual [4, 11] + expect(wrapper.clipScreenPosition([4, 1000])).toEqual [4, 11] + expect(wrapper.clipScreenPosition([1000, 1000])).toEqual [15, 2] + + it "also clips the screen position with respect to 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] + 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..2316eee67 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -15,9 +15,9 @@ class Cursor extends View bufferChanged: (e) -> @setScreenPosition(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 @@ -111,7 +117,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..13c506951 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 @@ -146,6 +146,9 @@ 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) @@ -202,27 +205,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) -> + @lineWrapper.clipScreenPosition(screenPosition) - 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 +260,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 +273,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..f708611eb 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) @@ -142,6 +140,9 @@ class LineFolder 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..6c3ae24c9 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -99,6 +99,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,9 +138,20 @@ class LineMap end = @screenPositionForBufferPosition(bufferRange.end) new Range(start, end) + bufferRangeForScreenRange: (screenRange) -> + start = @bufferPositionForScreenPosition(screenRange.start) + end = @bufferPositionForScreenPosition(screenRange.end) + new Range(start, end) + clipScreenPosition: (screenPosition) -> screenPosition = Point.fromObject(screenPosition) + + debugger if screenPosition.isEqual [7,4] screenPosition = new Point(Math.max(0, screenPosition.row), Math.max(0, screenPosition.column)) + maxRow = @lastScreenRow() + if screenPosition.row > maxRow + screenPosition.row = maxRow + screenPosition.column = Infinity screenDelta = new Point for screenLine in @screenLines @@ -147,6 +161,9 @@ class LineMap maxColumn = screenDelta.column + screenLine.lengthForClipping() screenDelta.column = Math.min(maxColumn, screenPosition.column) - screenDelta + 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..bfedd5e48 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,23 @@ class LineWrapper @lineFolder.bufferPositionForScreenPosition( @lineMap.bufferPositionForScreenPosition(screenPosition)) + screenRangeForBufferRange: (bufferRange) -> + @lineMap.screenRangeForBufferRange( + @lineFolder.screenRangeForBufferRange(bufferRange)) + + bufferRangeForScreenRange: (screenRange) -> + @lineFolder.bufferRangeForScreenRange( + @lineMap.bufferRangeForScreenRange(screenRange)) + + clipScreenPosition: (screenPosition) -> + # console.log @lineMap.bufferPositionForScreenPosition(@lineMap.clipScreenPosition(screenPosition)).inspect() + @lineFolder.clipScreenPosition(@lineMap.clipScreenPosition(screenPosition)) + # @lineMap.clipScreenPosition(screenPosition) + # @lineMap.screenPositionForBufferPosition( + # @lineFolder.clipScreenPosition( + # @lineMap.bufferPositionForScreenPosition( + # @lineMap.clipScreenPosition(screenPosition)))) + lineForScreenRow: (screenRow) -> @linesForScreenRows(screenRow, screenRow)[0] @@ -100,4 +114,7 @@ class LineWrapper lineCount: -> @lineMap.screenLineCount() + logLines: (start=0, end=@lineCount() - 1)-> + @lineMap.logLines(start, end) + _.extend(LineWrapper.prototype, EventEmitter) 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() From 35f1243d320ff8d0d9feab748346ba92ffed39d5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 21:13:34 -0700 Subject: [PATCH 02/10] Restore LineWrapper.clipScreenPosition version that passes existing tests. It's still wrong, but I need a test to demonstrate how. --- src/atom/line-wrapper.coffee | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index bfedd5e48..19dd2e6c3 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -94,13 +94,10 @@ class LineWrapper @lineMap.bufferRangeForScreenRange(screenRange)) clipScreenPosition: (screenPosition) -> - # console.log @lineMap.bufferPositionForScreenPosition(@lineMap.clipScreenPosition(screenPosition)).inspect() - @lineFolder.clipScreenPosition(@lineMap.clipScreenPosition(screenPosition)) - # @lineMap.clipScreenPosition(screenPosition) - # @lineMap.screenPositionForBufferPosition( - # @lineFolder.clipScreenPosition( - # @lineMap.bufferPositionForScreenPosition( - # @lineMap.clipScreenPosition(screenPosition)))) + @lineMap.screenPositionForBufferPosition( + @lineFolder.clipScreenPosition( + @lineMap.bufferPositionForScreenPosition( + @lineMap.clipScreenPosition(screenPosition)))) lineForScreenRow: (screenRow) -> @linesForScreenRows(screenRow, screenRow)[0] From 0bc510ab58baa1962f4bc2f9304a033add0fc4af Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 21:56:18 -0700 Subject: [PATCH 03/10] Properly translate positions when wrapper and folder are composed The key was in LineMap.linesForScreenRows. For each screen line, it concatenates all line fragments (if there are indeed more than 1) that traverse that line to return a single line fragment representing the line. The key was to update the buffer delta for that fragment to always be 1,0. Because the wrapper is treating the folder as if it's the buffer, the lines it stores in its map need to traverse only a single "buffer" line (that's a single line after folds are taken into account). We may need better language than "screen" and "buffer" because the wrapper treats the folder as the "buffer" but that's confusing because it isn't. --- spec/atom/editor-spec.coffee | 2 ++ spec/atom/line-wrapper-spec.coffee | 2 ++ src/atom/line-folder.coffee | 2 +- src/atom/line-map.coffee | 8 +++----- src/atom/line-wrapper.coffee | 5 ++++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 71201f16e..1a75184bb 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -743,3 +743,5 @@ describe "Editor", -> expect(editor.selection.isEmpty()).toBeTruthy() expect(editor.getCursorScreenPosition()).toEqual [4, 32] + editor.setCursorScreenPosition([9, 2]) + expect(editor.getCursorScreenPosition()).toEqual [9, 2] diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index e2ba4ee9c..ba0756ae2 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...) -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index f708611eb..cc2474865 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -114,7 +114,7 @@ class LineFolder @lineMap.lineForScreenRow(screenRow) getLines: -> - @lineMap.getScreenLines() + @lineMap.screenLinesForRows(0, @lastRow()) lineCount: -> @lineMap.screenLineCount() diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 6c3ae24c9..dd933d2b9 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) -> diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 19dd2e6c3..b0b34bd6b 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -106,11 +106,14 @@ 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) From 8e107359f3fbd4405825ff4678367543f4752f2a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 22:08:34 -0700 Subject: [PATCH 04/10] Changes correctly update cursor position when there are folds --- spec/atom/editor-spec.coffee | 6 ++++++ src/atom/cursor.coffee | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 1a75184bb..e2b71be22 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -745,3 +745,9 @@ describe "Editor", -> 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] + + diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 2316eee67..2674d5299 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -13,7 +13,7 @@ class Cursor extends View @one 'attach', => @updateAppearance() bufferChanged: (e) -> - @setScreenPosition(e.newRange.end) + @setBufferPosition(e.newRange.end) setScreenPosition: (position) -> position = Point.fromObject(position) From 796e24f278afb33dbc5ff4d69a0f450eb36eae4c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Feb 2012 10:31:37 -0700 Subject: [PATCH 05/10] Add Editor.linesForScreenRows and .getScreenLines These just abstract the line wrapper as the source of the screen lines. --- src/atom/editor.coffee | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 13c506951..26ca5cd40 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -132,9 +132,15 @@ 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) + setBuffer: (@buffer) -> @highlighter = new Highlighter(@buffer) @lineFolder = new LineFolder(@highlighter) @@ -151,7 +157,7 @@ class Editor extends View @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] From c2cba8bdcd32871759163971986b06f35c98a416 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Feb 2012 11:22:20 -0700 Subject: [PATCH 06/10] Add failing spec for rightward movement over folds. --- spec/atom/editor-spec.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index e2b71be22..1c3b235d3 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -750,4 +750,9 @@ describe "Editor", -> 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] + From 22305f350ffb82679f9457d2b256a4ef722689dd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Feb 2012 12:45:26 -0700 Subject: [PATCH 07/10] Skip fold placeholders when moving right This relies on a new `eagerWrap` option to clipScreenPosition which will wrap positions inside of atomic line fragments (which is what fold placeholders are) to the end of the fragment rather than the beginning. It also wraps positions beyond the end of a hard line to the next line, which means Cursor.moveRight just has to increment the column, then call clipPosition with eager wrap set to true to get all the correct behavior. --- spec/atom/line-folder-spec.coffee | 18 ++++++++--- spec/atom/line-wrapper-spec.coffee | 52 ++++++++++++++++++++++-------- src/atom/cursor.coffee | 7 +--- src/atom/editor.coffee | 4 +-- src/atom/line-folder.coffee | 4 +-- src/atom/line-map.coffee | 16 ++++++--- src/atom/line-wrapper.coffee | 8 +++-- 7 files changed, 74 insertions(+), 35 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 5cd5457cb..cfc1ac2b3 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -313,7 +313,7 @@ describe "LineFolder", -> 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])) @@ -326,10 +326,18 @@ describe "LineFolder", -> expect(folder.clipScreenPosition([4, 1000])).toEqual [4, 33] 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] - # expect(folder.clipScreenPosition([4, 32])).toEqual [4, 32] + describe "when eagerWrap 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 eagerWrap is true", -> + it "clips positions inside a placeholder to the end of the placeholder", -> + expect(folder.clipScreenPosition([4, 29], true)).toEqual [4, 29] + expect(folder.clipScreenPosition([4, 30], true)).toEqual [4, 32] + expect(folder.clipScreenPosition([4, 31], true)).toEqual [4, 32] + expect(folder.clipScreenPosition([4, 32], true)).toEqual [4, 32] diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index ba0756ae2..0654441eb 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -237,21 +237,45 @@ describe "LineWrapper", -> expect(line2.endColumn).toBe 14 expect(line2.text.length).toBe 3 - describe ".clipScreenPosition(screenPosition)", -> - it "returns the nearest valid position based on the current screen lines", -> - expect(wrapper.clipScreenPosition([-1, -1])).toEqual [0, 0] - expect(wrapper.clipScreenPosition([0, -1])).toEqual [0, 0] - expect(wrapper.clipScreenPosition([1, 10000])).toEqual [1, 30] - expect(wrapper.clipScreenPosition([3, 51])).toEqual [4, 0] - expect(wrapper.clipScreenPosition([3, 58])).toEqual [4, 0] + describe ".clipScreenPosition(screenPosition, eagerWrap=false)", -> + it "allows valid positions", -> expect(wrapper.clipScreenPosition([4, 5])).toEqual [4, 5] expect(wrapper.clipScreenPosition([4, 11])).toEqual [4, 11] - expect(wrapper.clipScreenPosition([4, 30])).toEqual [4, 11] - expect(wrapper.clipScreenPosition([4, 1000])).toEqual [4, 11] + + it "disallows negative positions", -> + expect(wrapper.clipScreenPosition([-1, -1])).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] - it "also clips the screen position with respect to 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] - + it "wraps positions at the end of soft-wrapped lines to the next screen line", -> + expect(wrapper.clipScreenPosition([3, 51])).toEqual [4, 0] + expect(wrapper.clipScreenPosition([3, 58])).toEqual [4, 0] + expect(wrapper.clipScreenPosition([3, 1000])).toEqual [4, 0] + + describe "when eagerWrap is false (the default)", -> + it "wraps positions beyond the end of hard lines 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] + + 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 eagerWrap is true", -> + it "wraps positions past the end of non-softwrapped lines to the next line", -> + expect(wrapper.clipScreenPosition([0, 29], true)).toEqual [0, 29] + expect(wrapper.clipScreenPosition([0, 30], true)).toEqual [1, 0] + expect(wrapper.clipScreenPosition([0, 1000], true)).toEqual [1, 0] + + 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], true)).toEqual [4, 4] + expect(wrapper.clipScreenPosition([4, 5], true)).toEqual [4, 7] + expect(wrapper.clipScreenPosition([4, 6], true)).toEqual [4, 7] + diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 2674d5299..87dfab2cf 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -77,12 +77,7 @@ 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], true)) moveLeft: -> { row, column } = @getScreenPosition() diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 26ca5cd40..d690a4565 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -211,8 +211,8 @@ class Editor extends View else $(window).off 'resize', @_setMaxLineLength - clipScreenPosition: (screenPosition) -> - @lineWrapper.clipScreenPosition(screenPosition) + clipScreenPosition: (screenPosition, eagerWrap=false) -> + @lineWrapper.clipScreenPosition(screenPosition, eagerWrap) pixelPositionForScreenPosition: ({row, column}) -> { top: row * @lineHeight, left: column * @charWidth } diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index cc2474865..1aa102b70 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -134,8 +134,8 @@ class LineFolder bufferPositionForScreenPosition: (screenPosition) -> @lineMap.bufferPositionForScreenPosition(screenPosition) - clipScreenPosition: (screenPosition) -> - @lineMap.clipScreenPosition(screenPosition) + clipScreenPosition: (screenPosition, eagerWrap=false) -> + @lineMap.clipScreenPosition(screenPosition, eagerWrap) screenRangeForBufferRange: (bufferRange) -> @lineMap.screenRangeForBufferRange(bufferRange) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index dd933d2b9..f2fb812fe 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -141,10 +141,9 @@ class LineMap end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) - clipScreenPosition: (screenPosition) -> + clipScreenPosition: (screenPosition, eagerWrap) -> screenPosition = Point.fromObject(screenPosition) - debugger if screenPosition.isEqual [7,4] screenPosition = new Point(Math.max(0, screenPosition.row), Math.max(0, screenPosition.column)) maxRow = @lastScreenRow() if screenPosition.row > maxRow @@ -157,8 +156,17 @@ class LineMap break if nextDelta.isGreaterThan(screenPosition) screenDelta = nextDelta - maxColumn = screenDelta.column + screenLine.lengthForClipping() - screenDelta.column = Math.min(maxColumn, screenPosition.column) + if screenLine.isAtomic + if eagerWrap and screenPosition.column > screenDelta.column + screenDelta.column = screenDelta.column + screenLine.text.length + else + maxColumn = screenDelta.column + screenLine.text.length + if eagerWrap and screenPosition.column > maxColumn + screenDelta.row++ + screenDelta.column = 0 + else + screenDelta.column = Math.min(maxColumn, screenPosition.column) + screenDelta logLines: (start=0, end=@screenLineCount() - 1)-> diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index b0b34bd6b..b76b846ef 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -93,11 +93,15 @@ class LineWrapper @lineFolder.bufferRangeForScreenRange( @lineMap.bufferRangeForScreenRange(screenRange)) - clipScreenPosition: (screenPosition) -> + clipScreenPosition: (screenPosition, eagerWrap=false) -> @lineMap.screenPositionForBufferPosition( @lineFolder.clipScreenPosition( @lineMap.bufferPositionForScreenPosition( - @lineMap.clipScreenPosition(screenPosition)))) + @lineMap.clipScreenPosition(screenPosition, eagerWrap) + ), + eagerWrap + ) + ) lineForScreenRow: (screenRow) -> @linesForScreenRows(screenRow, screenRow)[0] From f24a045e11707af85797a27d55eee14ebc6b5836 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Feb 2012 13:07:59 -0700 Subject: [PATCH 08/10] Cursor can move to last row when lines are wrapped --- spec/atom/editor-spec.coffee | 7 +++++++ src/atom/cursor.coffee | 6 +----- src/atom/editor.coffee | 6 ++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 1c3b235d3..ca3fd36c7 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -98,6 +98,13 @@ 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] + + describe "cursor movement", -> describe ".setCursorScreenPosition({row, column})", -> beforeEach -> diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 87dfab2cf..f52460f41 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -60,11 +60,7 @@ class Cursor extends View 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: -> diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index d690a4565..b822d41af 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -141,6 +141,12 @@ class Editor extends View linesForScreenRows: (start, end) -> @lineWrapper.linesForScreenRows(start, end) + screenLineCount: -> + @lineWrapper.lineCount() + + lastScreenRow: -> + @screenLineCount() - 1 + setBuffer: (@buffer) -> @highlighter = new Highlighter(@buffer) @lineFolder = new LineFolder(@highlighter) From f2f401e5a1d2ad07edf5f93bd36d0c7d64c29947 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 27 Feb 2012 13:16:21 -0700 Subject: [PATCH 09/10] Rely on clipScreenPosition in vertical movement methods Before, we were manually clipping the position of the cursor in vertical movement methods. Now we can just increment / decrement the row and the position will be clipped when it is assigned. Also, changed the definition of clip screen position to always 0 out the column when the row is negative. --- spec/atom/line-folder-spec.coffee | 1 + spec/atom/line-wrapper-spec.coffee | 1 + src/atom/cursor.coffee | 6 +----- src/atom/line-map.coffee | 7 ++++++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index cfc1ac2b3..2c573c730 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -320,6 +320,7 @@ describe "LineFolder", -> 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] diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 0654441eb..4f4bc6157 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -244,6 +244,7 @@ describe "LineWrapper", -> 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", -> diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index f52460f41..45dd7759d 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -50,11 +50,7 @@ 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: -> diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index f2fb812fe..320ea908c 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -144,7 +144,12 @@ class LineMap clipScreenPosition: (screenPosition, eagerWrap) -> 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 + maxRow = @lastScreenRow() if screenPosition.row > maxRow screenPosition.row = maxRow From 6e46b97a5cb441793f565c2ad7ffb8dcbff2986a Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Mon, 27 Feb 2012 16:56:02 -0700 Subject: [PATCH 10/10] Cursor moves correctly between wrapped lines Added explicit options for controlling line wrapping, and skipping of atomic tokens to the LineWrap.clipScreenPosition. These are used when moving right to wrap to the next line. --- spec/atom/editor-spec.coffee | 18 +++++++++++ spec/atom/line-folder-spec.coffee | 12 ++++---- spec/atom/line-wrapper-spec.coffee | 46 +++++++++++++++++----------- src/atom/cursor.coffee | 7 +++-- src/atom/line-folder.coffee | 4 +-- src/atom/line-map.coffee | 39 +++++++++++++---------- src/atom/line-wrapper.coffee | 8 ++--- src/atom/screen-line-fragment.coffee | 3 ++ 8 files changed, 87 insertions(+), 50 deletions(-) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index ca3fd36c7..fc1b46043 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -104,6 +104,24 @@ describe "Editor", -> 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})", -> diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 2c573c730..af0d453f3 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -327,18 +327,18 @@ describe "LineFolder", -> expect(folder.clipScreenPosition([4, 1000])).toEqual [4, 33] expect(folder.clipScreenPosition([1000, 1000])).toEqual [9, 2] - describe "when eagerWrap is false (the default)", -> + 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 eagerWrap is true", -> + describe "when skipAtomicTokens is true", -> it "clips positions inside a placeholder to the end of the placeholder", -> - expect(folder.clipScreenPosition([4, 29], true)).toEqual [4, 29] - expect(folder.clipScreenPosition([4, 30], true)).toEqual [4, 32] - expect(folder.clipScreenPosition([4, 31], true)).toEqual [4, 32] - expect(folder.clipScreenPosition([4, 32], true)).toEqual [4, 32] + 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 4f4bc6157..8669b3048 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -237,7 +237,7 @@ describe "LineWrapper", -> expect(line2.endColumn).toBe 14 expect(line2.text.length).toBe 3 - describe ".clipScreenPosition(screenPosition, eagerWrap=false)", -> + 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] @@ -251,32 +251,42 @@ describe "LineWrapper", -> expect(wrapper.clipScreenPosition([1000, 0])).toEqual [15, 2] expect(wrapper.clipScreenPosition([1000, 1000])).toEqual [15, 2] - it "wraps positions at the end of soft-wrapped lines to the next screen line", -> - expect(wrapper.clipScreenPosition([3, 51])).toEqual [4, 0] - expect(wrapper.clipScreenPosition([3, 58])).toEqual [4, 0] - expect(wrapper.clipScreenPosition([3, 1000])).toEqual [4, 0] - - describe "when eagerWrap is false (the default)", -> - it "wraps positions beyond the end of hard lines to the end of the line", -> + 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 eagerWrap is true", -> - it "wraps positions past the end of non-softwrapped lines to the next line", -> - expect(wrapper.clipScreenPosition([0, 29], true)).toEqual [0, 29] - expect(wrapper.clipScreenPosition([0, 30], true)).toEqual [1, 0] - expect(wrapper.clipScreenPosition([0, 1000], true)).toEqual [1, 0] - + 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], true)).toEqual [4, 4] - expect(wrapper.clipScreenPosition([4, 5], true)).toEqual [4, 7] - expect(wrapper.clipScreenPosition([4, 6], true)).toEqual [4, 7] - + 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/src/atom/cursor.coffee b/src/atom/cursor.coffee index 45dd7759d..b3f1ab9b8 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -69,15 +69,16 @@ class Cursor extends View moveRight: -> { row, column } = @getScreenPosition() - @setScreenPosition(@editor.clipScreenPosition([row, column + 1], true)) + @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}) diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 1aa102b70..e2c42d14e 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -134,8 +134,8 @@ class LineFolder bufferPositionForScreenPosition: (screenPosition) -> @lineMap.bufferPositionForScreenPosition(screenPosition) - clipScreenPosition: (screenPosition, eagerWrap=false) -> - @lineMap.clipScreenPosition(screenPosition, eagerWrap) + clipScreenPosition: (screenPosition, options={}) -> + @lineMap.clipScreenPosition(screenPosition, options) screenRangeForBufferRange: (bufferRange) -> @lineMap.screenRangeForBufferRange(bufferRange) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 320ea908c..3cd9f0298 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -141,7 +141,10 @@ class LineMap end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) - clipScreenPosition: (screenPosition, eagerWrap) -> + clipScreenPosition: (screenPosition, options) -> + wrapBeyondNewlines = options.wrapBeyondNewlines ? false + wrapAtSoftNewlines = options.wrapAtSoftNewlines ? false + skipAtomicTokens = options.skipAtomicTokens ? false screenPosition = Point.fromObject(screenPosition) screenPosition.column = Math.max(0, screenPosition.column) @@ -150,29 +153,33 @@ class LineMap screenPosition.row = 0 screenPosition.column = 0 - maxRow = @lastScreenRow() - if screenPosition.row > maxRow - screenPosition.row = maxRow + 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 - if screenLine.isAtomic - if eagerWrap and screenPosition.column > screenDelta.column - screenDelta.column = screenDelta.column + screenLine.text.length - else - maxColumn = screenDelta.column + screenLine.text.length - if eagerWrap and screenPosition.column > maxColumn - screenDelta.row++ - screenDelta.column = 0 + if lineFragment.isAtomic + if skipAtomicTokens and screenPosition.column > screenDelta.column + return new Point(screenDelta.row, screenDelta.column + lineFragment.text.length) else - screenDelta.column = Math.min(maxColumn, screenPosition.column) + 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] diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index b76b846ef..f945e30b6 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -93,13 +93,11 @@ class LineWrapper @lineFolder.bufferRangeForScreenRange( @lineMap.bufferRangeForScreenRange(screenRange)) - clipScreenPosition: (screenPosition, eagerWrap=false) -> + clipScreenPosition: (screenPosition, options={}) -> @lineMap.screenPositionForBufferPosition( @lineFolder.clipScreenPosition( - @lineMap.bufferPositionForScreenPosition( - @lineMap.clipScreenPosition(screenPosition, eagerWrap) - ), - eagerWrap + @lineMap.bufferPositionForScreenPosition(@lineMap.clipScreenPosition(screenPosition, options)), + options ) ) 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)