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.
This commit is contained in:
Corey Johnson & Nathan Sobo 2012-02-27 16:56:02 -07:00
parent f2f401e5a1
commit 6e46b97a5c
8 changed files with 87 additions and 50 deletions

View File

@ -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})", ->

View File

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

View File

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

View File

@ -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})

View File

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

View File

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

View File

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

View File

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