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