Recycle tile nodes (and descendants)

This commit is contained in:
Antonio Scandurra
2015-09-14 14:48:30 +02:00
parent ccb8623a88
commit ece15b2a24
6 changed files with 91 additions and 24 deletions

View 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)

View 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)

View File

@@ -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

View File

@@ -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: {}}

View File

@@ -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

View File

@@ -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]