Merge pull request #8730 from atom/as-faster-style-recalculation

Faster Style Recalculations
This commit is contained in:
Antonio Scandurra
2015-09-15 11:25:10 +02:00
9 changed files with 73 additions and 90 deletions

View File

@@ -108,7 +108,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@@ -118,7 +118,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
@@ -130,7 +130,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes.length).toBe(3)
@@ -158,7 +158,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(component.lineNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@@ -187,7 +187,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().deleteRows(0, 1)
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@@ -202,7 +202,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().insert([0, 0], '\n\n')
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@@ -294,7 +294,7 @@ describe "TextEditorComponent", ->
editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth()
for lineNode in lineNodes
expect(lineNode.style.width).toBe editorFullWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth)
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
component.measureDimensions()
@@ -302,7 +302,7 @@ describe "TextEditorComponent", ->
scrollViewWidth = scrollViewNode.offsetWidth
for lineNode in lineNodes
expect(lineNode.style.width).toBe scrollViewWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(scrollViewWidth)
it "renders an nbsp on empty lines when no line-ending character is defined", ->
atom.config.set("editor.showInvisibles", false)
@@ -313,7 +313,7 @@ describe "TextEditorComponent", ->
backgroundColor = getComputedStyle(wrapperNode).backgroundColor
expect(linesNode.style.backgroundColor).toBe backgroundColor
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
@@ -322,7 +322,7 @@ describe "TextEditorComponent", ->
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
@@ -606,7 +606,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@@ -616,33 +616,13 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
expect(tilesNodes[2].style.zIndex).toBe("1")
expect(tilesNodes[3].style.zIndex).toBe("0")
it "renders higher line numbers in front of lower ones", ->
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
component.measureDimensions()
nextAnimationFrame()
# Tile 0
expect(component.lineNumberNodeForScreenRow(0).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(1).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(2).style.zIndex).toBe("0")
# Tile 1
expect(component.lineNumberNodeForScreenRow(3).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(4).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(5).style.zIndex).toBe("0")
# Tile 2
expect(component.lineNumberNodeForScreenRow(6).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(7).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(8).style.zIndex).toBe("0")
it "gives the line numbers container the same height as the wrapper node", ->
linesNode = componentNode.querySelector(".line-numbers")
@@ -663,7 +643,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
@@ -689,7 +669,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(component.lineNumberNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@@ -791,7 +771,7 @@ describe "TextEditorComponent", ->
lineNumbersNode = gutterNode.querySelector('.line-numbers')
{backgroundColor} = getComputedStyle(wrapperNode)
expect(lineNumbersNode.style.backgroundColor).toBe backgroundColor
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
# favor gutter color if it's assigned
@@ -800,7 +780,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
it "hides or shows the gutter based on the '::isLineNumberGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", ->
@@ -1133,7 +1113,7 @@ describe "TextEditorComponent", ->
it "renders 2 regions for 2-line selections", ->
editor.setSelectedScreenRange([[1, 6], [2, 10]])
nextAnimationFrame()
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe 2
@@ -1154,7 +1134,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
# Tile 0
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@@ -1177,7 +1157,7 @@ describe "TextEditorComponent", ->
expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right
# Tile 3
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[1]
tileNode = component.tileNodesForLines()[1]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@@ -2408,7 +2388,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
top = 0
for tileNode in tilesNodes

View File

@@ -2035,15 +2035,10 @@ describe "TextEditorPresenter", ->
lineNumberStateForScreenRow = (presenter, screenRow) ->
editor = presenter.model
tileRow = presenter.tileForRow(screenRow)
bufferRow = editor.bufferRowForScreenRow(screenRow)
wrapCount = screenRow - editor.screenRowForBufferRow(bufferRow)
if wrapCount > 0
key = bufferRow + '-' + wrapCount
else
key = bufferRow
line = editor.tokenizedLineForScreenRow(screenRow)
gutterState = getLineNumberGutterState(presenter)
gutterState.content.tiles[tileRow]?.lineNumbers[key]
gutterState.content.tiles[tileRow]?.lineNumbers[line?.id]
tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content

View File

@@ -9,7 +9,6 @@ class LineNumbersTileComponent
constructor: ({@id}) ->
@lineNumberNodesById = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber
@@ -75,28 +74,32 @@ class LineNumbersTileComponent
newLineNumbersHTML += @buildLineNumberHTML(lineNumberState)
@oldTileState.lineNumbers[id] = _.clone(lineNumberState)
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = _.toArray(WrapperDiv.children)
return unless newLineNumberIds?
node = @domNode
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
node.appendChild(lineNumberNode)
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = _.toArray(WrapperDiv.children)
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
if nextNode = @findNodeNextTo(lineNumberNode)
@domNode.insertBefore(lineNumberNode, nextNode)
else
@domNode.appendChild(lineNumberNode)
findNodeNextTo: (node) ->
for nextNode in @domNode.children
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNumberHTML: (lineNumberState) ->
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
if screenRow?
style = "position: absolute; top: #{top}px; z-index: #{zIndex};"
else
style = "visibility: hidden;"
{screenRow, bufferRow, softWrapped, top, decorationClasses} = lineNumberState
className = @buildLineNumberClassName(lineNumberState)
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped)
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
"<div class=\"#{className}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
{maxLineNumberDigits} = @newState
@@ -119,18 +122,15 @@ class LineNumbersTileComponent
oldLineNumberState.foldable = newLineNumberState.foldable
oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses)
unless oldLineNumberState.top is newLineNumberState.top
node.style.top = newLineNumberState.top + 'px'
unless oldLineNumberState.screenRow is newLineNumberState.screenRow and oldLineNumberState.bufferRow is newLineNumberState.bufferRow
node.innerHTML = @buildLineNumberInnerHTML(newLineNumberState.bufferRow, newLineNumberState.softWrapped)
node.dataset.screenRow = newLineNumberState.screenRow
oldLineNumberState.top = newLineNumberState.top
node.dataset.bufferRow = newLineNumberState.bufferRow
oldLineNumberState.screenRow = newLineNumberState.screenRow
unless oldLineNumberState.zIndex is newLineNumberState.zIndex
node.style.zIndex = newLineNumberState.zIndex
oldLineNumberState.zIndex = newLineNumberState.zIndex
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
className = "line-number line-number-#{bufferRow}"
className = "line-number"
className += " " + decorationClasses.join(' ') if decorationClasses?
className += " foldable" if foldable and not softWrapped
className

