Comparar commits

...

7 Commits

Autor SHA1 Mensagem Data
Nathan Sobo 0eb44e06d4 Render lines inline instead of with separate components 2014-05-23 15:30:08 -06:00
Nathan Sobo 94b7fc8a75 Use inline line numbers instead of components 2014-05-23 13:19:47 -06:00
Nathan Sobo 0fde4382d2 Remove duplicated code 2014-05-23 12:59:21 -06:00
Nathan Sobo 4e1434eafa Preserve line numbers that are the target of mousewheel events 2014-05-22 16:05:54 -06:00
Nathan Sobo 7cffe77efa Render line numbers with React components 2014-05-22 16:05:54 -06:00
Nathan Sobo f9e94e9779 Keep lines on the DOM if they are the target of mousewheel events 2014-05-22 16:05:54 -06:00
Nathan Sobo 59a4cbafcf Use React components to render individual lines 2014-05-22 16:05:53 -06:00
3 arquivos alterados com 154 adições e 235 exclusões
+84 -126
Ver Arquivo
@@ -4,6 +4,7 @@ React = require 'react-atom-fork'
SubscriberMixin = require './subscriber-mixin'
WrapperDiv = document.createElement('div')
Nbsp = String.fromCharCode(160)
module.exports =
GutterComponent = React.createClass
@@ -11,29 +12,92 @@ GutterComponent = React.createClass
mixins: [SubscriberMixin]
lastMeasuredWidth: null
dummyLineNumberNode: null
wrapCountsByScreenRow: null
render: ->
{scrollHeight, scrollTop} = @props
style =
height: scrollHeight
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
div className: 'gutter',
div className: 'line-numbers', ref: 'lineNumbers', style:
height: scrollHeight
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
div className: 'line-numbers', ref: 'lineNumbers', style: style,
@renderDummyLineNode()
@renderLineNumbers() if @isMounted()
componentWillMount: ->
@lineNumberNodesById = {}
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
renderDummyLineNode: ->
{editor} = @props
@renderLineNumber('dummy', null, editor.getLastBufferRow(), false)
componentDidMount: ->
@appendDummyLineNumber()
renderLineNumbers: ->
{editor, renderedRowRange, maxLineNumberDigits, lineHeightInPixels, mouseWheelScreenRow} = @props
[startRow, endRow] = renderedRowRange
lastBufferRow = null
wrapCount = 0
wrapCountsByScreenRow = {}
lineNumberComponents =
for bufferRow, i in editor.bufferRowsForScreenRows(startRow, endRow - 1)
if bufferRow is lastBufferRow
softWrapped = true
key = "#{bufferRow}-#{++wrapCount}"
else
softWrapped = false
key = bufferRow.toString()
lastBufferRow = bufferRow
wrapCount = 0
screenRow = startRow + i
wrapCountsByScreenRow[screenRow] = wrapCount
@renderLineNumber(key, screenRow, bufferRow, softWrapped)
# Preserve the mouse wheel target's screen row if it exists
if mouseWheelScreenRow? and not (startRow <= mouseWheelScreenRow < endRow)
screenRow = mouseWheelScreenRow
bufferRow = editor.bufferRowForScreenRow(screenRow)
wrapCount = @wrapCountsByScreenRow[screenRow]
wrapCountsByScreenRow[screenRow] = wrapCount
if softWrapped = (wrapCount > 0)
key = "#{bufferRow}-#{wrapCount}"
else
key = bufferRow.toString()
lineNumberComponents.push(@renderLineNumber(key, screenRow, bufferRow, false, endRow))
@wrapCountsByScreenRow = wrapCountsByScreenRow
lineNumberComponents
renderLineNumber: (key, screenRow, bufferRow, softWrapped, screenRowOverride) ->
{lineHeightInPixels, maxLineNumberDigits} = @props
if screenRow?
style =
position: 'absolute'
top: (screenRowOverride ? screenRow) * lineHeightInPixels
else
style =
visibility: 'hidden'
if softWrapped
lineNumber = ""
else
lineNumber = (bufferRow + 1).toString()
if lineNumber.length < maxLineNumberDigits
padding = multiplyString(Nbsp, maxLineNumberDigits - lineNumber.length)
lineNumber = padding + lineNumber
div key: key, className: 'line-number', 'data-screen-row': screenRow, style: style,
lineNumber,
div className: 'icon-right'
# Only update the gutter if the visible row range has changed or if a
# non-zero-delta change to the screen lines has occurred within the current
# visible row range.
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'fontSize')
return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'fontSize', 'maxLineNumberDigits', 'mouseWheelScreenRow')
{renderedRowRange, pendingChanges} = newProps
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
@@ -42,125 +106,19 @@ GutterComponent = React.createClass
false
componentDidUpdate: (oldProps) ->
unless oldProps.maxLineNumberDigits is @props.maxLineNumberDigits
@updateDummyLineNumber()
@removeLineNumberNodes()
@measureWidth() unless @lastMeasuredWidth? and isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'fontSize', 'fontFamily')
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
@updateLineNumbers()
clearScreenRowCaches: ->
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
{maxLineNumberDigits} = @props
WrapperDiv.innerHTML = @buildLineNumberHTML(0, false, maxLineNumberDigits)
@dummyLineNumberNode = WrapperDiv.children[0]
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
updateDummyLineNumber: ->
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
updateLineNumbers: ->
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
@removeLineNumberNodes(lineNumberIdsToPreserve)
appendOrUpdateVisibleLineNumberNodes: ->
{editor, renderedRowRange, scrollTop, maxLineNumberDigits} = @props
[startRow, endRow] = renderedRowRange
newLineNumberIds = null
newLineNumbersHTML = null
visibleLineNumberIds = new Set
wrapCount = 0
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
screenRow = startRow + index
if bufferRow is lastBufferRow
id = "#{bufferRow}-#{wrapCount++}"
else
id = bufferRow.toString()
lastBufferRow = bufferRow
wrapCount = 0
visibleLineNumberIds.add(id)
if @hasLineNumberNode(id)
@updateLineNumberNode(id, screenRow)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
@screenRowsByLineNumberId[id] = screenRow
@lineNumberIdsByScreenRow[screenRow] = id
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = toArray(WrapperDiv.children)
node = @refs.lineNumbers.getDOMNode()
for lineNumberId, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[lineNumberId] = lineNumberNode
node.appendChild(lineNumberNode)
visibleLineNumberIds
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
{mouseWheelScreenRow} = @props
node = @refs.lineNumbers.getDOMNode()
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
screenRow = @screenRowsByLineNumberId[lineNumberId]
unless screenRow is mouseWheelScreenRow
delete @lineNumberNodesById[lineNumberId]
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
delete @screenRowsByLineNumberId[lineNumberId]
node.removeChild(lineNumberNode)
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
if screenRow?
{lineHeightInPixels} = @props
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
else
style = "visibility: hidden;"
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
"<div class=\"line-number\" style=\"#{style}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
if softWrapped
lineNumber = ""
else
lineNumber = (bufferRow + 1).toString()
padding = multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
updateLineNumberNode: (lineNumberId, screenRow) ->
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
{lineHeightInPixels} = @props
@lineNumberNodesById[lineNumberId].style.top = screenRow * lineHeightInPixels + 'px'
@lineNumberNodesById[lineNumberId].dataset.screenRow = screenRow
@screenRowsByLineNumberId[lineNumberId] = screenRow
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
hasLineNumberNode: (lineNumberId) ->
@lineNumberNodesById.hasOwnProperty(lineNumberId)
lineNumberNodeForScreenRow: (screenRow) ->
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
measureWidth: ->
lineNumberNode = @refs.lineNumbers.getDOMNode().firstChild
# return unless lineNumberNode?
width = lineNumberNode.offsetWidth
if width isnt @lastMeasuredWidth
@props.onWidthChanged(@lastMeasuredWidth = width)
lineNumberNodeForScreenRow: (screenRow) ->
{renderedRowRange} = @props
[startRow, endRow] = renderedRowRange
unless startRow <= screenRow < endRow
throw new Error("Requested screenRow #{screenRow} is not currently rendered")
@refs.lineNumbers.getDOMNode().children[screenRow - startRow + 1]
+68 -108
Ver Arquivo
@@ -1,6 +1,6 @@
React = require 'react-atom-fork'
{div, span} = require 'reactionary-atom-fork'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
{debounce, isEqual, isEqualForProperties, multiplyString, toArray, clone} = require 'underscore-plus'
{$$} = require 'space-pen'
SelectionsComponent = require './selections-component'
@@ -16,116 +16,48 @@ LinesComponent = React.createClass
measureWhenShown: false
render: ->
{editor, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, scrollViewHeight} = @props
if @isMounted()
{editor, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, scrollViewHeight} = @props
style =
height: Math.max(scrollHeight, scrollViewHeight)
width: scrollWidth
WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
div {className: 'lines', style},
SelectionsComponent({editor, lineHeightInPixels}) if @isMounted()
@renderLines() if @isMounted()
SelectionsComponent({key: 'selections', editor, lineHeightInPixels})
componentWillMount: ->
@measuredLines = new WeakSet
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
componentDidMount: ->
@measureLineHeightInPixelsAndCharWidth()
shouldComponentUpdate: (newProps) ->
return true if newProps.selectionChanged
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'fontSize', 'fontFamily', 'lineHeight', 'lineHeightInPixels',
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles',
'visible', 'scrollViewHeight'
)
{renderedRowRange, pendingChanges} = newProps
for change in pendingChanges
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
false
componentDidUpdate: (prevProps) ->
@measureLineHeightInPixelsAndCharWidthIfNeeded(prevProps)
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
@updateLines()
@clearScopedCharWidths() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily')
@measureCharactersInNewLines() unless @props.scrollingVertically
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
updateLines: ->
{editor, renderedRowRange, showIndentGuide, selectionChanged} = @props
renderLines: ->
{editor, renderedRowRange, lineHeightInPixels, showIndentGuide, mini, invisibles, mouseWheelScreenRow} = @props
[startRow, endRow] = renderedRowRange
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
@removeLineNodes(visibleLines)
@appendOrUpdateVisibleLineNodes(visibleLines, startRow)
lineComponents =
for line, i in editor.linesForScreenRows(startRow, endRow - 1)
@renderLine(line, startRow + i)
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]
unless screenRow is mouseWheelScreenRow
delete @lineNodesByLineId[lineId]
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
delete @screenRowsByLineId[lineId]
node.removeChild(lineNode)
if mouseWheelScreenRow? and not (startRow <= mouseWheelScreenRow < endRow)
lineComponents.push(@renderLine(editor.lineForScreenRow(mouseWheelScreenRow), mouseWheelScreenRow, endRow))
appendOrUpdateVisibleLineNodes: (visibleLines, startRow) ->
newLines = null
newLinesHTML = null
lineComponents
for line, index in visibleLines
screenRow = startRow + index
renderLine: (line, screenRow, screenRowOverride) ->
{lineHeightInPixels} = @props
if @hasLineNode(line.id)
@updateLineNode(line, screenRow)
else
newLines ?= []
newLinesHTML ?= ""
newLines.push(line)
newLinesHTML += @buildLineHTML(line, screenRow)
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
style =
position: "absolute"
top: (screenRowOverride ? screenRow) * lineHeightInPixels
return unless newLines?
@lineInnerHTMLByLineId[line.id] ?= @buildLineInnerHTML(line)
innerHTML = @lineInnerHTMLByLineId[line.id]
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = toArray(WrapperDiv.children)
node = @getDOMNode()
for line, i in newLines
lineNode = newLineNodes[i]
@lineNodesByLineId[line.id] = lineNode
node.appendChild(lineNode)
div key: line.id, className: "line", style: style, 'data-screen-row': screenRow, dangerouslySetInnerHTML: {__html: innerHTML}
hasLineNode: (lineId) ->
@lineNodesByLineId.hasOwnProperty(lineId)
buildLineHTML: (line, screenRow) ->
{editor, mini, showIndentGuide, lineHeightInPixels} = @props
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
top = screenRow * lineHeightInPixels
lineHTML = "<div class=\"line\" style=\"position: absolute; top: #{top}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(line)
buildLineInnerHTML: (line) ->
if line.text is ""
@buildEmptyLineInnerHTML(line)
else
lineHTML += @buildLineInnerHTML(line)
lineHTML += "</div>"
lineHTML
@buildNonEmptyLineInnerHTML(line)
buildEmptyLineInnerHTML: (line) ->
{showIndentGuide} = @props
@@ -137,7 +69,7 @@ LinesComponent = React.createClass
else
"&nbsp;"
buildLineInnerHTML: (line) ->
buildNonEmptyLineInnerHTML: (line) ->
{invisibles, mini, showIndentGuide, invisibles} = @props
{tokens, text} = line
innerHTML = ""
@@ -151,11 +83,12 @@ LinesComponent = React.createClass
innerHTML += token.getValueAsHtml({invisibles, hasIndentGuide})
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
innerHTML += @buildEndOfLineHTML(line, invisibles)
innerHTML += @buildEndOfLineHTML(line)
innerHTML
buildEndOfLineHTML: (line, invisibles) ->
return '' if @props.mini or line.isSoftWrapped()
buildEndOfLineHTML: (line) ->
{invisibles, mini} = @props
return '' if mini or line.isSoftWrapped()
html = ''
if invisibles.cr? and line.lineEnding is '\r\n'
@@ -190,17 +123,44 @@ LinesComponent = React.createClass
scopeStack.push(scope)
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (line, screenRow) ->
unless @screenRowsByLineId[line.id] is screenRow
{lineHeightInPixels} = @props
lineNode = @lineNodesByLineId[line.id]
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
componentWillMount: ->
@measuredLines = new WeakSet
@lineInnerHTMLByLineId = {}
componentDidMount: ->
@measureLineHeightInPixelsAndCharWidth()
shouldComponentUpdate: (newProps) ->
return true if newProps.selectionChanged
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'fontSize', 'fontFamily', 'lineHeight', 'lineHeightInPixels',
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles',
'visible', 'scrollViewHeight', 'mouseWheelScreenRow'
)
{renderedRowRange, pendingChanges} = newProps
for change in pendingChanges
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
false
componentWillUpdate: (newProps) ->
unless isEqualForProperties(newProps, @props, 'invisibles', 'showIndentGuide')
@lineInnerHTMLByLineId = {}
componentDidUpdate: (prevProps) ->
@measureLineHeightInPixelsAndCharWidthIfNeeded(prevProps)
@clearScopedCharWidths() unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily')
@measureCharactersInNewLines() unless @props.scrollingVertically
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
{renderedRowRange} = @props
[startRow, endRow] = renderedRowRange
unless startRow <= screenRow < endRow
throw new Error("Requested screenRow #{screenRow} is not currently rendered")
@getDOMNode().children[screenRow - startRow]
measureLineHeightInPixelsAndCharWidthIfNeeded: (prevProps) ->
{visible} = @props
@@ -228,9 +188,9 @@ LinesComponent = React.createClass
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
node = @getDOMNode()
for tokenizedLine in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
for tokenizedLine, i in @props.editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
unless @measuredLines.has(tokenizedLine)
lineNode = @lineNodesByLineId[tokenizedLine.id]
lineNode = node.children[i]
@measureCharactersInLine(tokenizedLine, lineNode)
measureCharactersInLine: (tokenizedLine, lineNode) ->
+2 -1
Ver Arquivo
@@ -7,7 +7,8 @@ SelectionsComponent = React.createClass
displayName: 'SelectionsComponent'
render: ->
div className: 'selections', @renderSelections()
div className: 'selections',
@renderSelections() if @isMounted()
renderSelections: ->
{editor, lineHeightInPixels} = @props