Correctly translate buffer positions to screen positions when the buffer has tab chars

This commit is contained in:
Nathan Sobo
2012-04-06 15:06:33 -06:00
parent 9da86982a8
commit 47685aab7b
5 changed files with 60 additions and 16 deletions

View File

@@ -1661,6 +1661,11 @@ describe "Editor", ->
expect(editor.getCursorBufferPosition()).toEqual [0, 1]
expect(editor.getCursorScreenPosition()).toEqual [0, editor.tabText.length]
editor.trigger 'tab'
expect(buffer.lineForRow(0)).toMatch(/^\t\t/)
expect(editor.getCursorBufferPosition()).toEqual [0, 2]
expect(editor.getCursorScreenPosition()).toEqual [0, editor.tabText.length * 2]
describe "undo/redo", ->
it "undoes/redoes the last change", ->
buffer.insert [0, 0], "foo"

View File

@@ -3,11 +3,12 @@ Buffer = require 'buffer'
Highlighter = require 'highlighter'
describe "screenLineFragment", ->
[screenLine, highlighter] = []
[buffer, tabText, screenLine, highlighter] = []
beforeEach ->
tabText = ' '
buffer = new Buffer(require.resolve 'fixtures/sample.js')
highlighter = new Highlighter(buffer)
highlighter = new Highlighter(buffer, tabText)
screenLine = highlighter.lineForScreenRow(3)
describe ".splitAt(column)", ->
@@ -75,7 +76,36 @@ describe "screenLineFragment", ->
expect(concatenated.screenDelta).toEqual [2, 0]
expect(concatenated.bufferDelta).toEqual [2, 0]
describe ".translateColumn(sourceDeltaType, targetDeltaType, sourceColumn, skipAtomicTokens: false)", ->
beforeEach ->
buffer.insert([0, 13], '\t')
buffer.insert([0, 0], '\t\t')
screenLine = highlighter.lineForScreenRow(0)
describe "when translating from buffer to screen coordinates", ->
it "accounts for tab characters being wider on screen", ->
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 0)).toBe 0
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 1)).toBe 2
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 2)).toBe 4
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 3)).toBe 5
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 15)).toBe 17
expect(screenLine.translateColumn('bufferDelta', 'screenDelta', 16)).toBe 19
describe "when translating from screen coordinates to buffer coordinates", ->
describe "when skipAtomicTokens is false (the default)", ->
it "clips positions in the middle of tab tokens to the beginning", ->
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 0)).toBe 0
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 1)).toBe 0
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 2)).toBe 1
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 3)).toBe 1
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 4)).toBe 2
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 5)).toBe 3
describe "when skipAtomicTokens is true", ->
it "clips positions in the middle of tab tokens to the end", ->
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 0, skipAtomicTokens: true)).toBe 0
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 1, skipAtomicTokens: true)).toBe 1
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 2, skipAtomicTokens: true)).toBe 1
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 3, skipAtomicTokens: true)).toBe 2
expect(screenLine.translateColumn('screenDelta', 'bufferDelta', 5, skipAtomicTokens: true)).toBe 3

View File

@@ -136,7 +136,8 @@ class LineMap
targetDelta.column = 0
else
additionalColumns = sourcePosition.column - sourceDelta.column
targetDelta.column += lastLineFragment.clipColumn(additionalColumns, { skipAtomicTokens })
additionalColumns = lastLineFragment.translateColumn(sourceDeltaType, targetDeltaType, additionalColumns, { skipAtomicTokens })
targetDelta.column += additionalColumns
targetDelta

View File

@@ -44,24 +44,30 @@ class ScreenLineFragment
bufferDelta = @bufferDelta.add(other.bufferDelta)
new ScreenLineFragment(tokens, text, screenDelta, bufferDelta, {state: other.state})
clipColumn: (column, { skipAtomicTokens }) ->
translateColumn: (sourceDeltaType, targetDeltaType, sourceColumn, options={}) ->
{ skipAtomicTokens } = options
textLength = @text.length
column = Math.min(column, textLength)
sourceColumn = Math.min(sourceColumn, textLength)
currentColumn = 0
currentSourceColumn = 0
currentTargetColumn = 0
for token in @tokens
tokenStartColumn = currentColumn
tokenEndColumn = tokenStartColumn + token.value.length
break if tokenEndColumn > column
currentColumn = tokenEndColumn
tokenStartTargetColumn = currentTargetColumn
tokenStartSourceColumn = currentSourceColumn
tokenEndSourceColumn = currentSourceColumn + token[sourceDeltaType]
tokenEndTargetColumn = currentTargetColumn + token[targetDeltaType]
break if tokenEndSourceColumn > sourceColumn
currentSourceColumn = tokenEndSourceColumn
currentTargetColumn = tokenEndTargetColumn
if token?.isAtomic
if skipAtomicTokens and column > tokenStartColumn
tokenEndColumn
if skipAtomicTokens and sourceColumn > tokenStartSourceColumn
tokenEndTargetColumn
else
tokenStartColumn
tokenStartTargetColumn
else
column
remainingColumns = sourceColumn - currentSourceColumn
currentTargetColumn + remainingColumns
isSoftWrapped: ->
@screenDelta.row == 1 and @bufferDelta.row == 0

View File

@@ -4,7 +4,9 @@ class Token
type: null
isAtomic: null
constructor: ({@value, @type, @isAtomic}) ->
constructor: ({@value, @type, @isAtomic, @bufferDelta}) ->
@screenDelta = @value.length
@bufferDelta ?= @screenDelta
isEqual: (other) ->
@value == other.value and @type == other.type and !!@isAtomic == !!other.isAtomic
@@ -22,4 +24,4 @@ class Token
new Token(value: substring, type: @type)
buildTabToken: (tabText) ->
new Token(value: tabText, type: @type, isAtomic: true)
new Token(value: tabText, type: @type, bufferDelta: 1, isAtomic: true)