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)