diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 4ed60c1ab..c7cedb863 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1171,12 +1171,11 @@ describe('TextEditorComponent', () => { editor.getScreenLineCount() * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2)}, + {tileStartRow: 3, height: 3 * component.getLineHeight()} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(item1.previousSibling).toBeNull() expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)) @@ -1196,12 +1195,11 @@ describe('TextEditorComponent', () => { getElementHeight(item1) + getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item3) - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item1) + getElementHeight(item2)}, + {tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item3)} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(item1.previousSibling).toBeNull() expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0)) @@ -1222,12 +1220,11 @@ describe('TextEditorComponent', () => { getElementHeight(item1) + getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item3) - ) - expect(tileNodeForScreenRow(component, 6).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item4) + getElementHeight(item5) - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item3)}, + {tileStartRow: 6, height: 3 * component.getLineHeight() + getElementHeight(item4) + getElementHeight(item5)} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(element.contains(item1)).toBe(false) expect(element.contains(item2)).toBe(false) @@ -1250,12 +1247,11 @@ describe('TextEditorComponent', () => { getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item2) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item3) - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item2)}, + {tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item3)} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(element.contains(item1)).toBe(false) expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1)) @@ -1277,12 +1273,11 @@ describe('TextEditorComponent', () => { getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item2) + getElementHeight(item3) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item2) + getElementHeight(item3)}, + {tileStartRow: 3, height: 3 * component.getLineHeight()} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(element.contains(item1)).toBe(false) expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) @@ -1303,12 +1298,11 @@ describe('TextEditorComponent', () => { getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item3) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item2) - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item3)}, + {tileStartRow: 3, height: 3 * component.getLineHeight() + getElementHeight(item2)} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(element.contains(item1)).toBe(false) expect(item2.previousSibling).toBeNull() @@ -1329,12 +1323,11 @@ describe('TextEditorComponent', () => { getElementHeight(item2) + getElementHeight(item3) + getElementHeight(item4) + getElementHeight(item5) + getElementHeight(item6) ) - expect(tileNodeForScreenRow(component, 0).offsetHeight).toBe( - 3 * component.getLineHeight() + getElementHeight(item2) + getElementHeight(item3) - ) - expect(tileNodeForScreenRow(component, 3).offsetHeight).toBe( - 3 * component.getLineHeight() - ) + assertTilesAreSizedAndPositionedCorrectly(component, [ + {tileStartRow: 0, height: 3 * component.getLineHeight() + getElementHeight(item2) + getElementHeight(item3)}, + {tileStartRow: 3, height: 3 * component.getLineHeight()} + ]) + assertLinesAreAlignedWithLineNumbers(component) expect(element.querySelectorAll('.line').length).toBe(6) expect(element.contains(item1)).toBe(false) expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 0)) @@ -1355,6 +1348,33 @@ describe('TextEditorComponent', () => { const decoration = editor.decorateMarker(marker, {type: 'block', item, position}) return {item, decoration} } + + function assertTilesAreSizedAndPositionedCorrectly (component, tiles) { + let top = 0 + for (let tile of tiles) { + const linesTileElement = lineNodeForScreenRow(component, tile.tileStartRow).parentElement + const linesTileBoundingRect = linesTileElement.getBoundingClientRect() + expect(linesTileBoundingRect.height).toBe(tile.height) + expect(linesTileBoundingRect.top).toBe(top) + + const lineNumbersTileElement = lineNumberNodeForScreenRow(component, tile.tileStartRow).parentElement + const lineNumbersTileBoundingRect = lineNumbersTileElement.getBoundingClientRect() + expect(lineNumbersTileBoundingRect.height).toBe(tile.height) + expect(lineNumbersTileBoundingRect.top).toBe(top) + + top += tile.height + } + } + + function assertLinesAreAlignedWithLineNumbers (component) { + const startRow = component.getRenderedStartRow() + const endRow = component.getRenderedEndRow() + for (let row = startRow; row < endRow; row++) { + const lineNode = lineNodeForScreenRow(component, row) + const lineNumberNode = lineNumberNodeForScreenRow(component, row) + expect(lineNumberNode.getBoundingClientRect().top).toBe(lineNode.getBoundingClientRect().top) + } + } }) describe('mouse input', () => { @@ -2125,10 +2145,6 @@ function lineNumberNodeForScreenRow (component, row) { return gutterElement.children[tileIndex + 1].children[row - tileStartRow] } -function tileNodeForScreenRow (component, row) { - return lineNodeForScreenRow(component, row).parentElement -} - function lineNodeForScreenRow (component, row) { const renderedScreenLine = component.renderedScreenLineForRow(row) return component.lineNodesByScreenLineId.get(renderedScreenLine.id) diff --git a/src/text-editor-component.js b/src/text-editor-component.js index 59593b882..e2451de1f 100644 --- a/src/text-editor-component.js +++ b/src/text-editor-component.js @@ -383,6 +383,7 @@ class TextEditorComponent { numbers: numbers, foldableFlags: foldableFlags, decorations: this.decorationsToRender.lineNumbers, + blockDecorations: this.decorationsToRender.blocks, height: this.getScrollHeight(), width: this.getLineNumberGutterWidth(), lineHeight: this.getLineHeight(), @@ -2335,7 +2336,17 @@ class LineNumberGutterComponent { if (number === -1) number = '•' number = NBSP_CHARACTER.repeat(maxDigits - number.length) + number - tileChildren[row - tileStartRow] = $.div({key, className}, + let lineNumberProps = {key, className} + + if (row === 0 || i > 0) { + let currentRowTop = parentComponent.pixelPositionAfterBlocksForRow(row) + let previousRowBottom = parentComponent.pixelPositionAfterBlocksForRow(row - 1) + lineHeight + if (currentRowTop > previousRowBottom) { + lineNumberProps.style = {marginTop: (currentRowTop - previousRowBottom) + 'px'} + } + } + + tileChildren[row - tileStartRow] = $.div(lineNumberProps, number, $.div({className: 'icon-right'}) ) @@ -2394,6 +2405,43 @@ class LineNumberGutterComponent { if (!arraysEqual(oldProps.numbers, newProps.numbers)) return true if (!arraysEqual(oldProps.foldableFlags, newProps.foldableFlags)) return true if (!arraysEqual(oldProps.decorations, newProps.decorations)) return true + + let oldTileStartRow = oldProps.startRow + let newTileStartRow = newProps.startRow + while (oldTileStartRow < oldProps.endRow || newTileStartRow < newProps.endRow) { + let oldTileBlockDecorations = oldProps.blockDecorations.get(oldTileStartRow) + let newTileBlockDecorations = newProps.blockDecorations.get(newTileStartRow) + + if (oldTileBlockDecorations && newTileBlockDecorations) { + if (oldTileBlockDecorations.size !== newTileBlockDecorations.size) return true + + let blockDecorationsChanged = false + + oldTileBlockDecorations.forEach((oldDecorations, screenLineId) => { + if (!blockDecorationsChanged) { + const newDecorations = newTileBlockDecorations.get(screenLineId) + blockDecorationsChanged = (newDecorations == null || !arraysEqual(oldDecorations, newDecorations)) + } + }) + if (blockDecorationsChanged) return true + + newTileBlockDecorations.forEach((newDecorations, screenLineId) => { + if (!blockDecorationsChanged) { + const oldDecorations = oldTileBlockDecorations.get(screenLineId) + blockDecorationsChanged = (oldDecorations == null) + } + }) + if (blockDecorationsChanged) return true + } else if (oldTileBlockDecorations) { + return true + } else if (newTileBlockDecorations) { + return true + } + + oldTileStartRow += oldProps.rowsPerTile + newTileStartRow += newProps.rowsPerTile + } + return false }