diff --git a/src/foo-component.coffee b/src/foo-component.coffee
new file mode 100644
index 000000000..1c855e125
--- /dev/null
+++ b/src/foo-component.coffee
@@ -0,0 +1,135 @@
+_ = require 'underscore-plus'
+{toArray} = require 'underscore-plus'
+{$$} = require 'space-pen'
+
+CursorsComponent = require './cursors-component'
+HighlightsComponent = require './highlights-component'
+TileComponent = require './tile-component'
+
+DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
+AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
+WrapperDiv = document.createElement('div')
+
+cloneObject = (object) ->
+ clone = {}
+ clone[key] = value for key, value of object
+ clone
+
+module.exports =
+class LinesComponent
+ placeholderTextDiv: null
+
+ constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
+ @tileComponentsByTileId = {}
+
+ @domNode = document.createElement('div')
+ @domNode.classList.add('lines')
+
+ @cursorsComponent = new CursorsComponent(@presenter)
+ @domNode.appendChild(@cursorsComponent.getDomNode())
+
+ @highlightsComponent = new HighlightsComponent(@presenter)
+ @domNode.appendChild(@highlightsComponent.getDomNode())
+
+ if @useShadowDOM
+ insertionPoint = document.createElement('content')
+ insertionPoint.setAttribute('select', '.overlayer')
+ @domNode.appendChild(insertionPoint)
+
+ getDomNode: ->
+ @domNode
+
+ updateSync: (state) ->
+ @newState = state.content
+ @oldState ?= {tiles: {}}
+
+ if @newState.scrollHeight isnt @oldState.scrollHeight
+ @domNode.style.height = @newState.scrollHeight + 'px'
+ @oldState.scrollHeight = @newState.scrollHeight
+
+ if @newState.backgroundColor isnt @oldState.backgroundColor
+ @domNode.style.backgroundColor = @newState.backgroundColor
+ @oldState.backgroundColor = @newState.backgroundColor
+
+ if @newState.placeholderText isnt @oldState.placeholderText
+ @placeholderTextDiv?.remove()
+ if @newState.placeholderText?
+ @placeholderTextDiv = document.createElement('div')
+ @placeholderTextDiv.classList.add('placeholder-text')
+ @placeholderTextDiv.textContent = @newState.placeholderText
+ @domNode.appendChild(@placeholderTextDiv)
+
+ @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
+ @updateTileNodes()
+
+ if @newState.scrollWidth isnt @oldState.scrollWidth
+ @domNode.style.width = @newState.scrollWidth + 'px'
+ @oldState.scrollWidth = @newState.scrollWidth
+
+ @cursorsComponent.updateSync(state)
+ @highlightsComponent.updateSync(state)
+
+ @oldState.indentGuidesVisible = @newState.indentGuidesVisible
+ @oldState.scrollWidth = @newState.scrollWidth
+
+ removeTileNodes: ->
+ @removeTileNode(id) for id of @oldState.tiles
+ return
+
+ removeTileNode: (id) ->
+ node = @tileComponentsByTileId[id].getDomNode()
+
+ node.remove()
+ delete @tileComponentsByTileId[id]
+ delete @oldState.tiles[id]
+
+ updateTileNodes: ->
+ for id of @oldState.tiles
+ unless @newState.tiles.hasOwnProperty(id)
+ @removeTileNode(id)
+
+ for id, tileState of @newState.tiles
+ if @oldState.tiles.hasOwnProperty(id)
+ tileComponent = @tileComponentsByTileId[id]
+ else
+ tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter})
+
+ @domNode.appendChild(tileComponent.getDomNode())
+ @oldState.tiles[id] = cloneObject(tileState)
+
+ tileComponent.updateSync(@newState)
+
+ return
+
+ measureLineHeightAndDefaultCharWidth: ->
+ @domNode.appendChild(DummyLineNode)
+ lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
+ charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
+ @domNode.removeChild(DummyLineNode)
+
+ @presenter.setLineHeight(lineHeightInPixels)
+ @presenter.setBaseCharacterWidth(charWidth)
+
+ remeasureCharacterWidths: ->
+ return unless @presenter.baseCharacterWidth
+
+ @clearScopedCharWidths()
+ @measureCharactersInNewLines()
+
+ measureCharactersInNewLines: ->
+ @presenter.batchCharacterMeasurement =>
+ for id, component of @tileComponentsByTileId
+ component.measureCharactersInNewLines()
+
+ return
+
+ clearScopedCharWidths: ->
+ for id, component of @tileComponentsByTileId
+ component.clearMeasurements()
+
+ @presenter.clearScopedCharacterWidths()
+
+ lineNodeForScreenRow: (screenRow) ->
+ tile = @presenter.tileForRow(screenRow)
+
+ @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow)
diff --git a/src/lines-component.coffee b/src/lines-component.coffee
index 1c855e125..20c9d7ff1 100644
--- a/src/lines-component.coffee
+++ b/src/lines-component.coffee
@@ -2,10 +2,6 @@ _ = require 'underscore-plus'
{toArray} = require 'underscore-plus'
{$$} = require 'space-pen'
-CursorsComponent = require './cursors-component'
-HighlightsComponent = require './highlights-component'
-TileComponent = require './tile-component'
-
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
@@ -16,120 +12,275 @@ cloneObject = (object) ->
clone
module.exports =
-class LinesComponent
+class TileComponent
placeholderTextDiv: null
- constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
- @tileComponentsByTileId = {}
-
- @domNode = document.createElement('div')
- @domNode.classList.add('lines')
-
- @cursorsComponent = new CursorsComponent(@presenter)
- @domNode.appendChild(@cursorsComponent.getDomNode())
-
- @highlightsComponent = new HighlightsComponent(@presenter)
- @domNode.appendChild(@highlightsComponent.getDomNode())
-
- if @useShadowDOM
- insertionPoint = document.createElement('content')
- insertionPoint.setAttribute('select', '.overlayer')
- @domNode.appendChild(insertionPoint)
+ constructor: ({@presenter, @id}) ->
+ @measuredLines = new Set
+ @lineNodesByLineId = {}
+ @screenRowsByLineId = {}
+ @lineIdsByScreenRow = {}
+ @domNode = document.createElement("div")
+ @domNode.classList.add("tile")
+ @domNode.style.position = "absolute"
+ @domNode.style.display = "block"
getDomNode: ->
@domNode
updateSync: (state) ->
- @newState = state.content
- @oldState ?= {tiles: {}}
+ @newState = state
+ unless @oldState
+ @oldState = {tiles: {}}
+ @oldState.tiles[@id] = {lines: {}}
- if @newState.scrollHeight isnt @oldState.scrollHeight
- @domNode.style.height = @newState.scrollHeight + 'px'
- @oldState.scrollHeight = @newState.scrollHeight
+ @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.display isnt @oldTileState.display
+ @domNode.style.display = @newTileState.display
+ @oldTileState.display = @newTileState.display
- if @newState.placeholderText isnt @oldState.placeholderText
- @placeholderTextDiv?.remove()
- if @newState.placeholderText?
- @placeholderTextDiv = document.createElement('div')
- @placeholderTextDiv.classList.add('placeholder-text')
- @placeholderTextDiv.textContent = @newState.placeholderText
- @domNode.appendChild(@placeholderTextDiv)
+ if @newTileState.height isnt @oldTileState.height
+ @domNode.style.height = @newTileState.height + 'px'
+ @oldTileState.height = @newTileState.height
- @removeTileNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
- @updateTileNodes()
+ 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
+
+ @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
+ @updateLineNodes()
if @newState.scrollWidth isnt @oldState.scrollWidth
@domNode.style.width = @newState.scrollWidth + 'px'
@oldState.scrollWidth = @newState.scrollWidth
- @cursorsComponent.updateSync(state)
- @highlightsComponent.updateSync(state)
-
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
- removeTileNodes: ->
- @removeTileNode(id) for id of @oldState.tiles
+ removeLineNodes: ->
+ @removeLineNode(id) for id of @oldTileState.lines
return
- removeTileNode: (id) ->
- node = @tileComponentsByTileId[id].getDomNode()
+ removeLineNode: (id) ->
+ @lineNodesByLineId[id].remove()
+ delete @lineNodesByLineId[id]
+ delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
+ delete @screenRowsByLineId[id]
+ delete @oldTileState.lines[id]
- node.remove()
- delete @tileComponentsByTileId[id]
- delete @oldState.tiles[id]
+ updateLineNodes: ->
+ for id of @oldTileState.lines
+ unless @newTileState.lines.hasOwnProperty(id)
+ @removeLineNode(id)
- updateTileNodes: ->
- for id of @oldState.tiles
- unless @newState.tiles.hasOwnProperty(id)
- @removeTileNode(id)
+ newLineIds = null
+ newLinesHTML = null
- for id, tileState of @newState.tiles
- if @oldState.tiles.hasOwnProperty(id)
- tileComponent = @tileComponentsByTileId[id]
+ for id, lineState of @newTileState.lines
+ if @oldTileState.lines.hasOwnProperty(id)
+ @updateLineNode(id)
else
- tileComponent = @tileComponentsByTileId[id] = new TileComponent({id, @presenter})
+ newLineIds ?= []
+ newLinesHTML ?= ""
+ newLineIds.push(id)
+ newLinesHTML += @buildLineHTML(id)
+ @screenRowsByLineId[id] = lineState.screenRow
+ @lineIdsByScreenRow[lineState.screenRow] = id
+ @oldTileState.lines[id] = cloneObject(lineState)
- @domNode.appendChild(tileComponent.getDomNode())
- @oldState.tiles[id] = cloneObject(tileState)
+ return unless newLineIds?
- tileComponent.updateSync(@newState)
+ WrapperDiv.innerHTML = newLinesHTML
+ newLineNodes = _.toArray(WrapperDiv.children)
+ for id, i in newLineIds
+ lineNode = newLineNodes[i]
+ @lineNodesByLineId[id] = lineNode
+ @domNode.appendChild(lineNode)
return
- measureLineHeightAndDefaultCharWidth: ->
- @domNode.appendChild(DummyLineNode)
- lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
- charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
- @domNode.removeChild(DummyLineNode)
+ buildLineHTML: (id) ->
+ {scrollWidth} = @newState
+ {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
- @presenter.setLineHeight(lineHeightInPixels)
- @presenter.setBaseCharacterWidth(charWidth)
+ classes = ''
+ if decorationClasses?
+ for decorationClass in decorationClasses
+ classes += decorationClass + ' '
+ classes += 'line'
- remeasureCharacterWidths: ->
- return unless @presenter.baseCharacterWidth
+ lineHTML = "
"
- @clearScopedCharWidths()
- @measureCharactersInNewLines()
+ if text is ""
+ lineHTML += @buildEmptyLineInnerHTML(id)
+ else
+ lineHTML += @buildLineInnerHTML(id)
- measureCharactersInNewLines: ->
- @presenter.batchCharacterMeasurement =>
- for id, component of @tileComponentsByTileId
- component.measureCharactersInNewLines()
+ lineHTML += '' if fold
+ lineHTML += "
"
+ lineHTML
- return
+ buildEmptyLineInnerHTML: (id) ->
+ {indentGuidesVisible} = @newState
+ {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
- clearScopedCharWidths: ->
- for id, component of @tileComponentsByTileId
- component.clearMeasurements()
+ if indentGuidesVisible and indentLevel > 0
+ invisibleIndex = 0
+ lineHTML = ''
+ for i in [0...indentLevel]
+ lineHTML += ""
+ for j in [0...tabLength]
+ if invisible = endOfLineInvisibles?[invisibleIndex++]
+ lineHTML += "#{invisible}"
+ else
+ lineHTML += ' '
+ lineHTML += ""
- @presenter.clearScopedCharacterWidths()
+ while invisibleIndex < endOfLineInvisibles?.length
+ lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}"
+
+ lineHTML
+ else
+ @buildEndOfLineHTML(id) or ' '
+
+ buildLineInnerHTML: (id) ->
+ {indentGuidesVisible} = @newState
+ {tokens, text, isOnlyWhitespace} = @newTileState.lines[id]
+ innerHTML = ""
+
+ scopeStack = []
+ for token in tokens
+ innerHTML += @updateScopeStack(scopeStack, token.scopes)
+ hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace))
+ innerHTML += token.getValueAsHtml({hasIndentGuide})
+
+ innerHTML += @popScope(scopeStack) while scopeStack.length > 0
+ innerHTML += @buildEndOfLineHTML(id)
+ innerHTML
+
+ buildEndOfLineHTML: (id) ->
+ {endOfLineInvisibles} = @newTileState.lines[id]
+
+ html = ''
+ if endOfLineInvisibles?
+ for invisible in endOfLineInvisibles
+ html += "#{invisible}"
+ html
+
+ updateScopeStack: (scopeStack, desiredScopeDescriptor) ->
+ html = ""
+
+ # Find a common prefix
+ for scope, i in desiredScopeDescriptor
+ break unless scopeStack[i] is desiredScopeDescriptor[i]
+
+ # Pop scopeDescriptor until we're at the common prefx
+ until scopeStack.length is i
+ html += @popScope(scopeStack)
+
+ # Push onto common prefix until scopeStack equals desiredScopeDescriptor
+ for j in [i...desiredScopeDescriptor.length]
+ html += @pushScope(scopeStack, desiredScopeDescriptor[j])
+
+ html
+
+ popScope: (scopeStack) ->
+ scopeStack.pop()
+ ""
+
+ pushScope: (scopeStack, scope) ->
+ scopeStack.push(scope)
+ ""
+
+ updateLineNode: (id) ->
+ oldLineState = @oldTileState.lines[id]
+ newLineState = @newTileState.lines[id]
+
+ lineNode = @lineNodesByLineId[id]
+
+ if @newState.scrollWidth isnt @oldState.scrollWidth
+ lineNode.style.width = @newState.scrollWidth + 'px'
+
+ 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 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
+ @lineIdsByScreenRow[newLineState.screenRow] = id
lineNodeForScreenRow: (screenRow) ->
- tile = @presenter.tileForRow(screenRow)
+ @lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
- @tileComponentsByTileId[tile]?.lineNodeForScreenRow(screenRow)
+ measureCharactersInNewLines: ->
+ for id, lineState of @oldTileState.lines
+ unless @measuredLines.has(id)
+ lineNode = @lineNodesByLineId[id]
+ @measureCharactersInLine(id, lineState, lineNode)
+ return
+
+ measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
+ rangeForMeasurement = null
+ iterator = null
+ charIndex = 0
+
+ for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens
+ charWidths = @presenter.getScopedCharacterWidths(scopes)
+
+ valueIndex = 0
+ while valueIndex < value.length
+ if hasPairedCharacter
+ char = value.substr(valueIndex, 2)
+ charLength = 2
+ valueIndex += 2
+ else
+ char = value[valueIndex]
+ charLength = 1
+ valueIndex++
+
+ continue if char is '\0'
+
+ unless charWidths[char]?
+ unless textNode?
+ rangeForMeasurement ?= document.createRange()
+ iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
+ textNode = iterator.nextNode()
+ textNodeIndex = 0
+ nextTextNodeIndex = textNode.textContent.length
+
+ while nextTextNodeIndex <= charIndex
+ textNode = iterator.nextNode()
+ textNodeIndex = nextTextNodeIndex
+ nextTextNodeIndex = textNodeIndex + textNode.textContent.length
+
+ i = charIndex - textNodeIndex
+ rangeForMeasurement.setStart(textNode, i)
+ rangeForMeasurement.setEnd(textNode, i + charLength)
+ charWidth = rangeForMeasurement.getBoundingClientRect().width
+ @presenter.setScopedCharacterWidth(scopes, char, charWidth)
+
+ charIndex += charLength
+
+ @measuredLines.add(lineId)
+
+ clearMeasurements: ->
+ @measuredLines.clear()
diff --git a/src/tile-component.coffee b/src/tile-component.coffee
deleted file mode 100644
index 20c9d7ff1..000000000
--- a/src/tile-component.coffee
+++ /dev/null
@@ -1,286 +0,0 @@
-_ = require 'underscore-plus'
-{toArray} = require 'underscore-plus'
-{$$} = require 'space-pen'
-
-DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
-AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
-WrapperDiv = document.createElement('div')
-
-cloneObject = (object) ->
- clone = {}
- clone[key] = value for key, value of object
- clone
-
-module.exports =
-class TileComponent
- placeholderTextDiv: null
-
- constructor: ({@presenter, @id}) ->
- @measuredLines = new Set
- @lineNodesByLineId = {}
- @screenRowsByLineId = {}
- @lineIdsByScreenRow = {}
- @domNode = document.createElement("div")
- @domNode.classList.add("tile")
- @domNode.style.position = "absolute"
- @domNode.style.display = "block"
-
- getDomNode: ->
- @domNode
-
- updateSync: (state) ->
- @newState = state
- unless @oldState
- @oldState = {tiles: {}}
- @oldState.tiles[@id] = {lines: {}}
-
- @newTileState = @newState.tiles[@id]
- @oldTileState = @oldState.tiles[@id]
-
- 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 @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
-
- @removeLineNodes() unless @oldState.indentGuidesVisible is @newState.indentGuidesVisible
- @updateLineNodes()
-
- if @newState.scrollWidth isnt @oldState.scrollWidth
- @domNode.style.width = @newState.scrollWidth + 'px'
- @oldState.scrollWidth = @newState.scrollWidth
-
- @oldState.indentGuidesVisible = @newState.indentGuidesVisible
- @oldState.scrollWidth = @newState.scrollWidth
-
- removeLineNodes: ->
- @removeLineNode(id) for id of @oldTileState.lines
- return
-
- removeLineNode: (id) ->
- @lineNodesByLineId[id].remove()
- delete @lineNodesByLineId[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
- newLinesHTML = null
-
- for id, lineState of @newTileState.lines
- if @oldTileState.lines.hasOwnProperty(id)
- @updateLineNode(id)
- else
- newLineIds ?= []
- newLinesHTML ?= ""
- newLineIds.push(id)
- newLinesHTML += @buildLineHTML(id)
- @screenRowsByLineId[id] = lineState.screenRow
- @lineIdsByScreenRow[lineState.screenRow] = id
- @oldTileState.lines[id] = cloneObject(lineState)
-
- return unless newLineIds?
-
- WrapperDiv.innerHTML = newLinesHTML
- newLineNodes = _.toArray(WrapperDiv.children)
- for id, i in newLineIds
- lineNode = newLineNodes[i]
- @lineNodesByLineId[id] = lineNode
- @domNode.appendChild(lineNode)
-
- return
-
- buildLineHTML: (id) ->
- {scrollWidth} = @newState
- {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
-
- classes = ''
- if decorationClasses?
- for decorationClass in decorationClasses
- classes += decorationClass + ' '
- classes += 'line'
-
- lineHTML = ""
-
- if text is ""
- lineHTML += @buildEmptyLineInnerHTML(id)
- else
- lineHTML += @buildLineInnerHTML(id)
-
- lineHTML += '' if fold
- lineHTML += "
"
- lineHTML
-
- buildEmptyLineInnerHTML: (id) ->
- {indentGuidesVisible} = @newState
- {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
-
- if indentGuidesVisible and indentLevel > 0
- invisibleIndex = 0
- lineHTML = ''
- for i in [0...indentLevel]
- lineHTML += ""
- for j in [0...tabLength]
- if invisible = endOfLineInvisibles?[invisibleIndex++]
- lineHTML += "#{invisible}"
- else
- lineHTML += ' '
- lineHTML += ""
-
- while invisibleIndex < endOfLineInvisibles?.length
- lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}"
-
- lineHTML
- else
- @buildEndOfLineHTML(id) or ' '
-
- buildLineInnerHTML: (id) ->
- {indentGuidesVisible} = @newState
- {tokens, text, isOnlyWhitespace} = @newTileState.lines[id]
- innerHTML = ""
-
- scopeStack = []
- for token in tokens
- innerHTML += @updateScopeStack(scopeStack, token.scopes)
- hasIndentGuide = indentGuidesVisible and (token.hasLeadingWhitespace() or (token.hasTrailingWhitespace() and isOnlyWhitespace))
- innerHTML += token.getValueAsHtml({hasIndentGuide})
-
- innerHTML += @popScope(scopeStack) while scopeStack.length > 0
- innerHTML += @buildEndOfLineHTML(id)
- innerHTML
-
- buildEndOfLineHTML: (id) ->
- {endOfLineInvisibles} = @newTileState.lines[id]
-
- html = ''
- if endOfLineInvisibles?
- for invisible in endOfLineInvisibles
- html += "#{invisible}"
- html
-
- updateScopeStack: (scopeStack, desiredScopeDescriptor) ->
- html = ""
-
- # Find a common prefix
- for scope, i in desiredScopeDescriptor
- break unless scopeStack[i] is desiredScopeDescriptor[i]
-
- # Pop scopeDescriptor until we're at the common prefx
- until scopeStack.length is i
- html += @popScope(scopeStack)
-
- # Push onto common prefix until scopeStack equals desiredScopeDescriptor
- for j in [i...desiredScopeDescriptor.length]
- html += @pushScope(scopeStack, desiredScopeDescriptor[j])
-
- html
-
- popScope: (scopeStack) ->
- scopeStack.pop()
- ""
-
- pushScope: (scopeStack, scope) ->
- scopeStack.push(scope)
- ""
-
- updateLineNode: (id) ->
- oldLineState = @oldTileState.lines[id]
- newLineState = @newTileState.lines[id]
-
- lineNode = @lineNodesByLineId[id]
-
- if @newState.scrollWidth isnt @oldState.scrollWidth
- lineNode.style.width = @newState.scrollWidth + 'px'
-
- 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 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
- @lineIdsByScreenRow[newLineState.screenRow] = id
-
- lineNodeForScreenRow: (screenRow) ->
- @lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
-
- measureCharactersInNewLines: ->
- for id, lineState of @oldTileState.lines
- unless @measuredLines.has(id)
- lineNode = @lineNodesByLineId[id]
- @measureCharactersInLine(id, lineState, lineNode)
- return
-
- measureCharactersInLine: (lineId, tokenizedLine, lineNode) ->
- rangeForMeasurement = null
- iterator = null
- charIndex = 0
-
- for {value, scopes, hasPairedCharacter} in tokenizedLine.tokens
- charWidths = @presenter.getScopedCharacterWidths(scopes)
-
- valueIndex = 0
- while valueIndex < value.length
- if hasPairedCharacter
- char = value.substr(valueIndex, 2)
- charLength = 2
- valueIndex += 2
- else
- char = value[valueIndex]
- charLength = 1
- valueIndex++
-
- continue if char is '\0'
-
- unless charWidths[char]?
- unless textNode?
- rangeForMeasurement ?= document.createRange()
- iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
- textNode = iterator.nextNode()
- textNodeIndex = 0
- nextTextNodeIndex = textNode.textContent.length
-
- while nextTextNodeIndex <= charIndex
- textNode = iterator.nextNode()
- textNodeIndex = nextTextNodeIndex
- nextTextNodeIndex = textNodeIndex + textNode.textContent.length
-
- i = charIndex - textNodeIndex
- rangeForMeasurement.setStart(textNode, i)
- rangeForMeasurement.setEnd(textNode, i + charLength)
- charWidth = rangeForMeasurement.getBoundingClientRect().width
- @presenter.setScopedCharacterWidth(scopes, char, charWidth)
-
- charIndex += charLength
-
- @measuredLines.add(lineId)
-
- clearMeasurements: ->
- @measuredLines.clear()