diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 7b65bb00e..883749aab 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -4,12 +4,14 @@ TextBuffer = require 'text-buffer' {Point, Range} = TextBuffer TextEditor = require '../src/text-editor' TextEditorPresenter = require '../src/text-editor-presenter' +LinesYardstick = require '../src/lines-yardstick' +MockLineNodesProvider = require './mock-line-nodes-provider' describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> - [buffer, editor] = [] + [buffer, editor, linesYardstick, lineNodesProvider] = [] beforeEach -> # These *should* be mocked in the spec helper, but changing that now would break packages :-( @@ -18,9 +20,13 @@ describe "TextEditorPresenter", -> buffer = new TextBuffer(filePath: require.resolve('./fixtures/sample.js')) editor = new TextEditor({buffer}) + lineNodesProvider = new MockLineNodesProvider(editor) + lineNodesProvider.setDefaultFont("16px monospace") + linesYardstick = new LinesYardstick(editor, lineNodesProvider) waitsForPromise -> buffer.load() afterEach -> + lineNodesProvider.dispose() editor.destroy() buffer.destroy() @@ -40,7 +46,9 @@ describe "TextEditorPresenter", -> scrollTop: 0 scrollLeft: 0 - new TextEditorPresenter(params) + presenter = new TextEditorPresenter(params) + presenter.setLinesYardstick(linesYardstick) + presenter expectValues = (actual, expected) -> for key, value of expected @@ -297,7 +305,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("25px monospace") + presenter.setBaseCharacterWidth(15) expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1 it "updates when the scoped character widths change", -> @@ -308,8 +318,13 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) - expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'support.function.js'], "34px monospace") + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'u', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 's', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'h', 20) + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 8)) + (20 * 8) + 1 # 8 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) @@ -536,10 +551,16 @@ describe "TextEditorPresenter", -> presenter = buildPresenter() expect(presenter.getState().hiddenInput.width).toBe 10 - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("25px monospace") + presenter.setBaseCharacterWidth(15) expect(presenter.getState().hiddenInput.width).toBe 15 - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'storage.modifier.js'], "33px monospace") + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'a', 20) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) expect(presenter.getState().hiddenInput.width).toBe 20 it "is 2px at the end of lines", -> @@ -631,7 +652,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("25px monospace") + presenter.setBaseCharacterWidth(15) expect(presenter.getState().content.scrollWidth).toBe 15 * maxLineLength + 1 it "updates when the scoped character widths change", -> @@ -642,8 +665,13 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) - expect(presenter.getState().content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'support.function.js'], "33px monospace") + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'u', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 's', 20) + presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'h', 20) + expect(presenter.getState().content.scrollWidth).toBe (10 * (maxLineLength - 8)) + (20 * 8) + 1 # 8 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) @@ -1221,7 +1249,9 @@ describe "TextEditorPresenter", -> editor.setCursorBufferPosition([2, 4]) presenter = buildPresenter(explicitHeight: 20, scrollTop: 20) - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("33px monospace") + presenter.setBaseCharacterWidth(20) expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10} it "updates when scoped character widths change", -> @@ -1232,11 +1262,19 @@ describe "TextEditorPresenter", -> editor.setCursorBufferPosition([1, 4]) presenter = buildPresenter(explicitHeight: 20) - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20) - expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10} + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'storage.modifier.js'], "33px monospace") + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'a', 20) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) + expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (2 * 10) + (2 * 20), width: 20, height: 10} - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) - expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10} + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'storage.modifier.js'], "36px monospace") + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 22) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'a', 22) + presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 22) + expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (2 * 10) + (2 * 22), width: 22, height: 10} it "updates when cursors are added, moved, hidden, shown, or destroyed", -> editor.setSelectedBufferRanges([ @@ -1561,7 +1599,9 @@ describe "TextEditorPresenter", -> expectValues stateForSelectionInTile(presenter, 0, 2), { regions: [{top: 0, left: 2 * 10, width: 2 * 10, height: 10}] } - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("33px monospace") + presenter.setBaseCharacterWidth(20) expectValues stateForSelectionInTile(presenter, 0, 2), { regions: [{top: 0, left: 2 * 20, width: 2 * 20, height: 10}] } @@ -1580,9 +1620,12 @@ describe "TextEditorPresenter", -> expectValues stateForSelectionInTile(presenter, 0, 2), { regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}] } - expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20) + expectStateUpdate presenter, -> + lineNodesProvider.setFontForScopes(['source.js', 'keyword.control.js'], "25px monospace") + presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20) + presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'f', 20) expectValues stateForSelectionInTile(presenter, 0, 2), { - regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}] + regions: [{top: 0, left: 4 * 10, width: 15 * 2, height: 10}] } it "updates when highlight decorations are added, moved, hidden, shown, or destroyed", -> @@ -1744,7 +1787,9 @@ describe "TextEditorPresenter", -> pixelPosition: {top: 3 * 10 - scrollTop, left: 13 * 10} } - expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(5) + expectStateUpdate presenter, -> + lineNodesProvider.setDefaultFont("9px monospace") + presenter.setBaseCharacterWidth(5) expectValues stateForOverlay(presenter, decoration), { item: item diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 05b8a636b..a82c88825 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -38,6 +38,8 @@ class TextEditorPresenter @startBlinkingCursors() if @focused @updating = false + setLinesYardstick: (@linesYardstick) -> + destroy: -> @disposables.dispose() @@ -1057,42 +1059,12 @@ class TextEditorPresenter hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? - pixelPositionForScreenPosition: (screenPosition, clip=true) -> - screenPosition = Point.fromObject(screenPosition) - screenPosition = @model.clipScreenPosition(screenPosition) if clip - - targetRow = screenPosition.row - targetColumn = screenPosition.column - baseCharacterWidth = @baseCharacterWidth - - top = targetRow * @lineHeight - left = 0 - column = 0 - - iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator() - while iterator.next() - characterWidths = @getScopedCharacterWidths(iterator.getScopes()) - - valueIndex = 0 - text = iterator.getText() - while valueIndex < text.length - if iterator.isPairedCharacter() - char = text - charLength = 2 - valueIndex += 2 - else - char = text[valueIndex] - charLength = 1 - valueIndex++ - - break if column is targetColumn - - left += characterWidths[char] ? baseCharacterWidth unless char is '\0' - column += charLength - - top -= @scrollTop - left -= @scrollLeft - {top, left} + pixelPositionForScreenPosition: (screenPosition, clip) -> + pixelPosition = + @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip) + pixelPosition.top -= @scrollTop + pixelPosition.left -= @scrollLeft + pixelPosition hasPixelRectRequirements: -> @hasPixelPositionRequirements() and @scrollWidth?