mirror of
https://github.com/atom/atom.git
synced 2026-02-07 21:25:05 -05:00
Move highlight decorations outside of tiles
As a consequence of https://github.com/atom/atom/pull/15378, we are now able to render highlight decorations in a separate div, as opposed to having an highlight container for each tile. Code-wise this is much simpler, because highlights spanning multiple tiles can be represented via a single region and don't need to be split across the tiles they span anymore. As a byproduct, performance should improve as well, because the number of nodes that need to be managed should decrease significantly. This also fixes https://github.com/atom/atom/issues/15449, and other similar rendering artifacts, because highlight decoration DOM nodes won't need to move between tiles anymore when their position changes.
This commit is contained in:
@@ -156,7 +156,7 @@ class TextEditorComponent {
|
||||
this.decorationsToRender = {
|
||||
lineNumbers: null,
|
||||
lines: null,
|
||||
highlights: new Map(),
|
||||
highlights: [],
|
||||
cursors: [],
|
||||
overlays: [],
|
||||
customGutter: new Map(),
|
||||
@@ -164,7 +164,7 @@ class TextEditorComponent {
|
||||
text: []
|
||||
}
|
||||
this.decorationsToMeasure = {
|
||||
highlights: new Map(),
|
||||
highlights: [],
|
||||
cursors: new Map()
|
||||
}
|
||||
this.textDecorationsByMarker = new Map()
|
||||
@@ -546,7 +546,6 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
let children
|
||||
let style = {
|
||||
contain: 'strict',
|
||||
overflow: 'hidden',
|
||||
@@ -557,17 +556,6 @@ class TextEditorComponent {
|
||||
style.height = ceilToPhysicalPixelBoundary(this.getScrollHeight()) + 'px'
|
||||
style.willChange = 'transform'
|
||||
style.transform = `translate(${-roundToPhysicalPixelBoundary(this.getScrollLeft())}px, ${-roundToPhysicalPixelBoundary(this.getScrollTop())}px)`
|
||||
children = [
|
||||
this.renderLineTiles(),
|
||||
this.renderBlockDecorationMeasurementArea(),
|
||||
this.renderCharacterMeasurementLine()
|
||||
]
|
||||
} else {
|
||||
children = [
|
||||
this.renderLineTiles(),
|
||||
this.renderBlockDecorationMeasurementArea(),
|
||||
this.renderCharacterMeasurementLine()
|
||||
]
|
||||
}
|
||||
|
||||
return $.div(
|
||||
@@ -576,10 +564,23 @@ class TextEditorComponent {
|
||||
on: {mousedown: this.didMouseDownOnContent},
|
||||
style
|
||||
},
|
||||
children
|
||||
this.renderHighlightDecorations(),
|
||||
this.renderLineTiles(),
|
||||
this.renderBlockDecorationMeasurementArea(),
|
||||
this.renderCharacterMeasurementLine()
|
||||
)
|
||||
}
|
||||
|
||||
renderHighlightDecorations () {
|
||||
return $(HighlightsComponent, {
|
||||
hasInitialMeasurements: this.hasInitialMeasurements,
|
||||
highlightDecorations: this.decorationsToRender.highlights.slice(),
|
||||
width: this.getScrollWidth(),
|
||||
height: this.getScrollHeight(),
|
||||
lineHeight: this.getLineHeight()
|
||||
})
|
||||
}
|
||||
|
||||
renderLineTiles () {
|
||||
const children = []
|
||||
const style = {
|
||||
@@ -615,7 +616,6 @@ class TextEditorComponent {
|
||||
lineDecorations: this.decorationsToRender.lines.slice(tileStartRow - startRow, tileEndRow - startRow),
|
||||
textDecorations: this.decorationsToRender.text.slice(tileStartRow - startRow, tileEndRow - startRow),
|
||||
blockDecorations: this.decorationsToRender.blocks.get(tileStartRow),
|
||||
highlightDecorations: this.decorationsToRender.highlights.get(tileStartRow),
|
||||
displayLayer: this.props.model.displayLayer,
|
||||
nodePool: this.lineNodesPool,
|
||||
lineComponentsByScreenLineId
|
||||
@@ -963,7 +963,7 @@ class TextEditorComponent {
|
||||
this.decorationsToRender.customGutter.clear()
|
||||
this.decorationsToRender.blocks = new Map()
|
||||
this.decorationsToRender.text = []
|
||||
this.decorationsToMeasure.highlights.clear()
|
||||
this.decorationsToMeasure.highlights.length = 0
|
||||
this.decorationsToMeasure.cursors.clear()
|
||||
this.textDecorationsByMarker.clear()
|
||||
this.textDecorationBoundaries.length = 0
|
||||
@@ -1061,34 +1061,16 @@ class TextEditorComponent {
|
||||
|
||||
const {class: className, flashRequested, flashClass, flashDuration} = decoration
|
||||
decoration.flashRequested = false
|
||||
|
||||
let tileStartRow = this.tileStartRowForRow(screenRange.start.row)
|
||||
const rowsPerTile = this.getRowsPerTile()
|
||||
|
||||
while (tileStartRow <= screenRange.end.row) {
|
||||
const tileEndRow = tileStartRow + rowsPerTile
|
||||
const screenRangeInTile = constrainRangeToRows(screenRange, tileStartRow, tileEndRow)
|
||||
|
||||
let tileHighlights = this.decorationsToMeasure.highlights.get(tileStartRow)
|
||||
if (!tileHighlights) {
|
||||
tileHighlights = []
|
||||
this.decorationsToMeasure.highlights.set(tileStartRow, tileHighlights)
|
||||
}
|
||||
|
||||
tileHighlights.push({
|
||||
screenRange: screenRangeInTile,
|
||||
key,
|
||||
className,
|
||||
flashRequested,
|
||||
flashClass,
|
||||
flashDuration
|
||||
})
|
||||
|
||||
this.requestHorizontalMeasurement(screenRangeInTile.start.row, screenRangeInTile.start.column)
|
||||
this.requestHorizontalMeasurement(screenRangeInTile.end.row, screenRangeInTile.end.column)
|
||||
|
||||
tileStartRow = tileStartRow + rowsPerTile
|
||||
}
|
||||
this.decorationsToMeasure.highlights.push({
|
||||
screenRange,
|
||||
key,
|
||||
className,
|
||||
flashRequested,
|
||||
flashClass,
|
||||
flashDuration
|
||||
})
|
||||
this.requestHorizontalMeasurement(screenRange.start.row, screenRange.start.column)
|
||||
this.requestHorizontalMeasurement(screenRange.end.row, screenRange.end.column)
|
||||
}
|
||||
|
||||
addCursorDecorationToMeasure (decoration, marker, screenRange, reversed) {
|
||||
@@ -1309,18 +1291,16 @@ class TextEditorComponent {
|
||||
}
|
||||
|
||||
updateHighlightsToRender () {
|
||||
this.decorationsToRender.highlights.clear()
|
||||
this.decorationsToMeasure.highlights.forEach((highlights, tileRow) => {
|
||||
for (let i = 0, length = highlights.length; i < length; i++) {
|
||||
const highlight = highlights[i]
|
||||
const {start, end} = highlight.screenRange
|
||||
highlight.startPixelTop = this.pixelPositionAfterBlocksForRow(start.row)
|
||||
highlight.startPixelLeft = this.pixelLeftForRowAndColumn(start.row, start.column)
|
||||
highlight.endPixelTop = this.pixelPositionAfterBlocksForRow(end.row) + this.getLineHeight()
|
||||
highlight.endPixelLeft = this.pixelLeftForRowAndColumn(end.row, end.column)
|
||||
}
|
||||
this.decorationsToRender.highlights.set(tileRow, highlights)
|
||||
})
|
||||
this.decorationsToRender.highlights.length = 0
|
||||
for (let i = 0; i < this.decorationsToMeasure.highlights.length; i++) {
|
||||
const highlight = this.decorationsToMeasure.highlights[i]
|
||||
const {start, end} = highlight.screenRange
|
||||
highlight.startPixelTop = this.pixelPositionAfterBlocksForRow(start.row)
|
||||
highlight.startPixelLeft = this.pixelLeftForRowAndColumn(start.row, start.column)
|
||||
highlight.endPixelTop = this.pixelPositionAfterBlocksForRow(end.row) + this.getLineHeight()
|
||||
highlight.endPixelLeft = this.pixelLeftForRowAndColumn(end.row, end.column)
|
||||
this.decorationsToRender.highlights.push(highlight)
|
||||
}
|
||||
}
|
||||
|
||||
updateCursorsToRender () {
|
||||
@@ -3533,10 +3513,8 @@ class CursorsAndInputComponent {
|
||||
|
||||
class LinesTileComponent {
|
||||
constructor (props) {
|
||||
this.highlightComponentsByKey = new Map()
|
||||
this.props = props
|
||||
etch.initialize(this)
|
||||
this.updateHighlights()
|
||||
this.createLines()
|
||||
this.updateBlockDecorations({}, props)
|
||||
}
|
||||
@@ -3550,16 +3528,10 @@ class LinesTileComponent {
|
||||
this.updateLines(oldProps, newProps)
|
||||
this.updateBlockDecorations(oldProps, newProps)
|
||||
}
|
||||
this.updateHighlights()
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.highlightComponentsByKey.forEach((highlightComponent) => {
|
||||
highlightComponent.destroy()
|
||||
})
|
||||
this.highlightComponentsByKey.clear()
|
||||
|
||||
for (let i = 0; i < this.lineComponents.length; i++) {
|
||||
this.lineComponents[i].destroy()
|
||||
}
|
||||
@@ -3580,12 +3552,7 @@ class LinesTileComponent {
|
||||
width: width + 'px',
|
||||
transform: `translateY(${top}px)`
|
||||
}
|
||||
},
|
||||
$.div({
|
||||
ref: 'highlights',
|
||||
className: 'highlights',
|
||||
style: {layout: 'contain'}
|
||||
})
|
||||
}
|
||||
// Lines and block decorations will be manually inserted here for efficiency
|
||||
)
|
||||
}
|
||||
@@ -3775,40 +3742,6 @@ class LinesTileComponent {
|
||||
}
|
||||
}
|
||||
|
||||
updateHighlights () {
|
||||
const {top, lineHeight, highlightDecorations} = this.props
|
||||
|
||||
const visibleHighlightDecorations = new Set()
|
||||
if (highlightDecorations) {
|
||||
for (let i = 0; i < highlightDecorations.length; i++) {
|
||||
const highlightDecoration = highlightDecorations[i]
|
||||
|
||||
const highlightProps = Object.assign(
|
||||
{parentTileTop: top, lineHeight},
|
||||
highlightDecorations[i]
|
||||
)
|
||||
let highlightComponent = this.highlightComponentsByKey.get(highlightDecoration.key)
|
||||
if (highlightComponent) {
|
||||
highlightComponent.update(highlightProps)
|
||||
} else {
|
||||
highlightComponent = new HighlightComponent(highlightProps)
|
||||
this.refs.highlights.appendChild(highlightComponent.element)
|
||||
this.highlightComponentsByKey.set(highlightDecoration.key, highlightComponent)
|
||||
}
|
||||
|
||||
highlightDecorations[i].flashRequested = false
|
||||
visibleHighlightDecorations.add(highlightDecoration.key)
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightComponentsByKey.forEach((highlightComponent, key) => {
|
||||
if (!visibleHighlightDecorations.has(key)) {
|
||||
highlightComponent.destroy()
|
||||
this.highlightComponentsByKey.delete(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shouldUpdate (newProps) {
|
||||
const oldProps = this.props
|
||||
if (oldProps.top !== newProps.top) return true
|
||||
@@ -3820,25 +3753,6 @@ class LinesTileComponent {
|
||||
if (!arraysEqual(oldProps.screenLines, newProps.screenLines)) return true
|
||||
if (!arraysEqual(oldProps.lineDecorations, newProps.lineDecorations)) return true
|
||||
|
||||
if (!oldProps.highlightDecorations && newProps.highlightDecorations) return true
|
||||
if (oldProps.highlightDecorations && !newProps.highlightDecorations) return true
|
||||
|
||||
if (oldProps.highlightDecorations && newProps.highlightDecorations) {
|
||||
if (oldProps.highlightDecorations.length !== newProps.highlightDecorations.length) return true
|
||||
|
||||
for (let i = 0, length = oldProps.highlightDecorations.length; i < length; i++) {
|
||||
const oldHighlight = oldProps.highlightDecorations[i]
|
||||
const newHighlight = newProps.highlightDecorations[i]
|
||||
if (oldHighlight.className !== newHighlight.className) return true
|
||||
if (newHighlight.flashRequested) return true
|
||||
if (oldHighlight.startPixelTop !== newHighlight.startPixelTop) return true
|
||||
if (oldHighlight.startPixelLeft !== newHighlight.startPixelLeft) return true
|
||||
if (oldHighlight.endPixelTop !== newHighlight.endPixelTop) return true
|
||||
if (oldHighlight.endPixelLeft !== newHighlight.endPixelLeft) return true
|
||||
if (!oldHighlight.screenRange.isEqual(newHighlight.screenRange)) return true
|
||||
}
|
||||
}
|
||||
|
||||
if (oldProps.blockDecorations && newProps.blockDecorations) {
|
||||
if (oldProps.blockDecorations.size !== newProps.blockDecorations.size) return true
|
||||
|
||||
@@ -4009,6 +3923,90 @@ class LineComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightsComponent {
|
||||
constructor (props) {
|
||||
this.props = {}
|
||||
this.element = document.createElement('div')
|
||||
this.element.className = 'highlights'
|
||||
this.element.style.contain = 'strict'
|
||||
this.element.style.position = 'absolute'
|
||||
this.element.style.overflow = 'hidden'
|
||||
this.highlightComponentsByKey = new Map()
|
||||
this.update(props)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.highlightComponentsByKey.forEach((highlightComponent) => {
|
||||
highlightComponent.destroy()
|
||||
})
|
||||
this.highlightComponentsByKey.clear()
|
||||
}
|
||||
|
||||
update (newProps) {
|
||||
if (this.shouldUpdate(newProps)) {
|
||||
this.props = newProps
|
||||
const {height, width, lineHeight, highlightDecorations} = this.props
|
||||
|
||||
this.element.style.height = height + 'px'
|
||||
this.element.style.width = width + 'px'
|
||||
|
||||
const visibleHighlightDecorations = new Set()
|
||||
if (highlightDecorations) {
|
||||
for (let i = 0; i < highlightDecorations.length; i++) {
|
||||
const highlightDecoration = highlightDecorations[i]
|
||||
const highlightProps = Object.assign({lineHeight}, highlightDecorations[i])
|
||||
|
||||
let highlightComponent = this.highlightComponentsByKey.get(highlightDecoration.key)
|
||||
if (highlightComponent) {
|
||||
highlightComponent.update(highlightProps)
|
||||
} else {
|
||||
highlightComponent = new HighlightComponent(highlightProps)
|
||||
this.element.appendChild(highlightComponent.element)
|
||||
this.highlightComponentsByKey.set(highlightDecoration.key, highlightComponent)
|
||||
}
|
||||
|
||||
highlightDecorations[i].flashRequested = false
|
||||
visibleHighlightDecorations.add(highlightDecoration.key)
|
||||
}
|
||||
}
|
||||
|
||||
this.highlightComponentsByKey.forEach((highlightComponent, key) => {
|
||||
if (!visibleHighlightDecorations.has(key)) {
|
||||
highlightComponent.destroy()
|
||||
this.highlightComponentsByKey.delete(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
shouldUpdate (newProps) {
|
||||
const oldProps = this.props
|
||||
|
||||
if (!newProps.hasInitialMeasurements) return false
|
||||
|
||||
if (oldProps.width !== newProps.width) return true
|
||||
if (oldProps.height !== newProps.height) return true
|
||||
if (oldProps.lineHeight !== newProps.lineHeight) return true
|
||||
if (!oldProps.highlightDecorations && newProps.highlightDecorations) return true
|
||||
if (oldProps.highlightDecorations && !newProps.highlightDecorations) return true
|
||||
if (oldProps.highlightDecorations && newProps.highlightDecorations) {
|
||||
if (oldProps.highlightDecorations.length !== newProps.highlightDecorations.length) return true
|
||||
|
||||
for (let i = 0, length = oldProps.highlightDecorations.length; i < length; i++) {
|
||||
const oldHighlight = oldProps.highlightDecorations[i]
|
||||
const newHighlight = newProps.highlightDecorations[i]
|
||||
if (oldHighlight.className !== newHighlight.className) return true
|
||||
if (newHighlight.flashRequested) return true
|
||||
if (oldHighlight.startPixelTop !== newHighlight.startPixelTop) return true
|
||||
if (oldHighlight.startPixelLeft !== newHighlight.startPixelLeft) return true
|
||||
if (oldHighlight.endPixelTop !== newHighlight.endPixelTop) return true
|
||||
if (oldHighlight.endPixelLeft !== newHighlight.endPixelLeft) return true
|
||||
if (!oldHighlight.screenRange.isEqual(newHighlight.screenRange)) return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightComponent {
|
||||
constructor (props) {
|
||||
this.props = props
|
||||
@@ -4054,15 +4052,12 @@ class HighlightComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
let {startPixelTop, endPixelTop} = this.props
|
||||
const {
|
||||
className, screenRange, parentTileTop, lineHeight,
|
||||
startPixelLeft, endPixelLeft
|
||||
className, screenRange, lineHeight,
|
||||
startPixelTop, startPixelLeft, endPixelTop, endPixelLeft
|
||||
} = this.props
|
||||
startPixelTop -= parentTileTop
|
||||
endPixelTop -= parentTileTop
|
||||
const regionClassName = 'region ' + className
|
||||
|
||||
let regionClassName = 'region ' + className
|
||||
let children
|
||||
if (screenRange.start.row === screenRange.end.row) {
|
||||
children = $.div({
|
||||
|
||||
Reference in New Issue
Block a user