From 19d1d148eb063bd5b6894141a7bfdcadc6346bb9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2017 17:30:18 -0700 Subject: [PATCH] Measure the longest visible screen line on initial render --- spec/text-editor-component-spec.js | 15 ++++++- src/text-editor-component.js | 65 +++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 894a518ea..5432065b1 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -4,6 +4,7 @@ import {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} from './a const TextEditorComponent = require('../src/text-editor-component') const TextEditor = require('../src/text-editor') +const TextBuffer = require('text-buffer') const fs = require('fs') const path = require('path') @@ -16,8 +17,8 @@ describe('TextEditorComponent', () => { }) function buildComponent (params = {}) { - const editor = new TextEditor() - editor.setText(SAMPLE_TEXT) + const buffer = new TextBuffer({text: SAMPLE_TEXT}) + const editor = new TextEditor({buffer}) const component = new TextEditorComponent({model: editor, rowsPerTile: params.rowsPerTile}) const {element} = component element.style.width = params.width ? params.width + 'px' : '800px' @@ -76,6 +77,16 @@ describe('TextEditorComponent', () => { ]) }) + it('bases the width of the lines div on the width of the longest initially-visible screen line', () => { + const {component, element, editor} = buildComponent({rowsPerTile: 2, height: 20}) + + expect(editor.getApproximateLongestScreenRow()).toBe(3) + const expectedWidth = element.querySelectorAll('.line')[3].offsetWidth + expect(element.querySelector('.lines').style.width).toBe(expectedWidth + 'px') + + // TODO: Confirm that we'll update this value as indexing proceeds + }) + it('gives the line number gutter an explicit width and height so its layout can be strictly contained', () => { const {component, element, editor} = buildComponent({rowsPerTile: 3}) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 9afd41a2e..fa4773dc9 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -41,13 +41,23 @@ class TextEditorComponent { updateSync () { if (this.nextUpdatePromise) { - const resolveNextUpdatePromise = this.resolveNextUpdatePromise + this.resolveNextUpdatePromise() this.nextUpdatePromise = null this.resolveNextUpdatePromise = null - resolveNextUpdatePromise() } + if (this.staleMeasurements.editorDimensions) this.measureEditorDimensions() - etch.updateSync(this) + + const longestLine = this.getLongestScreenLine() + if (longestLine !== this.previousLongestLine) { + this.longestLineToMeasure = longestLine + etch.updateSync(this) + this.measureLongestLineWidth() + this.previousLongestLine = longestLine + etch.updateSync(this) + } else { + etch.updateSync(this) + } } render () { @@ -170,12 +180,14 @@ class TextEditorComponent { } renderLines () { - let style, children + let children + let style = { + contain: 'strict', + overflow: 'hidden' + } if (this.measurements) { - style = { - width: this.measurements.scrollWidth + 'px', - height: this.getScrollHeight() + 'px' - } + style.width = this.measurements.scrollWidth + 'px', + style.height = this.getScrollHeight() + 'px' children = this.renderLineTiles() } else { children = $.div({ref: 'characterMeasurementLine', className: 'line'}, @@ -206,7 +218,13 @@ class TextEditorComponent { for (let row = tileStartRow; row < tileEndRow; row++) { const screenLine = screenLines[row - firstTileStartRow] if (!screenLine) break - lineNodes.push($(LineComponent, {key: screenLine.id, displayLayer, screenLine})) + + const lineProps = {key: screenLine.id, displayLayer, screenLine} + if (screenLine === this.longestLineToMeasure) { + lineProps.ref = 'longestLineToMeasure' + this.longestLineToMeasure = null + } + lineNodes.push($(LineComponent, lineProps)) } const tileHeight = this.getRowsPerTile() * this.measurements.lineHeight @@ -226,6 +244,16 @@ class TextEditorComponent { }, lineNodes) } + if (this.longestLineToMeasure) { + tileNodes.push($(LineComponent, { + ref: 'longestLineToMeasure', + key: this.longestLineToMeasure.id, + displayLayer, + screenLine: this.longestLineToMeasure + })) + this.longestLineToMeasure = null + } + return tileNodes } @@ -245,7 +273,7 @@ class TextEditorComponent { didShow () { this.getModel().setVisible(true) if (!this.measurements) this.performInitialMeasurements() - etch.updateSync(this) + this.updateSync() } didHide () { @@ -268,7 +296,6 @@ class TextEditorComponent { this.measureEditorDimensions() this.measureScrollPosition() this.measureCharacterDimensions() - this.measureLongestLineWidth() this.measureGutterDimensions() } @@ -290,9 +317,7 @@ class TextEditorComponent { } measureLongestLineWidth () { - const displayLayer = this.getModel().displayLayer - const rightmostPosition = displayLayer.getRightmostScreenPosition() - this.measurements.scrollWidth = rightmostPosition.column * this.measurements.baseCharacterWidth + this.measurements.scrollWidth = this.refs.longestLineToMeasure.element.firstChild.offsetWidth } measureGutterDimensions () { @@ -352,6 +377,15 @@ class TextEditorComponent { return this.getModel().getApproximateScreenLineCount() * this.measurements.lineHeight } + getLongestScreenLine () { + const model = this.getModel() + // Ensure the spatial index is populated with rows that are currently + // visible so we *at least* get the longest row in the visible range. + const renderedEndRow = this.getTileStartRow(this.getLastVisibleRow()) + this.getRowsPerTile() + model.displayLayer.populateSpatialIndexIfNeeded(Infinity, renderedEndRow) + return model.screenLineForScreenRow(model.getApproximateLongestScreenRow()) + } + getNextUpdatePromise () { if (!this.nextUpdatePromise) { this.nextUpdatePromise = new Promise((resolve) => { @@ -370,7 +404,8 @@ class LineComponent { const textNodes = [] let startIndex = 0 - let openScopeNode = this.element + let openScopeNode = document.createElement('span') + this.element.appendChild(openScopeNode) for (let i = 0; i < tagCodes.length; i++) { const tagCode = tagCodes[i] if (tagCode !== 0) {