Extract a LinesComponent

This commit is contained in:
Nathan Sobo
2014-04-14 13:48:54 -06:00
parent 5c2eb053d8
commit e952ab2e02
4 changed files with 142 additions and 162 deletions

112
src/lines-component.coffee Normal file
View File

@@ -0,0 +1,112 @@
React = require 'react'
{div, span} = require 'reactionary'
{debounce, isEqual, multiplyString, pick} = require 'underscore-plus'
{$$} = require 'space-pen'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
module.exports =
LinesComponent = React.createClass
render: ->
{editor, visibleRowRange, showIndentGuide} = @props
[startRow, endRow] = visibleRowRange
lineHeightInPixels = editor.getLineHeight()
precedingHeight = startRow * lineHeightInPixels
followingHeight = (editor.getScreenLineCount() - endRow) * lineHeightInPixels
div className: 'lines', ref: 'lines', [
div className: 'spacer', key: 'top-spacer', style: {height: precedingHeight}
(for tokenizedLine in @props.editor.linesForScreenRows(startRow, endRow - 1)
LineComponent({tokenizedLine, showIndentGuide, key: tokenizedLine.id}))...
div className: 'spacer', key: 'bottom-spacer', style: {height: followingHeight}
]
componentDidMount: ->
@measuredLines = new WeakSet
@updateModelDimensions()
componentDidUpdate: (prevProps) ->
@updateModelDimensions() unless @compareProps(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight')
@clearScopedCharWidths() unless @compareProps(prevProps, @props, 'fontSize', 'fontFamily')
@measureCharactersInNewLines()
compareProps: (a, b, whiteList...) ->
isEqual(pick(a, whiteList...), pick(b, whiteList...))
updateModelDimensions: ->
{editor} = @props
{lineHeightInPixels, charWidth} = @measureLineDimensions()
editor.setLineHeight(lineHeightInPixels)
editor.setDefaultCharWidth(charWidth)
measureLineDimensions: ->
linesNode = @refs.lines.getDOMNode()
linesNode.appendChild(DummyLineNode)
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
linesNode.removeChild(DummyLineNode)
{lineHeightInPixels, charWidth}
measureCharactersInNewLines: ->
[visibleStartRow, visibleEndRow] = @props.visibleRowRange
linesNode = @refs.lines.getDOMNode()
for tokenizedLine, i in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
unless @measuredLines.has(tokenizedLine)
lineNode = linesNode.children[i + 1]
@measureCharactersInLine(tokenizedLine, lineNode)
measureCharactersInLine: (tokenizedLine, lineNode) ->
{editor} = @props
iterator = document.createNodeIterator(lineNode, NodeFilter.SHOW_TEXT, AcceptFilter)
rangeForMeasurement = document.createRange()
for {value, scopes} in tokenizedLine.tokens
textNode = iterator.nextNode()
charWidths = editor.getScopedCharWidths(scopes)
for char, i in value
unless charWidths[char]?
rangeForMeasurement.setStart(textNode, i)
rangeForMeasurement.setEnd(textNode, i + 1)
charWidth = rangeForMeasurement.getBoundingClientRect().width
editor.setScopedCharWidth(scopes, char, charWidth)
@measuredLines.add(tokenizedLine)
clearScopedCharWidths: ->
@measuredLines.clear()
@props.editor.clearScopedCharWidths()
LineComponent = React.createClass
render: ->
div className: 'line', dangerouslySetInnerHTML: {__html: @buildInnerHTML()}
buildInnerHTML: ->
if @props.tokenizedLine.text.length is 0
@buildEmptyLineHTML()
else
@buildScopeTreeHTML(@props.tokenizedLine.getScopeTree())
buildEmptyLineHTML: ->
{showIndentGuide, tokenizedLine} = @props
{indentLevel, tabLength} = tokenizedLine
if showIndentGuide and indentLevel > 0
indentSpan = "<span class='indent-guide'>#{multiplyString(' ', tabLength)}</span>"
multiplyString(indentSpan, indentLevel + 1)
else
"&nbsp;"
buildScopeTreeHTML: (scopeTree) ->
if scopeTree.children?
html = "<span class='#{scopeTree.scope.replace(/\./g, ' ')}'>"
html += @buildScopeTreeHTML(child) for child in scopeTree.children
html += "</span>"
html
else
"<span>#{scopeTree.getValueAsHtml({hasIndentGuide: @props.showIndentGuide})}</span>"
shouldComponentUpdate: (newProps, newState) ->
newProps.showIndentGuide isnt @props.showIndentGuide