Make each section of presenter state self-contained

This means we have some duplicated values in different parts of the
tree, but it’s cleaner in the view since each component only consumes
a single object. Seems like the presenter should convey the correct
data to the correct locations and minimize the logic in the view. A
few duplicated integers is a reasonable trade-off.
This commit is contained in:
Nathan Sobo
2015-01-29 14:27:53 -07:00
parent da4b3a47ef
commit bbc1a264b5
7 changed files with 238 additions and 110 deletions

View File

@@ -19,16 +19,20 @@ GutterComponent = React.createClass
render: ->
{presenter} = @props
@newState = presenter.state.gutter
@oldState ?= {lineNumbers: {}}
{scrollHeight, backgroundColor} = @newState
div className: 'gutter',
div className: 'line-numbers', ref: 'lineNumbers', style:
height: presenter.state.scrollHeight
height: scrollHeight
WebkitTransform: @getTransform() if presenter.hasRequiredMeasurements()
backgroundColor: presenter.state.gutter.backgroundColor
backgroundColor: backgroundColor
getTransform: ->
{presenter, useHardwareAcceleration} = @props
{scrollTop} = presenter.state
{useHardwareAcceleration} = @props
{scrollTop} = @newState
if useHardwareAcceleration
"translate3d(0px, #{-scrollTop}px, 0px)"
@@ -39,7 +43,7 @@ GutterComponent = React.createClass
@lineNumberNodesById = {}
componentDidMount: ->
{@maxLineNumberDigits} = @props.presenter.state.gutter
{@maxLineNumberDigits} = @newState
@appendDummyLineNumber()
@updateLineNumbers()
@@ -48,7 +52,7 @@ GutterComponent = React.createClass
node.addEventListener 'mousedown', @onMouseDown
componentDidUpdate: (oldProps) ->
{maxLineNumberDigits} = @props.presenter.state.gutter
{maxLineNumberDigits} = @newState
unless maxLineNumberDigits is @maxLineNumberDigits
@maxLineNumberDigits = maxLineNumberDigits
@updateDummyLineNumber()
@@ -69,13 +73,10 @@ GutterComponent = React.createClass
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false)
updateLineNumbers: ->
{presenter} = @props
@oldState ?= {lineNumbers: {}}
newState = presenter.state.gutter
newLineNumberIds = null
newLineNumbersHTML = null
for id, lineNumberState of newState.lineNumbers
for id, lineNumberState of @newState.lineNumbers
if @oldState.lineNumbers.hasOwnProperty(id)
@updateLineNumberNode(id, lineNumberState)
else
@@ -96,7 +97,7 @@ GutterComponent = React.createClass
node.appendChild(lineNumberNode)
for id, lineNumberState of @oldState.lineNumbers
unless newState.lineNumbers.hasOwnProperty(id)
unless @newState.lineNumbers.hasOwnProperty(id)
@lineNumberNodesById[id].remove()
delete @lineNumberNodesById[id]
delete @oldState.lineNumbers[id]
@@ -113,7 +114,7 @@ GutterComponent = React.createClass
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
{maxLineNumberDigits} = @props.presenter.state.gutter
{maxLineNumberDigits} = @newState
if softWrapped
lineNumber = ""

View File

