mirror of
https://github.com/atom/atom.git
synced 2026-01-26 07:19:06 -05:00
295 lines
11 KiB
CoffeeScript
295 lines
11 KiB
CoffeeScript
_ = require 'underscore-plus'
|
|
|
|
HighlightsComponent = require './highlights-component'
|
|
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
|
TokenTextEscapeRegex = /[&"'<>]/g
|
|
MaxTokenLength = 20000
|
|
ZERO_WIDTH_NBSP = '\ufeff'
|
|
|
|
cloneObject = (object) ->
|
|
clone = {}
|
|
clone[key] = value for key, value of object
|
|
clone
|
|
|
|
module.exports =
|
|
class LinesTileComponent
|
|
constructor: ({@presenter, @id, @domElementPool, @assert, grammars}) ->
|
|
@measuredLines = new Set
|
|
@lineNodesByLineId = {}
|
|
@screenRowsByLineId = {}
|
|
@lineIdsByScreenRow = {}
|
|
@textNodesByLineId = {}
|
|
@insertionPointsBeforeLineById = {}
|
|
@insertionPointsAfterLineById = {}
|
|
@domNode = @domElementPool.buildElement("div")
|
|
@domNode.style.position = "absolute"
|
|
@domNode.style.display = "block"
|
|
|
|
@highlightsComponent = new HighlightsComponent(@domElementPool)
|
|
@domNode.appendChild(@highlightsComponent.getDomNode())
|
|
|
|
destroy: ->
|
|
@domElementPool.freeElementAndDescendants(@domNode)
|
|
|
|
getDomNode: ->
|
|
@domNode
|
|
|
|
updateSync: (state) ->
|
|
@newState = state
|
|
unless @oldState
|
|
@oldState = {tiles: {}}
|
|
@oldState.tiles[@id] = {lines: {}}
|
|
|
|
@newTileState = @newState.tiles[@id]
|
|
@oldTileState = @oldState.tiles[@id]
|
|
|
|
if @newState.backgroundColor isnt @oldState.backgroundColor
|
|
@domNode.style.backgroundColor = @newState.backgroundColor
|
|
@oldState.backgroundColor = @newState.backgroundColor
|
|
|
|
if @newTileState.zIndex isnt @oldTileState.zIndex
|
|
@domNode.style.zIndex = @newTileState.zIndex
|
|
@oldTileState.zIndex = @newTileState.zIndex
|
|
|
|
if @newTileState.display isnt @oldTileState.display
|
|
@domNode.style.display = @newTileState.display
|
|
@oldTileState.display = @newTileState.display
|
|
|
|
if @newTileState.height isnt @oldTileState.height
|
|
@domNode.style.height = @newTileState.height + 'px'
|
|
@oldTileState.height = @newTileState.height
|
|
|
|
if @newState.width isnt @oldState.width
|
|
@domNode.style.width = @newState.width + 'px'
|
|
@oldTileState.width = @newTileState.width
|
|
|
|
if @newTileState.top isnt @oldTileState.top or @newTileState.left isnt @oldTileState.left
|
|
@domNode.style['-webkit-transform'] = "translate3d(#{@newTileState.left}px, #{@newTileState.top}px, 0px)"
|
|
@oldTileState.top = @newTileState.top
|
|
@oldTileState.left = @newTileState.left
|
|
|
|
@updateLineNodes()
|
|
|
|
@highlightsComponent.updateSync(@newTileState)
|
|
|
|
removeLineNodes: ->
|
|
@removeLineNode(id) for id of @oldTileState.lines
|
|
return
|
|
|
|
removeLineNode: (id) ->
|
|
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
|
|
@removeBlockDecorationInsertionPointBeforeLine(id)
|
|
@removeBlockDecorationInsertionPointAfterLine(id)
|
|
|
|
delete @lineNodesByLineId[id]
|
|
delete @textNodesByLineId[id]
|
|
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
|
delete @screenRowsByLineId[id]
|
|
delete @oldTileState.lines[id]
|
|
|
|
updateLineNodes: ->
|
|
for id of @oldTileState.lines
|
|
unless @newTileState.lines.hasOwnProperty(id)
|
|
@removeLineNode(id)
|
|
|
|
newLineIds = null
|
|
newLineNodes = null
|
|
|
|
for id, lineState of @newTileState.lines
|
|
if @oldTileState.lines.hasOwnProperty(id)
|
|
@updateLineNode(id)
|
|
else
|
|
newLineIds ?= []
|
|
newLineNodes ?= []
|
|
newLineIds.push(id)
|
|
newLineNodes.push(@buildLineNode(id))
|
|
@screenRowsByLineId[id] = lineState.screenRow
|
|
@lineIdsByScreenRow[lineState.screenRow] = id
|
|
@oldTileState.lines[id] = cloneObject(lineState)
|
|
|
|
return unless newLineIds?
|
|
|
|
for id, i in newLineIds
|
|
lineNode = newLineNodes[i]
|
|
@lineNodesByLineId[id] = lineNode
|
|
if nextNode = @findNodeNextTo(lineNode)
|
|
@domNode.insertBefore(lineNode, nextNode)
|
|
else
|
|
@domNode.appendChild(lineNode)
|
|
|
|
@insertBlockDecorationInsertionPointBeforeLine(id)
|
|
@insertBlockDecorationInsertionPointAfterLine(id)
|
|
|
|
removeBlockDecorationInsertionPointBeforeLine: (id) ->
|
|
if insertionPoint = @insertionPointsBeforeLineById[id]
|
|
@domElementPool.freeElementAndDescendants(insertionPoint)
|
|
delete @insertionPointsBeforeLineById[id]
|
|
|
|
insertBlockDecorationInsertionPointBeforeLine: (id) ->
|
|
{hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id]
|
|
|
|
if hasPrecedingBlockDecorations
|
|
lineNode = @lineNodesByLineId[id]
|
|
insertionPoint = @domElementPool.buildElement("content")
|
|
@domNode.insertBefore(insertionPoint, lineNode)
|
|
@insertionPointsBeforeLineById[id] = insertionPoint
|
|
insertionPoint.dataset.screenRow = screenRow
|
|
@updateBlockDecorationInsertionPointBeforeLine(id)
|
|
|
|
updateBlockDecorationInsertionPointBeforeLine: (id) ->
|
|
oldLineState = @oldTileState.lines[id]
|
|
newLineState = @newTileState.lines[id]
|
|
insertionPoint = @insertionPointsBeforeLineById[id]
|
|
return unless insertionPoint?
|
|
|
|
if newLineState.screenRow isnt oldLineState.screenRow
|
|
insertionPoint.dataset.screenRow = newLineState.screenRow
|
|
|
|
precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations.map((d) -> ".atom--block-decoration-#{d.id}").join(',')
|
|
|
|
if precedingBlockDecorationsSelector isnt oldLineState.precedingBlockDecorationsSelector
|
|
insertionPoint.setAttribute("select", precedingBlockDecorationsSelector)
|
|
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
|
|
|
|
removeBlockDecorationInsertionPointAfterLine: (id) ->
|
|
if insertionPoint = @insertionPointsAfterLineById[id]
|
|
@domElementPool.freeElementAndDescendants(insertionPoint)
|
|
delete @insertionPointsAfterLineById[id]
|
|
|
|
insertBlockDecorationInsertionPointAfterLine: (id) ->
|
|
{hasFollowingBlockDecorations, screenRow} = @newTileState.lines[id]
|
|
|
|
if hasFollowingBlockDecorations
|
|
lineNode = @lineNodesByLineId[id]
|
|
insertionPoint = @domElementPool.buildElement("content")
|
|
@domNode.insertBefore(insertionPoint, lineNode.nextSibling)
|
|
@insertionPointsAfterLineById[id] = insertionPoint
|
|
insertionPoint.dataset.screenRow = screenRow
|
|
@updateBlockDecorationInsertionPointAfterLine(id)
|
|
|
|
updateBlockDecorationInsertionPointAfterLine: (id) ->
|
|
oldLineState = @oldTileState.lines[id]
|
|
newLineState = @newTileState.lines[id]
|
|
insertionPoint = @insertionPointsAfterLineById[id]
|
|
return unless insertionPoint?
|
|
|
|
if newLineState.screenRow isnt oldLineState.screenRow
|
|
insertionPoint.dataset.screenRow = newLineState.screenRow
|
|
|
|
followingBlockDecorationsSelector = newLineState.followingBlockDecorations.map((d) -> ".atom--block-decoration-#{d.id}").join(',')
|
|
|
|
if followingBlockDecorationsSelector isnt oldLineState.followingBlockDecorationsSelector
|
|
insertionPoint.setAttribute("select", followingBlockDecorationsSelector)
|
|
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
|
|
|
|
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)
|
|
|
|
buildLineNode: (id) ->
|
|
{lineText, tagCodes, screenRow, decorationClasses} = @newTileState.lines[id]
|
|
|
|
lineNode = @domElementPool.buildElement("div", "line")
|
|
lineNode.dataset.screenRow = screenRow
|
|
|
|
if decorationClasses?
|
|
for decorationClass in decorationClasses
|
|
lineNode.classList.add(decorationClass)
|
|
|
|
textNodes = []
|
|
lineLength = 0
|
|
startIndex = 0
|
|
openScopeNode = lineNode
|
|
for tagCode in tagCodes when tagCode isnt 0
|
|
if @presenter.isCloseTagCode(tagCode)
|
|
openScopeNode = openScopeNode.parentElement
|
|
else if @presenter.isOpenTagCode(tagCode)
|
|
scope = @presenter.tagForCode(tagCode)
|
|
newScopeNode = @domElementPool.buildElement("span", scope.replace(/\.+/g, ' '))
|
|
openScopeNode.appendChild(newScopeNode)
|
|
openScopeNode = newScopeNode
|
|
else
|
|
textNode = @domElementPool.buildText(lineText.substr(startIndex, tagCode))
|
|
startIndex += tagCode
|
|
openScopeNode.appendChild(textNode)
|
|
textNodes.push(textNode)
|
|
|
|
if startIndex is 0
|
|
textNode = @domElementPool.buildText(' ')
|
|
lineNode.appendChild(textNode)
|
|
textNodes.push(textNode)
|
|
|
|
if lineText.endsWith(@presenter.displayLayer.foldCharacter)
|
|
# Insert a zero-width non-breaking whitespace, so that
|
|
# LinesYardstick can take the fold-marker::after pseudo-element
|
|
# into account during measurements when such marker is the last
|
|
# character on the line.
|
|
textNode = @domElementPool.buildText(ZERO_WIDTH_NBSP)
|
|
lineNode.appendChild(textNode)
|
|
textNodes.push(textNode)
|
|
|
|
@textNodesByLineId[id] = textNodes
|
|
lineNode
|
|
|
|
updateLineNode: (id) ->
|
|
oldLineState = @oldTileState.lines[id]
|
|
newLineState = @newTileState.lines[id]
|
|
|
|
lineNode = @lineNodesByLineId[id]
|
|
|
|
newDecorationClasses = newLineState.decorationClasses
|
|
oldDecorationClasses = oldLineState.decorationClasses
|
|
|
|
if oldDecorationClasses?
|
|
for decorationClass in oldDecorationClasses
|
|
unless newDecorationClasses? and decorationClass in newDecorationClasses
|
|
lineNode.classList.remove(decorationClass)
|
|
|
|
if newDecorationClasses?
|
|
for decorationClass in newDecorationClasses
|
|
unless oldDecorationClasses? and decorationClass in oldDecorationClasses
|
|
lineNode.classList.add(decorationClass)
|
|
|
|
oldLineState.decorationClasses = newLineState.decorationClasses
|
|
|
|
if not oldLineState.hasPrecedingBlockDecorations and newLineState.hasPrecedingBlockDecorations
|
|
@insertBlockDecorationInsertionPointBeforeLine(id)
|
|
else if oldLineState.hasPrecedingBlockDecorations and not newLineState.hasPrecedingBlockDecorations
|
|
@removeBlockDecorationInsertionPointBeforeLine(id)
|
|
|
|
if not oldLineState.hasFollowingBlockDecorations and newLineState.hasFollowingBlockDecorations
|
|
@insertBlockDecorationInsertionPointAfterLine(id)
|
|
else if oldLineState.hasFollowingBlockDecorations and not newLineState.hasFollowingBlockDecorations
|
|
@removeBlockDecorationInsertionPointAfterLine(id)
|
|
|
|
if newLineState.screenRow isnt oldLineState.screenRow
|
|
lineNode.dataset.screenRow = newLineState.screenRow
|
|
@lineIdsByScreenRow[newLineState.screenRow] = id
|
|
@screenRowsByLineId[id] = newLineState.screenRow
|
|
|
|
@updateBlockDecorationInsertionPointBeforeLine(id)
|
|
@updateBlockDecorationInsertionPointAfterLine(id)
|
|
|
|
oldLineState.screenRow = newLineState.screenRow
|
|
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
|
|
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
|
|
|
|
lineNodeForScreenRow: (screenRow) ->
|
|
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
|
|
|
lineNodeForLineId: (lineId) ->
|
|
@lineNodesByLineId[lineId]
|
|
|
|
textNodesForLineId: (lineId) ->
|
|
@textNodesByLineId[lineId].slice()
|
|
|
|
lineIdForScreenRow: (screenRow) ->
|
|
@lineIdsByScreenRow[screenRow]
|
|
|
|
textNodesForScreenRow: (screenRow) ->
|
|
@textNodesByLineId[@lineIdsByScreenRow[screenRow]]?.slice()
|