View File

@@ -21,7 +21,6 @@ class LinesTileComponent
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@@ -110,10 +109,19 @@ class LinesTileComponent
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@domNode.appendChild(lineNode)
if nextNode = @findNodeNextTo(lineNode)
@domNode.insertBefore(lineNode, nextNode)
else
@domNode.appendChild(lineNode)
findNodeNextTo: (node) ->
for nextNode, index in @domNode.children
continue if index is 0 # skips highlights node
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineHTML: (id) ->
{width} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
@@ -124,7 +132,7 @@ class LinesTileComponent
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
lineHTML = "<div class=\"#{classes}\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
@@ -284,9 +292,6 @@ class LinesTileComponent
lineNode = @lineNodesByLineId[id]
if @newState.width isnt @oldState.width
lineNode.style.width = @newState.width + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
@@ -302,10 +307,6 @@ class LinesTileComponent
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.top
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow

View File

@@ -749,6 +749,13 @@ class TextEditorComponent
tileComponent?.lineNumberNodeForScreenRow(screenRow)
tileNodesForLines: ->
@linesComponent.getTiles()
tileNodesForLineNumbers: ->
gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent()
gutterComponent.getTiles()
screenRowForNode: (node) ->
while node?
if screenRow = node.dataset.screenRow

View File

@@ -20,7 +20,7 @@ class TextEditorPresenter
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 12
@tileSize ?= 6
@disposables = new CompositeDisposable
@emitter = new Emitter
@@ -584,22 +584,15 @@ class TextEditorPresenter
if startRow > 0
rowBeforeStartRow = startRow - 1
lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow)
else
lastBufferRow = null
wrapCount = 0
if endRow > startRow
bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1)
zIndex = bufferRows.length - 1
for bufferRow, i in bufferRows
if bufferRow is lastBufferRow
wrapCount++
id = bufferRow + '-' + wrapCount
softWrapped = true
else
id = bufferRow
wrapCount = 0
lastBufferRow = bufferRow
softWrapped = false
@@ -607,10 +600,10 @@ class TextEditorPresenter
top = (screenRow - startRow) * @lineHeight
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
foldable = @model.isFoldableAtScreenRow(screenRow)
id = @model.tokenizedLineForScreenRow(screenRow).id
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable, zIndex}
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
visibleLineNumberIds[id] = true
zIndex--
for id of tileState.lineNumbers
delete tileState.lineNumbers[id] unless visibleLineNumberIds[id]

View File

@@ -1,3 +1,5 @@
{values} = require 'underscore-plus'
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
@@ -49,3 +51,6 @@ class TiledComponent
getComponentForTile: (tileRow) ->
@componentsByTileId[tileRow]
getTiles: ->
values(@componentsByTileId).map (component) -> component.getDomNode()

View File

@@ -46,6 +46,7 @@ atom-text-editor {
}
.line-number {
position: relative;
white-space: nowrap;
padding-left: .5em;
opacity: 0.6;

View File

@@ -29,6 +29,7 @@
}
.line-number {
position: relative;
white-space: nowrap;
padding-left: .5em;
opacity: 0.6;