From f30e4ccc9de3a437d030ea74388bb04c0f6b69b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 16:01:55 +0100 Subject: [PATCH] Use the new LineTopIndex in TextEditorPresenter --- spec/fake-lines-yardstick.coffee | 41 +++--------------------- spec/text-editor-component-spec.js | 12 +++---- spec/text-editor-presenter-spec.coffee | 5 ++- src/block-decorations-presenter.js | 4 +-- src/linear-line-top-index.js | 15 +++++++-- src/lines-yardstick.coffee | 43 +++----------------------- src/text-editor-component.coffee | 6 ++-- src/text-editor-presenter.coffee | 41 ++++++++++-------------- 8 files changed, 54 insertions(+), 113 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index e6d1f4f53..3d2ebe7d5 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -2,7 +2,7 @@ module.exports = class FakeLinesYardstick - constructor: (@model, @presenter) -> + constructor: (@model, @presenter, @lineTopIndex) -> @characterWidthsByScope = {} prepareScreenRowsForMeasurement: -> @@ -31,7 +31,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = @bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.bottomPixelPositionForRow(targetRow) left = 0 column = 0 @@ -60,48 +60,15 @@ class FakeLinesYardstick {top, left} - rowForTopPixelPosition: (position, floor = true) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - nextTop = top + @presenter.getScreenRowHeight(row) - if floor - return row if nextTop > position - else - return row if top >= position - top = nextTop - @model.getScreenLineCount() - - topPixelPositionForRow: (targetRow) -> - top = 0 - for row in [0..targetRow] - return top if targetRow is row - top += @presenter.getScreenRowHeight(row) - top - - bottomPixelPositionForRow: (targetRow) -> - @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() - - topPixelPositionForRows: (startRow, endRow, step) -> - results = {} - top = 0 - for tileStartRow in [0..endRow] by step - tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) - results[tileStartRow] = top - for row in [tileStartRow...tileEndRow] by 1 - top += @presenter.getScreenRowHeight(row) - results - pixelRectForScreenRange: (screenRange) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start).top left = 0 - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false).left - left {top, left, width, height} diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 18ad030c0..85d404b1e 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1707,13 +1707,13 @@ describe('TextEditorComponent', function () { await nextViewUpdatePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px") expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)") expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 40 + "px") expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`) - expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + "px") + expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px") expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`) expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull() @@ -1724,7 +1724,7 @@ describe('TextEditorComponent', function () { expect(item1.getBoundingClientRect().height).toBe(0) // hidden expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) - expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 40 + 100) + expect(item4.getBoundingClientRect().height).toBe(0) // hidden await nextViewUpdatePromise() @@ -1734,13 +1734,13 @@ describe('TextEditorComponent', function () { await nextViewUpdatePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px") expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)") expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 60 + "px") expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`) - expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + "px") + expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px") expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`) expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull() @@ -1751,7 +1751,7 @@ describe('TextEditorComponent', function () { expect(item1.getBoundingClientRect().height).toBe(0) // hidden expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) - expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 60 + 100) + expect(item4.getBoundingClientRect().height).toBe(0) // hidden }) }) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 83f0d8236..0e80ed113 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -5,6 +5,7 @@ TextBuffer = require 'text-buffer' TextEditor = require '../src/text-editor' TextEditorPresenter = require '../src/text-editor-presenter' FakeLinesYardstick = require './fake-lines-yardstick' +LineTopIndex = require '../src/linear-line-top-index' describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. @@ -26,12 +27,14 @@ describe "TextEditorPresenter", -> buffer.destroy() buildPresenterWithoutMeasurements = (params={}) -> + lineTopIndex = new LineTopIndex _.defaults params, model: editor config: atom.config contentFrameWidth: 500 + lineTopIndex: lineTopIndex presenter = new TextEditorPresenter(params) - presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter)) + presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter, lineTopIndex)) presenter buildPresenter = (params={}) -> diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 4d787c25f..80f4de6f4 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -5,12 +5,12 @@ const LineTopIndex = require('./linear-line-top-index') module.exports = class BlockDecorationsPresenter { - constructor (model) { + constructor (model, lineTopIndex) { this.model = model this.disposables = new CompositeDisposable() this.emitter = new Emitter() this.firstUpdate = true - this.lineTopIndex = new LineTopIndex + this.lineTopIndex = lineTopIndex this.blocksByDecoration = new Map this.decorationsByBlock = new Map this.observedDecorations = new Set diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 1f4a62fb9..7b6103f55 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -76,7 +76,7 @@ class LineTopIndex { return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight } - rowForTopPixelPosition (top) { + rowForTopPixelPosition (top, roundingStrategy='round') { let blocksHeight = 0 let lastRow = 0 let lastTop = 0 @@ -97,7 +97,16 @@ class LineTopIndex { } let remainingHeight = Math.max(0, top - lastTop) - let remainingRows = Math.round(remainingHeight / this.defaultLineHeight) - return Math.min(this.maxRow, lastRow + remainingRows) + let remainingRows = Math.min(this.maxRow, lastRow + remainingHeight / this.defaultLineHeight) + switch (roundingStrategy) { + case "round": + return Math.round(remainingRows) + case "floor": + return Math.floor(remainingRows) + case "ceil": + return Math.ceil(remainingRows) + default: + throw new Error(`Cannot use '${roundingStrategy}' as a rounding strategy!`) + } } } diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index bb2c7faba..c1faa6399 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -3,7 +3,7 @@ TokenIterator = require './token-iterator' module.exports = class LinesYardstick - constructor: (@model, @presenter, @lineNodesProvider, grammarRegistry) -> + constructor: (@model, @presenter, @lineNodesProvider, @lineTopIndex, grammarRegistry) -> @tokenIterator = new TokenIterator({grammarRegistry}) @rangeForMeasurement = document.createRange() @invalidateCache() @@ -22,7 +22,7 @@ class LinesYardstick targetTop = pixelPosition.top targetLeft = pixelPosition.left defaultCharWidth = @model.getDefaultCharWidth() - row = @rowForTopPixelPosition(targetTop) + row = @lineTopIndex.rowForTopPixelPosition(targetTop, 'floor') targetLeft = 0 if targetTop < 0 targetLeft = Infinity if row > @model.getLastScreenRow() @@ -90,7 +90,7 @@ class LinesYardstick @prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly - top = @bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.bottomPixelPositionForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) @clearScreenRowsForMeasurement() unless measureVisibleLinesOnly @@ -172,48 +172,15 @@ class LinesYardstick left + width - offset - rowForTopPixelPosition: (position, floor = true) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - nextTop = top + @presenter.getScreenRowHeight(row) - if floor - return row if nextTop > position - else - return row if top >= position - top = nextTop - @model.getScreenLineCount() - - topPixelPositionForRow: (targetRow) -> - top = 0 - for row in [0..targetRow] - return top if targetRow is row - top += @presenter.getScreenRowHeight(row) - top - - bottomPixelPositionForRow: (targetRow) -> - @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() - - topPixelPositionForRows: (startRow, endRow, step) -> - results = {} - top = 0 - for tileStartRow in [0..endRow] by step - tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) - results[tileStartRow] = top - for row in [tileStartRow...tileEndRow] by 1 - top += @presenter.getScreenRowHeight(row) - results - pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start, true, measureVisibleLinesOnly).top left = 0 - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly) - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false, measureVisibleLinesOnly).left - left {top, left, width, height} diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index faf6a6715..7fc51b9a4 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -14,6 +14,7 @@ OverlayManager = require './overlay-manager' DOMElementPool = require './dom-element-pool' LinesYardstick = require './lines-yardstick' BlockDecorationsComponent = require './block-decorations-component' +LineTopIndex = require './linear-line-top-index' module.exports = class TextEditorComponent @@ -49,6 +50,7 @@ class TextEditorComponent @observeConfig() @setScrollSensitivity(@config.get('editor.scrollSensitivity')) + lineTopIndex = new LineTopIndex(@editor) @presenter = new TextEditorPresenter model: @editor tileSize: tileSize @@ -56,11 +58,11 @@ class TextEditorComponent cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 config: @config + lineTopIndex: lineTopIndex @presenter.onDidUpdateState(@requestUpdate) @domElementPool = new DOMElementPool - @domNode = document.createElement('div') if @useShadowDOM @domNode.classList.add('editor-contents--private') @@ -85,7 +87,7 @@ class TextEditorComponent @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) - @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, @grammars) + @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, lineTopIndex, @grammars) @presenter.setLinesYardstick(@linesYardstick) @horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll}) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 41209d128..29428c1d4 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -14,7 +14,7 @@ class TextEditorPresenter minimumReflowInterval: 200 constructor: (params) -> - {@model, @config} = params + {@model, @config, @lineTopIndex} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params {@contentFrameWidth} = params @@ -29,7 +29,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsPresenter = new BlockDecorationsPresenter(@model) + @blockDecorationsPresenter = new BlockDecorationsPresenter(@model, @lineTopIndex) @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -434,12 +434,6 @@ class TextEditorPresenter screenRowIndex = screenRows.length - 1 zIndex = 0 - tilesPositions = @linesYardstick.topPixelPositionForRows( - @tileForRow(startRow), - @tileForRow(endRow) + @tileSize, - @tileSize - ) - for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize tileEndRow = @constrainRow(tileStartRow + @tileSize) rowsWithinTile = [] @@ -452,8 +446,9 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = Math.round(tilesPositions[tileStartRow]) - height = Math.round(tilesPositions[tileEndRow] - top) + top = Math.round(@lineTopIndex.topPixelPositionForRow(tileStartRow)) + bottom = Math.round(@lineTopIndex.topPixelPositionForRow(tileEndRow)) + height = bottom - top tile = @state.content.tiles[tileStartRow] ?= {} tile.top = top - @scrollTop @@ -667,8 +662,8 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] - top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) - bottom = @linesYardstick.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) @customGutterDecorations[gutterName][decorationId] = top: top height: bottom - top @@ -735,12 +730,12 @@ class TextEditorPresenter updateStartRow: -> return unless @scrollTop? and @lineHeight? - @startRow = Math.max(0, @linesYardstick.rowForTopPixelPosition(@scrollTop)) + @startRow = Math.max(0, @lineTopIndex.rowForTopPixelPosition(@scrollTop, "floor")) updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? - @endRow = @linesYardstick.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, false) + @endRow = @lineTopIndex.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, 'ceil') updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) @@ -775,9 +770,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round( - @linesYardstick.topPixelPositionForRow(@model.getScreenLineCount()) - ) + @contentHeight = Math.round(@lineTopIndex.topPixelPositionForRow(Infinity)) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1138,9 +1131,9 @@ class TextEditorPresenter setLineHeight: (lineHeight) -> unless @lineHeight is lineHeight @lineHeight = lineHeight + @model.setLineHeightInPixels(@lineHeight) + @lineTopIndex.setDefaultLineHeight(@lineHeight) @restoreScrollTopIfNeeded() - @model.setLineHeightInPixels(lineHeight) - @blockDecorationsPresenter.setLineHeight(lineHeight) @shouldUpdateHeightState = true @shouldUpdateHorizontalScrollState = true @shouldUpdateVerticalScrollState = true @@ -1348,7 +1341,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - @linesYardstick.topPixelPositionForRow(tileStartRow) + region.top += @scrollTop - @lineTopIndex.topPixelPositionForRow(tileStartRow) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1500,7 +1493,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(@linesYardstick.topPixelPositionForRow(screenRow)) + @updateScrollTop(@lineTopIndex.topPixelPositionForRow(screenRow)) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1521,8 +1514,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) - bottom = @linesYardstick.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) if options?.center desiredScrollCenter = (top + bottom) / 2 @@ -1594,7 +1587,7 @@ class TextEditorPresenter restoreScrollTopIfNeeded: -> unless @scrollTop? - @updateScrollTop(@linesYardstick.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) + @updateScrollTop(@lineTopIndex.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) restoreScrollLeftIfNeeded: -> unless @scrollLeft?