mirror of
https://github.com/atom/atom.git
synced 2026-01-24 22:38:20 -05:00
Recycle tile nodes (and descendants)
This commit is contained in:
41
spec/dom-elements-pool-spec.coffee
Normal file
41
spec/dom-elements-pool-spec.coffee
Normal file
@@ -0,0 +1,41 @@
|
||||
DomElementsPool = require '../src/dom-elements-pool'
|
||||
|
||||
describe "DomElementsPool", ->
|
||||
domElementsPool = null
|
||||
|
||||
beforeEach ->
|
||||
domElementsPool = new DomElementsPool
|
||||
|
||||
it "creates new nodes until some of them are freed", ->
|
||||
span1 = domElementsPool.build("span")
|
||||
span2 = domElementsPool.build("span")
|
||||
span3 = domElementsPool.build("span")
|
||||
|
||||
expect(span1).not.toBe(span2)
|
||||
expect(span2).not.toBe(span3)
|
||||
|
||||
domElementsPool.free(span1)
|
||||
domElementsPool.free(span2)
|
||||
|
||||
expect(domElementsPool.build("span")).toBe(span2)
|
||||
expect(domElementsPool.build("span")).toBe(span1)
|
||||
|
||||
it "recursively frees a dom tree", ->
|
||||
div = domElementsPool.build("div")
|
||||
span1 = domElementsPool.build("span")
|
||||
span2 = domElementsPool.build("span")
|
||||
span3 = domElementsPool.build("span")
|
||||
span4 = domElementsPool.build("span")
|
||||
|
||||
div.appendChild(span1)
|
||||
span1.appendChild(span2)
|
||||
div.appendChild(span3)
|
||||
span3.appendChild(span4)
|
||||
|
||||
domElementsPool.freeElementAndDescendants(div)
|
||||
|
||||
expect(domElementsPool.build("div")).toBe(div)
|
||||
expect(domElementsPool.build("span")).toBe(span3)
|
||||
expect(domElementsPool.build("span")).toBe(span4)
|
||||
expect(domElementsPool.build("span")).toBe(span1)
|
||||
expect(domElementsPool.build("span")).toBe(span2)
|
||||
25
src/dom-elements-pool.coffee
Normal file
25
src/dom-elements-pool.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
{toArray} = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
class DomElementsPool
|
||||
constructor: ->
|
||||
@freeElementsByTagName = {}
|
||||
|
||||
build: (tagName, className, textContent) ->
|
||||
element = @freeElementsByTagName[tagName]?.pop()
|
||||
element ?= document.createElement(tagName)
|
||||
element.className = className
|
||||
element.textContent = textContent
|
||||
element.removeAttribute("style")
|
||||
element
|
||||
|
||||
free: (element) ->
|
||||
element.remove()
|
||||
@freeElementsByTagName[element.tagName.toLowerCase()] ?= []
|
||||
@freeElementsByTagName[element.tagName.toLowerCase()].push(element)
|
||||
|
||||
freeElementAndDescendants: (element) ->
|
||||
for child in toArray(element.children)
|
||||
@freeElementAndDescendants(child)
|
||||
|
||||
@free(element)
|
||||
@@ -14,6 +14,9 @@ class LineNumbersTileComponent
|
||||
@domNode.style.display = "block"
|
||||
@domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber
|
||||
|
||||
destroy: ->
|
||||
@domNode.remove()
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
CursorsComponent = require './cursors-component'
|
||||
LinesTileComponent = require './lines-tile-component'
|
||||
TiledComponent = require './tiled-component'
|
||||
DomElementsPool = require './dom-elements-pool'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
|
||||
@@ -23,6 +24,8 @@ class LinesComponent extends TiledComponent
|
||||
@cursorsComponent = new CursorsComponent
|
||||
@domNode.appendChild(@cursorsComponent.getDomNode())
|
||||
|
||||
@elementsPool = new DomElementsPool
|
||||
|
||||
if @useShadowDOM
|
||||
insertionPoint = document.createElement('content')
|
||||
insertionPoint.setAttribute('select', '.overlayer')
|
||||
@@ -60,7 +63,7 @@ class LinesComponent extends TiledComponent
|
||||
|
||||
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
|
||||
|
||||
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter})
|
||||
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @elementsPool})
|
||||
|
||||
buildEmptyState: ->
|
||||
{tiles: {}}
|
||||
|
||||
@@ -14,13 +14,13 @@ cloneObject = (object) ->
|
||||
|
||||
module.exports =
|
||||
class LinesTileComponent
|
||||
constructor: ({@presenter, @id}) ->
|
||||
constructor: ({@presenter, @id, @elementsPool}) ->
|
||||
@tokenIterator = new TokenIterator
|
||||
@measuredLines = new Set
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@domNode = document.createElement("div")
|
||||
@domNode = @elementsPool.build("div", "tile")
|
||||
@domNode.classList.add("tile")
|
||||
@domNode.style.position = "absolute"
|
||||
@domNode.style.display = "block"
|
||||
@@ -28,6 +28,9 @@ class LinesTileComponent
|
||||
@highlightsComponent = new HighlightsComponent
|
||||
@domNode.appendChild(@highlightsComponent.getDomNode())
|
||||
|
||||
destroy: ->
|
||||
@elementsPool.freeElementAndDescendants(@domNode)
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
@@ -77,7 +80,7 @@ class LinesTileComponent
|
||||
return
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@lineNodesByLineId[id].remove()
|
||||
@elementsPool.freeElementAndDescendants(@lineNodesByLineId[id])
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
delete @screenRowsByLineId[id]
|
||||
@@ -116,7 +119,7 @@ class LinesTileComponent
|
||||
{width} = @newState
|
||||
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
|
||||
|
||||
lineNode = @buildElement("div", "line")
|
||||
lineNode = @elementsPool.build("div", "line")
|
||||
if decorationClasses?
|
||||
for decorationClass in decorationClasses
|
||||
lineNode.classList.add(decorationClass)
|
||||
@@ -131,15 +134,9 @@ class LinesTileComponent
|
||||
else
|
||||
@appendLineInnerNodes(id, lineNode)
|
||||
|
||||
lineNode.appendChild(@buildElement("span", "fold-marker")) if fold
|
||||
lineNode.appendChild(@elementsPool.build("span", "fold-marker")) if fold
|
||||
lineNode
|
||||
|
||||
buildElement: (name, className, textContent) ->
|
||||
element = document.createElement(name)
|
||||
element.className = className if className?
|
||||
element.textContent = textContent if textContent?
|
||||
element
|
||||
|
||||
appendEmptyLineInnerNodes: (id, lineNode) ->
|
||||
{indentGuidesVisible} = @newState
|
||||
{indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
|
||||
@@ -148,11 +145,11 @@ class LinesTileComponent
|
||||
invisibleIndex = 0
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
indentGuide = @buildElement("span", "indent-guide")
|
||||
indentGuide = @elementsPool.build("span", "indent-guide")
|
||||
for j in [0...tabLength]
|
||||
if invisible = endOfLineInvisibles?[invisibleIndex++]
|
||||
indentGuide.appendChild(
|
||||
@buildElement("span", "invisible-character", invisible)
|
||||
@elementsPool.build("span", "invisible-character", invisible)
|
||||
)
|
||||
else
|
||||
indentGuide.insertAdjacentText("beforeend", " ")
|
||||
@@ -161,7 +158,7 @@ class LinesTileComponent
|
||||
while invisibleIndex < endOfLineInvisibles?.length
|
||||
invisible = endOfLineInvisibles[invisibleIndex++]
|
||||
lineNode.appendChild(
|
||||
@buildElement("span", "invisible-character", invisible)
|
||||
@elementsPool.build("span", "invisible-character", invisible)
|
||||
)
|
||||
else
|
||||
unless @appendEndOfLineNodes(id, lineNode)
|
||||
@@ -181,7 +178,7 @@ class LinesTileComponent
|
||||
openScopeNode = openScopeNode.parentElement
|
||||
|
||||
for scope in @tokenIterator.getScopeStarts()
|
||||
newScopeNode = @buildElement("span", scope.replace(/\.+/g, ' '))
|
||||
newScopeNode = @elementsPool.build("span", scope.replace(/\.+/g, ' '))
|
||||
openScopeNode.appendChild(newScopeNode)
|
||||
openScopeNode = newScopeNode
|
||||
|
||||
@@ -214,7 +211,7 @@ class LinesTileComponent
|
||||
|
||||
appendToken: (scopeNode, tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
|
||||
if isHardTab
|
||||
hardTab = @buildElement("span", "hard-tab", tokenText)
|
||||
hardTab = @elementsPool.build("span", "hard-tab", tokenText)
|
||||
hardTab.classList.add("leading-whitespace") if firstNonWhitespaceIndex?
|
||||
hardTab.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex?
|
||||
hardTab.classList.add("indent-guide") if hasIndentGuide
|
||||
@@ -229,7 +226,7 @@ class LinesTileComponent
|
||||
trailingWhitespaceNode = null
|
||||
|
||||
if firstNonWhitespaceIndex?
|
||||
leadingWhitespaceNode = @buildElement(
|
||||
leadingWhitespaceNode = @elementsPool.build(
|
||||
"span",
|
||||
"leading-whitespace",
|
||||
tokenText.substring(0, firstNonWhitespaceIndex)
|
||||
@@ -242,7 +239,7 @@ class LinesTileComponent
|
||||
if firstTrailingWhitespaceIndex?
|
||||
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
|
||||
|
||||
trailingWhitespaceNode = @buildElement(
|
||||
trailingWhitespaceNode = @elementsPool.build(
|
||||
"span",
|
||||
"trailing-whitespace",
|
||||
tokenText.substring(firstTrailingWhitespaceIndex)
|
||||
@@ -257,7 +254,7 @@ class LinesTileComponent
|
||||
if tokenText.length > MaxTokenLength
|
||||
while startIndex < endIndex
|
||||
text = @sliceText(tokenText, startIndex, startIndex + MaxTokenLength)
|
||||
scopeNode.appendChild(@buildElement("span", null, text))
|
||||
scopeNode.appendChild(@elementsPool.build("span", null, text))
|
||||
startIndex += MaxTokenLength
|
||||
else
|
||||
scopeNode.insertAdjacentText("beforeend", @sliceText(tokenText, startIndex, endIndex))
|
||||
@@ -277,7 +274,7 @@ class LinesTileComponent
|
||||
for invisible in endOfLineInvisibles
|
||||
hasInvisibles = true
|
||||
lineNode.appendChild(
|
||||
@buildElement("span", "invisible-character", invisible)
|
||||
@elementsPool.build("span", "invisible-character", invisible)
|
||||
)
|
||||
|
||||
hasInvisibles
|
||||
|
||||
@@ -21,9 +21,7 @@ class TiledComponent
|
||||
return
|
||||
|
||||
removeTileNode: (tileRow) ->
|
||||
node = @componentsByTileId[tileRow].getDomNode()
|
||||
|
||||
node.remove()
|
||||
@componentsByTileId[tileRow].destroy()
|
||||
delete @componentsByTileId[tileRow]
|
||||
delete @oldState.tiles[tileRow]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user