@@ -18,11 +18,10 @@ LinesComponent = React.createClass
render: ->
{editor, presenter} = @props
@oldState ?= {content: {lines: {}}}
@newState = presenter.state
@oldState ?= {lines: {}}
@newState = presenter.state.content
{scrollHeight} = @newState
{scrollWidth, backgroundColor, placeholderText} = @newState.content
{scrollHeight, scrollWidth, backgroundColor, placeholderText} = @newState
style =
height: scrollHeight
@@ -36,8 +35,7 @@ LinesComponent = React.createClass
HighlightsComponent {presenter}
getTransform: ->
{scrollTop} = @newState
{scrollLeft} = @newState.content
{scrollTop, scrollLeft} = @newState
{useHardwareAcceleration} = @props
if useHardwareAcceleration
@@ -68,41 +66,41 @@ LinesComponent = React.createClass
componentDidUpdate: ->
{visible, presenter} = @props
@removeLineNodes() unless @oldState?.content.indentGuidesVisible is @newState?.content.indentGuidesVisible
@removeLineNodes() unless @oldState?.indentGuidesVisible is @newState?.indentGuidesVisible
@updateLineNodes()
@measureCharactersInNewLines() if visible and not presenter.state.scrollingVertically
@overlayManager?.render(@props)
@oldState.content.indentGuidesVisible = @newState.content.indentGuidesVisible
@oldState.content.scrollWidth = @newState.content.scrollWidth
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
removeLineNodes: ->
@removeLineNode(id) for id of @oldState.content.lines
@removeLineNode(id) for id of @oldState.lines
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
delete @oldState.content.lines[id]
delete @oldState.lines[id]
updateLineNodes: ->
{presenter} = @props
for id of @oldState.content.lines
unless @newState.content.lines.hasOwnProperty(id)
for id of @oldState.lines
unless @newState.lines.hasOwnProperty(id)
@removeLineNode(id)
newLineIds = null
newLinesHTML = null
for id, lineState of @newState.content.lines
if @oldState.content.lines.hasOwnProperty(id)
for id, lineState of @newState.lines
if @oldState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
@@ -111,7 +109,7 @@ LinesComponent = React.createClass
newLinesHTML += @buildLineHTML(id)
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldState.content.lines[id] = _.clone(lineState)
@oldState.lines[id] = _.clone(lineState)
return unless newLineIds?
@@ -125,8 +123,8 @@ LinesComponent = React.createClass
buildLineHTML: (id) ->
{presenter} = @props
{scrollWidth} = @newState.content
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.content.lines[id]
{scrollWidth} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newState.lines[id]
classes = ''
if decorationClasses?
@@ -146,8 +144,8 @@ LinesComponent = React.createClass
lineHTML
buildEmptyLineInnerHTML: (id) ->
{indentGuidesVisible} = @newState.content
{indentLevel, tabLength, endOfLineInvisibles} = @newState.content.lines[id]
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newState.lines[id]
if indentGuidesVisible and indentLevel > 0
invisibleIndex = 0
@@ -170,8 +168,8 @@ LinesComponent = React.createClass
buildLineInnerHTML: (id) ->
{editor} = @props
{indentGuidesVisible} = @newState.content
{tokens, text} = @newState.content.lines[id]
{indentGuidesVisible} = @newState
{tokens, text} = @newState.lines[id]
innerHTML = ""
scopeStack = []
@@ -187,7 +185,7 @@ LinesComponent = React.createClass
innerHTML
buildEndOfLineHTML: (id) ->
{endOfLineInvisibles} = @newState.content.lines[id]
{endOfLineInvisibles} = @newState.lines[id]
html = ''
if endOfLineInvisibles?
@@ -221,13 +219,13 @@ LinesComponent = React.createClass
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
updateLineNode: (id) ->
{scrollWidth} = @newState.content
{screenRow, top} = @newState.content.lines[id]
{scrollWidth} = @newState
{screenRow, top} = @newState.lines[id]
lineNode = @lineNodesByLineId[id]
newDecorationClasses = @newState.content.lines[id].decorationClasses
oldDecorationClasses = @oldState.content.lines[id].decorationClasses
newDecorationClasses = @newState.lines[id].decorationClasses
oldDecorationClasses = @oldState.lines[id].decorationClasses
if oldDecorationClasses?
for decorationClass in oldDecorationClasses
@@ -272,7 +270,7 @@ LinesComponent = React.createClass
node = @getDOMNode()
editor.batchCharacterMeasurement =>
for id, lineState of @oldState.content.lines
for id, lineState of @oldState.lines
unless @measuredLines.has(id)
lineNode = @lineNodesByLineId[id]
@measureCharactersInLine(lineState, lineNode)

View File

@@ -27,8 +27,7 @@ class OverlayManager
itemHeight = item.offsetHeight
{scrollTop} = presenter.state
{scrollLeft} = presenter.state.content
{scrollTop, scrollLeft} = presenter.state.content
left = pixelPosition.left
if left + itemWidth - scrollLeft > presenter.getContentFrameWidth() and left - itemWidth >= scrollLeft

View File

@@ -11,29 +11,29 @@ ScrollbarComponent = React.createClass
switch orientation
when 'vertical'
state = presenter.state.verticalScrollbar
@newState = presenter.state.verticalScrollbar
when 'horizontal'
state = presenter.state.horizontalScrollbar
@newState = presenter.state.horizontalScrollbar
style = {}
style.display = 'none' unless state.visible
style.display = 'none' unless @newState.visible
style.transform = 'translateZ(0)' if useHardwareAcceleration # See atom/atom#3559
switch orientation
when 'vertical'
style.width = state.width
style.bottom = state.bottom
style.width = @newState.width
style.bottom = @newState.bottom
when 'horizontal'
style.left = 0
style.right = state.right
style.height = state.height
style.right = @newState.right
style.height = @newState.height
div {className, style},
switch orientation
when 'vertical'
div className: 'scrollbar-content', style: {height: presenter.state.scrollHeight}
div className: 'scrollbar-content', style: {height: @newState.scrollHeight}
when 'horizontal'
div className: 'scrollbar-content', style: {width: presenter.state.content.scrollWidth}
div className: 'scrollbar-content', style: {width: @newState.scrollWidth}
componentDidMount: ->
{orientation} = @props
@@ -47,14 +47,14 @@ ScrollbarComponent = React.createClass
@getDOMNode().removeEventListener 'scroll', @onScroll
componentDidUpdate: ->
{orientation, presenter} = @props
{orientation} = @props
node = @getDOMNode()
switch orientation
when 'vertical'
node.scrollTop = presenter.state.scrollTop
node.scrollTop = @newState.scrollTop
when 'horizontal'
node.scrollLeft = presenter.state.content.scrollLeft
node.scrollLeft = @newState.scrollLeft
onScroll: ->
{orientation, onScroll} = @props

