diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 9ff0ca14c..78ab6a8e2 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -186,6 +186,15 @@ describe('TextEditorComponent', () => { expect(component.refs.lineTiles.children.length).toBe(3) }) + it('recycles tiles on resize', async () => { + const {component, element, editor} = buildComponent({rowsPerTile: 2, autoHeight: false}) + await setEditorHeightInLines(component, 7) + await setScrollTop(component, 3.5 * component.getLineHeight()) + const lineNode = lineNodeForScreenRow(component, 7) + await setEditorHeightInLines(component, 4) + expect(lineNodeForScreenRow(component, 7)).toBe(lineNode) + }) + it('renders dummy vertical and horizontal scrollbars when content overflows', async () => { const {component, element, editor} = buildComponent({height: 100, width: 100}) const verticalScrollbar = component.refs.verticalScrollbar.element @@ -3425,7 +3434,7 @@ function clientPositionForCharacter (component, row, column) { function lineNumberNodeForScreenRow (component, row) { const gutterElement = component.refs.gutterContainer.refs.lineNumberGutter.element const tileStartRow = component.tileStartRowForRow(row) - const tileIndex = component.tileIndexForTileStartRow(tileStartRow) + const tileIndex = component.renderedTileStartRows.indexOf(tileStartRow) return gutterElement.children[tileIndex + 1].children[row - tileStartRow] } diff --git a/src/text-editor-component.js b/src/text-editor-component.js index b661f2bbd..da62af98c 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -127,6 +127,9 @@ class TextEditorComponent { this.remeasureGutterDimensions = false this.guttersToRender = [this.props.model.getLineNumberGutter()] this.guttersVisibility = [this.guttersToRender[0].visible] + this.idsByTileStartRow = new Map() + this.nextTileId = 0 + this.renderedTileStartRows = [] this.lineNumbersToRender = { maxDigits: 2, bufferRows: [], @@ -328,6 +331,7 @@ class TextEditorComponent { this.requestHorizontalMeasurement(screenRange.end.row, screenRange.end.column) } this.populateVisibleRowRange() + this.populateVisibleTiles() this.queryScreenLinesToRender() this.queryLineNumbersToRender() this.queryGuttersToRender() @@ -551,15 +555,15 @@ class TextEditorComponent { const tileWidth = this.getScrollWidth() const displayLayer = this.props.model.displayLayer - const tileNodes = new Array(this.getRenderedTileCount()) + const tileNodes = new Array(this.renderedTileStartRows.length) - for (let tileStartRow = startRow; tileStartRow < endRow; tileStartRow = tileStartRow + rowsPerTile) { + for (let i = 0; i < this.renderedTileStartRows.length; i++) { + const tileStartRow = this.renderedTileStartRows[i] const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile) const tileHeight = this.pixelPositionBeforeBlocksForRow(tileEndRow) - this.pixelPositionBeforeBlocksForRow(tileStartRow) - const tileIndex = this.tileIndexForTileStartRow(tileStartRow) - tileNodes[tileIndex] = $(LinesTileComponent, { - key: tileIndex, + tileNodes[i] = $(LinesTileComponent, { + key: this.idsByTileStartRow.get(tileStartRow), measuredContent: this.measuredContent, height: tileHeight, width: tileWidth, @@ -2495,10 +2499,6 @@ class TextEditorComponent { return row - (row % this.getRowsPerTile()) } - tileIndexForTileStartRow (startRow) { - return (startRow / this.getRowsPerTile()) % this.getRenderedTileCount() - } - getRenderedStartRow () { if (this.derivedDimensionsCache.renderedStartRow == null) { this.derivedDimensionsCache.renderedStartRow = this.tileStartRowForRow(this.getFirstVisibleRow()) @@ -2676,6 +2676,35 @@ class TextEditorComponent { this.props.model.displayLayer.populateSpatialIndexIfNeeded(Infinity, lastPossibleRenderedRow) } + populateVisibleTiles () { + const startRow = this.getRenderedStartRow() + const endRow = this.getRenderedEndRow() + const freeTileIds = [] + for (let i = 0; i < this.renderedTileStartRows.length; i++) { + const tileStartRow = this.renderedTileStartRows[i] + if (tileStartRow < startRow || tileStartRow >= endRow) { + const tileId = this.idsByTileStartRow.get(tileStartRow) + freeTileIds.push(tileId) + this.idsByTileStartRow.delete(tileStartRow) + } + } + + const rowsPerTile = this.getRowsPerTile() + this.renderedTileStartRows.length = this.getRenderedTileCount() + for (let tileStartRow = startRow, i = 0; tileStartRow < endRow; tileStartRow = tileStartRow + rowsPerTile, i++) { + this.renderedTileStartRows[i] = tileStartRow + if (!this.idsByTileStartRow.has(tileStartRow)) { + if (freeTileIds.length > 0) { + this.idsByTileStartRow.set(tileStartRow, freeTileIds.shift()) + } else { + this.idsByTileStartRow.set(tileStartRow, this.nextTileId++) + } + } + } + + this.renderedTileStartRows.sort((a, b) => this.idsByTileStartRow.get(a) - this.idsByTileStartRow.get(b)) + } + getNextUpdatePromise () { if (!this.nextUpdatePromise) { this.nextUpdatePromise = new Promise((resolve) => { @@ -2914,18 +2943,17 @@ class LineNumberGutterComponent { let children = null if (bufferRows) { - const renderedTileCount = rootComponent.getRenderedTileCount() - children = new Array(renderedTileCount) - - for (let tileStartRow = startRow; tileStartRow < endRow; tileStartRow = tileStartRow + rowsPerTile) { + children = new Array(rootComponent.renderedTileStartRows.length) + for (let i = 0; i < rootComponent.renderedTileStartRows.length; i++) { + const tileStartRow = rootComponent.renderedTileStartRows[i] const tileEndRow = Math.min(endRow, tileStartRow + rowsPerTile) const tileChildren = new Array(tileEndRow - tileStartRow) for (let row = tileStartRow; row < tileEndRow; row++) { - const i = row - startRow - const key = keys[i] - const softWrapped = softWrappedFlags[i] - const foldable = foldableFlags[i] - const bufferRow = bufferRows[i] + const j = row - startRow + const key = keys[j] + const softWrapped = softWrappedFlags[j] + const foldable = foldableFlags[j] + const bufferRow = bufferRows[j] let className = 'line-number' if (foldable) className = className + ' foldable' @@ -2954,13 +2982,12 @@ class LineNumberGutterComponent { ) } - const tileIndex = rootComponent.tileIndexForTileStartRow(tileStartRow) const tileTop = rootComponent.pixelPositionBeforeBlocksForRow(tileStartRow) const tileBottom = rootComponent.pixelPositionBeforeBlocksForRow(tileEndRow) const tileHeight = tileBottom - tileTop - children[tileIndex] = $.div({ - key: tileIndex, + children[i] = $.div({ + key: rootComponent.idsByTileStartRow.get(tileStartRow), style: { contain: 'strict', overflow: 'hidden',