diff --git a/spec/app/editor-spec.coffee b/spec/app/editor-spec.coffee index 1b448bec0..c6790acfb 100644 --- a/spec/app/editor-spec.coffee +++ b/spec/app/editor-spec.coffee @@ -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" diff --git a/spec/app/screen-line-fragment-spec.coffee b/spec/app/screen-line-fragment-spec.coffee index ba52797a8..02c1d9726 100644 --- a/spec/app/screen-line-fragment-spec.coffee +++ b/spec/app/screen-line-fragment-spec.coffee @@ -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 diff --git a/src/app/line-map.coffee b/src/app/line-map.coffee index b3fe7be68..c0ab86514 100644 --- a/src/app/line-map.coffee +++ b/src/app/line-map.coffee @@ -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 diff --git a/src/app/screen-line-fragment.coffee b/src/app/screen-line-fragment.coffee index 586b7a0e6..e16391d80 100644 --- a/src/app/screen-line-fragment.coffee +++ b/src/app/screen-line-fragment.coffee @@ -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 diff --git a/src/app/token.coffee b/src/app/token.coffee index e989ffb0a..609974644 100644 --- a/src/app/token.coffee +++ b/src/app/token.coffee @@ -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)