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] 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)