Replace skipAtomicTokens with clip

When clipping a screen position, callers used to have to pick between
clipping to the left edge or the right edge when the position was in the
middle of an atomic token. This change allows them to choose the closest
edge, and makes this the default.

This makes selecting hard tabs (or any other atomic tokens) work in a
similar manner as in other text editors; that is, when clicking near
the middle of a tab, the insertion point will move to the closest edge
rather than the left edge.
This commit is contained in:
Nikolaus Wittenstein 2015-01-27 15:14:15 -05:00
parent 372fb49c88
commit 5a3f2035a1
4 changed files with 40 additions and 13 deletions

View File

@ -638,8 +638,11 @@ describe "DisplayBuffer", ->
expect(displayBuffer.outermostFoldsInBufferRowRange(3, 18)).toEqual [fold1, fold3, fold5]
expect(displayBuffer.outermostFoldsInBufferRowRange(5, 16)).toEqual [fold3]
describe "::clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
describe "::clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, clip: 'closest')", ->
beforeEach ->
tabLength = 4
displayBuffer.setTabLength(tabLength)
displayBuffer.setSoftWrapped(true)
displayBuffer.setEditorWidthInChars(50)
@ -698,19 +701,28 @@ describe "DisplayBuffer", ->
expect(displayBuffer.clipScreenPosition([3, 58], wrapAtSoftNewlines: true)).toEqual [4, 4]
expect(displayBuffer.clipScreenPosition([3, 1000], wrapAtSoftNewlines: true)).toEqual [4, 4]
describe "when skipAtomicTokens is false (the default)", ->
it "clips screen positions in the middle of atomic tab characters to the beginning of the character", ->
describe "when clip is 'closest' (the default)", ->
it "clips screen positions in the middle of atomic tab characters to the closest edge of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 2])).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength-1])).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength])).toEqual [0, tabLength]
describe "when skipAtomicTokens is true", ->
describe "when clip is 'backward'", ->
it "clips screen positions in the middle of atomic tab characters to the beginning of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0], clip: 'backward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength-1], clip: 'backward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'backward')).toEqual [0, tabLength]
describe "when clip is 'forward'", ->
it "clips screen positions in the middle of atomic tab characters to the end of the character", ->
buffer.insert([0, 0], '\t')
expect(displayBuffer.clipScreenPosition([0, 0], skipAtomicTokens: true)).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1], skipAtomicTokens: true)).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], skipAtomicTokens: true)).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, 0], clip: 'forward')).toEqual [0, 0]
expect(displayBuffer.clipScreenPosition([0, 1], clip: 'forward')).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'forward')).toEqual [0, tabLength]
describe "::screenPositionForBufferPosition(bufferPosition, options)", ->
it "clips the specified buffer position", ->

View File

@ -305,7 +305,7 @@ class Cursor extends Model
columnCount-- # subtract 1 for the row move
column = column - columnCount
@setScreenPosition({row, column})
@setScreenPosition({row, column}, clip: 'backward')
# Public: Moves the cursor right one screen column.
#
@ -332,7 +332,7 @@ class Cursor extends Model
columnsRemainingInLine = rowLength
column = column + columnCount
@setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
@setScreenPosition({row, column}, clip: 'forward', wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Public: Moves the cursor to the top of the buffer.
moveToTop: ->

View File

@ -393,7 +393,7 @@ class Selection extends Model
if options.select
@setBufferRange(newBufferRange, reversed: wasReversed)
else
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
@cursor.setBufferPosition(newBufferRange.end, clip: 'forward') if wasReversed
if autoIndentFirstLine
@editor.setIndentationForBufferRow(oldBufferRange.start.row, desiredIndentLevel)

View File

@ -41,10 +41,20 @@ class TokenizedLine
copy: ->
new TokenizedLine({@tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold})
# This clips a given screen column to a valid column that's within the line
# and not in the middle of any atomic tokens.
#
# column - A {Number} representing the column to clip
# options - A hash with the key clip. Valid values for this key:
# 'closest' (default): clip to the closest edge of an atomic token.
# 'forward': clip to the forward edge.
# 'backward': clip to the backward edge.
#
# Returns a {Number} representing the clipped column.
clipScreenColumn: (column, options={}) ->
return 0 if @tokens.length == 0
{ skipAtomicTokens } = options
{ clip } = options
column = Math.min(column, @getMaxScreenColumn())
tokenStartColumn = 0
@ -55,10 +65,15 @@ class TokenizedLine
if @isColumnInsideSoftWrapIndentation(tokenStartColumn)
@softWrapIndentationDelta
else if token.isAtomic and tokenStartColumn < column
if skipAtomicTokens
if clip == 'forward'
tokenStartColumn + token.screenDelta
else
else if clip == 'backward'
tokenStartColumn
else #'closest'
if column > tokenStartColumn + (token.screenDelta / 2)
tokenStartColumn + token.screenDelta
else
tokenStartColumn
else
column