From 20bb14da81ca2fdc21ab6a4f5b4806f3448dd2e2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 19 Jan 2015 16:41:11 -0700 Subject: [PATCH] Start using TextEditorPresenter in LinesComponent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed shouldComponentUpdate because we will always update the component manually once this is done, but I don’t want to accidentally prevent the component from updating during the conversion process. This commit has a failing spec due to the presenter not accounting for individual character widths. --- src/lines-component.coffee | 165 +++++++++++++------------------ src/text-editor-component.coffee | 1 + 2 files changed, 72 insertions(+), 94 deletions(-) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index dc3bdee63..6acf388dc 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -71,33 +71,15 @@ LinesComponent = React.createClass else @overlayManager = new OverlayManager(@getDOMNode()) - shouldComponentUpdate: (newProps) -> - return true unless isEqualForProperties(newProps, @props, - 'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth', - 'overlayDecorations', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'visible', - 'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration', - 'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects' - ) - - {renderedRowRange, pendingChanges} = newProps - return false unless renderedRowRange? - - [renderedStartRow, renderedEndRow] = renderedRowRange - for change in pendingChanges - if change.screenDelta is 0 - return true unless change.end < renderedStartRow or renderedEndRow <= change.start - else - return true unless renderedEndRow <= change.start - - false - componentDidUpdate: (prevProps) -> {visible, scrollingVertically, performedInitialMeasurement} = @props return unless performedInitialMeasurement - @clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels - @removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide') - @updateLines(@props.lineWidth isnt prevProps.lineWidth) + unless isEqualForProperties(prevProps, @props, 'showIndentGuide') + @removeLineNodes() + + @updateLineNodes(@props.lineWidth isnt prevProps.lineWidth) + @measureCharactersInNewLines() if visible and not scrollingVertically @overlayManager?.render(@props) @@ -106,87 +88,80 @@ LinesComponent = React.createClass @screenRowsByLineId = {} @lineIdsByScreenRow = {} - updateLines: (updateWidth) -> - {tokenizedLines, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props - [startRow] = renderedRowRange + removeLineNodes: -> + @removeLineNode(id) for id of @oldState - @removeLineNodes(tokenizedLines) - @appendOrUpdateVisibleLineNodes(tokenizedLines, startRow, updateWidth) + removeLineNode: (id) -> + @lineNodesByLineId[id].remove() + delete @lineNodesByLineId[id] + delete @lineIdsByScreenRow[@screenRowsByLineId[id]] + delete @screenRowsByLineId[id] + delete @oldState[id] - removeLineNodes: (visibleLines=[]) -> - {mouseWheelScreenRow} = @props - visibleLineIds = new Set - visibleLineIds.add(line.id.toString()) for line in visibleLines - node = @getDOMNode() - for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId) - screenRow = @screenRowsByLineId[lineId] - if not screenRow? or screenRow isnt mouseWheelScreenRow - delete @lineNodesByLineId[lineId] - delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId - delete @screenRowsByLineId[lineId] - delete @renderedDecorationsByLineId[lineId] - node.removeChild(lineNode) + updateLineNodes: -> + {presenter, lineDecorations, mouseWheelScreenRow} = @props + @newState = presenter?.state.lines - appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) -> - {lineDecorations} = @props + return unless @newState? + @oldState ?= {} + @lineNodesByLineId ?= {} - newLines = null + for id of @oldState + unless @newState.hasOwnProperty(id) or mouseWheelScreenRow is @screenRowsByLineId[id] + @removeLineNode(id) + + newLineIds = null newLinesHTML = null - for line, index in visibleLines - screenRow = startRow + index - - if @hasLineNode(line.id) - @updateLineNode(line, screenRow, updateWidth) + for id, lineState of @newState + if @oldState.hasOwnProperty(id) + @updateLineNode(id) else - newLines ?= [] + newLineIds ?= [] newLinesHTML ?= "" - newLines.push(line) - newLinesHTML += @buildLineHTML(line, screenRow) - @screenRowsByLineId[line.id] = screenRow - @lineIdsByScreenRow[screenRow] = line.id + newLineIds.push(id) + newLinesHTML += @buildLineHTML(id) + @screenRowsByLineId[id] = lineState.screenRow + @lineIdsByScreenRow[lineState.screenRow] = id + @oldState[id] = _.clone(lineState) - @renderedDecorationsByLineId[line.id] = lineDecorations[screenRow] + @renderedDecorationsByLineId[id] = lineDecorations[lineState.screenRow] - return unless newLines? + return unless newLineIds? WrapperDiv.innerHTML = newLinesHTML newLineNodes = toArray(WrapperDiv.children) node = @getDOMNode() - for line, i in newLines + for id, i in newLineIds lineNode = newLineNodes[i] - @lineNodesByLineId[line.id] = lineNode + @lineNodesByLineId[id] = lineNode node.appendChild(lineNode) - hasLineNode: (lineId) -> - @lineNodesByLineId.hasOwnProperty(lineId) - - buildLineHTML: (line, screenRow) -> - {showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props - {tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line + buildLineHTML: (id) -> + {presenter, showIndentGuide, lineHeightInPixels, lineDecorations} = @props + {screenRow, tokens, text, top, width, lineEnding, fold, isSoftWrapped, indentLevel} = @newState[id] classes = '' if decorations = lineDecorations[screenRow] - for id, decoration of decorations + for decorationId, decoration of decorations if Decoration.isType(decoration, 'line') classes += decoration.class + ' ' classes += 'line' - top = screenRow * lineHeightInPixels - lineHTML = "
" + lineHTML = "
" if text is "" - lineHTML += @buildEmptyLineInnerHTML(line) + lineHTML += @buildEmptyLineInnerHTML(id) else - lineHTML += @buildLineInnerHTML(line) + lineHTML += @buildLineInnerHTML(id) lineHTML += '' if fold lineHTML += "
" lineHTML - buildEmptyLineInnerHTML: (line) -> + buildEmptyLineInnerHTML: (id) -> {showIndentGuide} = @props - {indentLevel, tabLength, endOfLineInvisibles} = line + {indentLevel, tabLength, endOfLineInvisibles} = @newState[id] if showIndentGuide and indentLevel > 0 invisibleIndex = 0 @@ -201,15 +176,15 @@ LinesComponent = React.createClass lineHTML += "" while invisibleIndex < endOfLineInvisibles?.length - lineHTML += "#{line.endOfLineInvisibles[invisibleIndex++]}" + lineHTML += "#{endOfLineInvisibles[invisibleIndex++]}" lineHTML else - @buildEndOfLineHTML(line) or ' ' + @buildEndOfLineHTML(id) or ' ' - buildLineInnerHTML: (line) -> + buildLineInnerHTML: (id) -> {editor, showIndentGuide} = @props - {tokens, text} = line + {tokens, text} = @newState[id] innerHTML = "" scopeStack = [] @@ -221,11 +196,11 @@ LinesComponent = React.createClass innerHTML += token.getValueAsHtml({hasIndentGuide}) innerHTML += @popScope(scopeStack) while scopeStack.length > 0 - innerHTML += @buildEndOfLineHTML(line) + innerHTML += @buildEndOfLineHTML(id) innerHTML - buildEndOfLineHTML: (line) -> - {endOfLineInvisibles} = line + buildEndOfLineHTML: (id) -> + {endOfLineInvisibles} = @newState[id] html = '' if endOfLineInvisibles? @@ -258,30 +233,32 @@ LinesComponent = React.createClass scopeStack.push(scope) "" - updateLineNode: (line, screenRow, updateWidth) -> - {lineHeightInPixels, lineDecorations, lineWidth} = @props - lineNode = @lineNodesByLineId[line.id] + updateLineNode: (id) -> + + {lineHeightInPixels, lineDecorations} = @props + {screenRow, top, width} = @newState[id] + + + lineNode = @lineNodesByLineId[id] decorations = lineDecorations[screenRow] - previousDecorations = @renderedDecorationsByLineId[line.id] + previousDecorations = @renderedDecorationsByLineId[id] if previousDecorations? - for id, decoration of previousDecorations + for decorationId, decoration of previousDecorations if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration) lineNode.classList.remove(decoration.class) if decorations? - for id, decoration of decorations + for decorationId, decoration of decorations if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration) lineNode.classList.add(decoration.class) - lineNode.style.width = lineWidth + 'px' if updateWidth - - unless @screenRowsByLineId[line.id] is screenRow - lineNode.style.top = screenRow * lineHeightInPixels + 'px' - lineNode.dataset.screenRow = screenRow - @screenRowsByLineId[line.id] = screenRow - @lineIdsByScreenRow[screenRow] = line.id + lineNode.style.width = width + 'px' + lineNode.style.top = top + 'px' + lineNode.dataset.screenRow = screenRow + @screenRowsByLineId[id] = screenRow + @lineIdsByScreenRow[screenRow] = id hasDecoration: (decorations, decoration) -> decorations? and decorations[decoration.id] is decoration @@ -314,10 +291,10 @@ LinesComponent = React.createClass node = @getDOMNode() editor.batchCharacterMeasurement => - for tokenizedLine in tokenizedLines + for id, lineState in @newState unless @measuredLines.has(tokenizedLine) - lineNode = @lineNodesByLineId[tokenizedLine.id] - @measureCharactersInLine(tokenizedLine, lineNode) + lineNode = @lineNodesByLineId[id] + @measureCharactersInLine(lineState, lineNode) return measureCharactersInLine: (tokenizedLine, lineNode) -> diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index dbccaf000..189d979df 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -720,6 +720,7 @@ TextEditorComponent = React.createClass onScrollTopChanged: -> @scrollingVertically = true + @presenter?.setScrollTop(@props.editor.getScrollTop()) @requestUpdate() @onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 200) @onStoppedScrollingAfterDelay()