From c2b854123b838354908c2ee016c85dfd9bb4699f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 May 2017 11:53:20 +0200 Subject: [PATCH] Never create empty spans at the beginning of a row This was happening when a text decoration overlapped a row, but the next boundary was located exactly at the beginning of it. --- spec/text-editor-component-spec.js | 44 ++++++++++++++++++++++++++++-- src/text-editor-component.js | 21 +++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index b2e825986..1de2a091a 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1953,10 +1953,9 @@ describe('TextEditorComponent', () => { describe('text decorations', () => { it('injects spans with custom class names and inline styles based on text decorations', async () => { - const {component, element, editor} = buildComponent() + const {component, element, editor} = buildComponent({rowsPerTile: 2}) const markerLayer = editor.addMarkerLayer() - const marker1 = markerLayer.markBufferRange([[0, 2], [2, 7]]) const marker2 = markerLayer.markBufferRange([[0, 2], [3, 8]]) const marker3 = markerLayer.markBufferRange([[1, 13], [2, 7]]) @@ -1996,6 +1995,47 @@ describe('TextEditorComponent', () => { expect(textContentOnRowMatchingSelector(component, 3, '.b')).toBe(editor.lineTextForScreenRow(3).slice(0, 10)) }) + it('correctly handles text decorations starting before the first rendered row and/or ending after the last rendered row', async () => { + const {component, element, editor} = buildComponent({autoHeight: false, rowsPerTile: 1}) + element.style.height = 3 * component.getLineHeight() + 'px' + await component.getNextUpdatePromise() + await setScrollTop(component, 4 * component.getLineHeight()) + expect(component.getRenderedStartRow()).toBe(4) + expect(component.getRenderedEndRow()).toBe(9) + + const markerLayer = editor.addMarkerLayer() + const marker1 = markerLayer.markBufferRange([[0, 0], [4, 5]]) + const marker2 = markerLayer.markBufferRange([[7, 2], [10, 8]]) + editor.decorateMarker(marker1, {type: 'text', class: 'a'}) + editor.decorateMarker(marker2, {type: 'text', class: 'b'}) + await component.getNextUpdatePromise() + + expect(textContentOnRowMatchingSelector(component, 4, '.a')).toBe(editor.lineTextForScreenRow(4).slice(0, 5)) + expect(textContentOnRowMatchingSelector(component, 5, '.a')).toBe('') + expect(textContentOnRowMatchingSelector(component, 6, '.a')).toBe('') + expect(textContentOnRowMatchingSelector(component, 7, '.a')).toBe('') + expect(textContentOnRowMatchingSelector(component, 8, '.a')).toBe('') + + expect(textContentOnRowMatchingSelector(component, 4, '.b')).toBe('') + expect(textContentOnRowMatchingSelector(component, 5, '.b')).toBe('') + expect(textContentOnRowMatchingSelector(component, 6, '.b')).toBe('') + expect(textContentOnRowMatchingSelector(component, 7, '.b')).toBe(editor.lineTextForScreenRow(7).slice(2)) + expect(textContentOnRowMatchingSelector(component, 8, '.b')).toBe(editor.lineTextForScreenRow(8)) + }) + + it('does not create empty spans when a text decoration contains a row but another text decoration starts or ends at the beginning of it', async () => { + const {component, element, editor} = buildComponent() + const markerLayer = editor.addMarkerLayer() + const marker1 = markerLayer.markBufferRange([[0, 2], [4, 0]]) + const marker2 = markerLayer.markBufferRange([[2, 0], [5, 8]]) + editor.decorateMarker(marker1, {type: 'text', class: 'a'}) + editor.decorateMarker(marker2, {type: 'text', class: 'b'}) + await component.getNextUpdatePromise() + for (const decorationSpan of element.querySelectorAll('.a, .b')) { + expect(decorationSpan.textContent).not.toBe('') + } + }) + function textContentOnRowMatchingSelector (component, row, selector) { return Array.from(lineNodeForScreenRow(component, row).querySelectorAll(selector)) .map((span) => span.textContent) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 57691e039..6049fb2a1 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -1135,6 +1135,7 @@ class TextEditorComponent { } const renderedStartRow = this.getRenderedStartRow() + const renderedEndRow = this.getRenderedEndRow() const containingMarkers = [] // Iterate over boundaries to build up text decorations. @@ -1186,10 +1187,18 @@ class TextEditorComponent { // Add decoration start with className/style for current position's column, // and also for the start of every row up until the next decoration boundary - this.addTextDecorationStart(boundary.position.row, boundary.position.column, className, style) + if (boundary.position.row >= renderedStartRow) { + this.addTextDecorationStart(boundary.position.row, boundary.position.column, className, style) + } const nextBoundary = this.textDecorationBoundaries[i + 1] if (nextBoundary) { - for (let row = boundary.position.row + 1; row <= nextBoundary.position.row; row++) { + let row = Math.max(boundary.position.row + 1, renderedStartRow) + const endRow = Math.min(nextBoundary.position.row, renderedEndRow) + for (; row < endRow; row++) { + this.addTextDecorationStart(row, 0, className, style) + } + + if (row === nextBoundary.position.row && nextBoundary.position.column !== 0) { this.addTextDecorationStart(row, 0, className, style) } } @@ -3584,7 +3593,9 @@ class LineComponent { let activeStyle = null let nextDecoration = textDecorations ? textDecorations[decorationIndex] : null if (nextDecoration && nextDecoration.column === 0) { - ({className: activeClassName, style: activeStyle} = nextDecoration) + column = nextDecoration.column + activeClassName = nextDecoration.className + activeStyle = nextDecoration.style nextDecoration = textDecorations[++decorationIndex] } @@ -3603,7 +3614,9 @@ class LineComponent { while (nextDecoration && nextDecoration.column <= nextTokenColumn) { const text = lineText.substring(column, nextDecoration.column) this.appendTextNode(textNodes, openScopeNode, text, activeClassName, activeStyle) - ,({column, className: activeClassName, style: activeStyle} = nextDecoration) + column = nextDecoration.column + activeClassName = nextDecoration.className + activeStyle = nextDecoration.style nextDecoration = textDecorations[++decorationIndex] }