React = require 'react' {div, span} = require 'reactionary' {debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus' {$$} = require 'space-pen' SelectionsComponent = require './selections-component' DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT} WrapperDiv = document.createElement('div') module.exports = LinesComponent = React.createClass displayName: 'LinesComponent' render: -> if @isMounted() {editor, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeight} = @props style = height: scrollHeight width: scrollWidth WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)" div {className: 'lines', style}, SelectionsComponent({editor, lineHeight}) if @isMounted componentWillMount: -> @measuredLines = new WeakSet @lineNodesByLineId = {} @lineNodeTopPositions = {} componentDidMount: -> @measureLineHeightAndCharWidth() shouldComponentUpdate: (newProps) -> return true if newProps.selectionChanged return true unless isEqualForProperties(newProps, @props, 'visibleRowRange', 'fontSize', 'fontFamily', 'lineHeight', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically') {visibleRowRange, pendingChanges} = newProps for change in pendingChanges return true unless change.end <= visibleRowRange.start or visibleRowRange.end <= change.start false componentDidUpdate: (prevProps) -> @updateLines() @measureLineHeightAndCharWidth() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight') @clearScopedCharWidths() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily') @measureCharactersInNewLines() unless @props.scrollingVertically updateLines: -> {editor, visibleRowRange, showIndentGuide, selectionChanged} = @props [startRow, endRow] = visibleRowRange startRow = Math.max(0, startRow - 8) endRow = Math.min(editor.getLineCount(), endRow + 8) visibleLines = editor.linesForScreenRows(startRow, endRow - 1) @removeNonVisibleLineNodes(visibleLines) @appendOrUpdateVisibleLineNodes(visibleLines, startRow) removeNonVisibleLineNodes: (visibleLines) -> visibleLineIds = new Set visibleLineIds.add(line.id.toString()) for line in visibleLines node = @getDOMNode() for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId) delete @lineNodesByLineId[lineId] delete @lineNodeTopPositions[lineId] node.removeChild(lineNode) appendOrUpdateVisibleLineNodes: (visibleLines, startRow) -> {lineHeight} = @props newLines = null newLinesHTML = null for line, index in visibleLines screenRow = startRow + index top = (screenRow * lineHeight) if @hasLineNode(line.id) @updateLineNode(line, top) else newLines ?= [] newLinesHTML ?= "" newLines.push(line) newLinesHTML += @buildLineHTML(line, top) @lineNodeTopPositions[line.id] = top return unless newLines? WrapperDiv.innerHTML = newLinesHTML newLineNodes = toArray(WrapperDiv.children) node = @getDOMNode() for line, i in newLines lineNode = newLineNodes[i] @lineNodesByLineId[line.id] = lineNode node.appendChild(lineNode) hasLineNode: (lineId) -> @lineNodesByLineId.hasOwnProperty(lineId) buildTranslate3d: (top) -> "translate3d(0px, #{top}px, 0px)" buildLineHTML: (line, top, left) -> {editor, mini, showIndentGuide} = @props {tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line translate3d = @buildTranslate3d(top, left) lineHTML = "