View File

@@ -34,7 +34,6 @@ TextEditorComponent = React.createClass
scrollSensitivity: 0.4
heightAndWidthMeasurementRequested: false
inputEnabled: true
scopedCharacterWidthsChangeCount: null
domPollingInterval: 100
domPollingIntervalId: null
domPollingPaused: false
@@ -56,7 +55,7 @@ TextEditorComponent = React.createClass
hiddenInputStyle = @getHiddenInputPosition()
hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @useHardwareAcceleration
style.height = @presenter.state.scrollHeight if @autoHeight
style.height = @presenter.state.content.scrollHeight if @autoHeight
if useShadowDOM
className = 'editor-contents--private'
@@ -233,7 +232,6 @@ TextEditorComponent = React.createClass
@subscribe editor.observeGrammar(@onGrammarChanged)
@subscribe editor.observeCursors(@onCursorAdded)
@subscribe editor.observeSelections(@onSelectionAdded)
@subscribe editor.onDidChangeCharacterWidths(@onCharacterWidthsChanged)
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
@subscribe editor.$scrollLeft.changes, @onScrollLeftChanged
@subscribe editor.$verticalScrollbarWidth.changes, @requestUpdate
@@ -579,9 +577,6 @@ TextEditorComponent = React.createClass
@cursorMoved = true
@requestUpdate()
onCharacterWidthsChanged: (@scopedCharacterWidthsChangeCount) ->
@requestUpdate()
handleDragUntilMouseUp: (event, dragHandler) ->
{editor} = @props
dragging = false

View File

@@ -51,10 +51,10 @@ class TextEditorPresenter
buildState: ->
@state =
scrollingVertically: false
horizontalScrollbar: {}
verticalScrollbar: {}
content:
scrollingVertically: false
blinkCursorsOff: false
lines: {}
highlights: {}
@@ -65,6 +65,7 @@ class TextEditorPresenter
updateState: ->
@updateVerticalScrollState()
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateContentState()
@updateLinesState()
@@ -75,8 +76,28 @@ class TextEditorPresenter
@updateLineNumbersState()
updateVerticalScrollState: ->
@state.scrollHeight = @computeScrollHeight()
@state.scrollTop = @getScrollTop()
scrollHeight = @computeScrollHeight()
@state.content.scrollHeight = scrollHeight
@state.gutter.scrollHeight = scrollHeight
@state.verticalScrollbar.scrollHeight = scrollHeight
scrollTop = @getScrollTop()
@state.content.scrollTop = scrollTop
@state.gutter.scrollTop = scrollTop
@state.verticalScrollbar.scrollTop = scrollTop
@emitter.emit 'did-update-state'
updateHorizontalScrollState: ->
scrollWidth = @computeScrollWidth()
@state.content.scrollWidth = scrollWidth
@state.horizontalScrollbar.scrollWidth = scrollWidth
scrollLeft = @getScrollLeft()
@state.content.scrollLeft = @getScrollLeft()
@state.horizontalScrollbar.scrollLeft = @getScrollLeft()
@emitter.emit 'did-update-state'
updateScrollbarsState: ->
contentWidth = @computeContentWidth()
@@ -406,11 +427,11 @@ class TextEditorPresenter
clearTimeout(@stoppedScrollingTimeoutId)
@stoppedScrollingTimeoutId = null
@stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay)
@state.scrollingVertically = true
@state.content.scrollingVertically = true
@emitter.emit 'did-update-state'
didStopScrolling: ->
@state.scrollingVertically = false
@state.content.scrollingVertically = false
if @getMouseWheelScreenRow()?
@mouseWheelScreenRow = null
@updateLinesState()
@@ -421,7 +442,7 @@ class TextEditorPresenter
getScrollTop: -> @scrollTop
setScrollLeft: (@scrollLeft) ->
@updateContentState()
@updateHorizontalScrollState()
getScrollLeft: -> @scrollLeft
@@ -447,6 +468,7 @@ class TextEditorPresenter
@height ? @model.getScreenLineCount() * @getLineHeight()
setContentFrameWidth: (@contentFrameWidth) ->
@updateHorizontalScrollState()
@updateScrollbarsState()
@updateContentState()
@updateLinesState()
@@ -511,6 +533,7 @@ class TextEditorPresenter
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@updateHorizontalScrollState()
@updateContentState()
@updateLinesState()
@updateCursorsState()