mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
Compute ranges of cursors & selections in EditorComponent and pass down
Previously, SelectionsComponenet::shouldComponentUpdate was storing the ranges for selections as a side effect. We also were passing boolean values (cursorMoved and selectionUpdated) to determine if these components should update. Now, we compute a simple hash of screen ranges for selections and cursors in the root component and pass them down. This simplifies shouldComponentUpdate for selections and allows us to implement one for cursors.
This commit is contained in:
@@ -317,7 +317,7 @@ describe "EditorComponent", ->
|
||||
expect(cursorNodes[0].offsetWidth).toBe charWidth
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{5 * charWidth}px, #{0 * lineHeightInPixels}px, 0px)"
|
||||
|
||||
cursor2 = editor.addCursorAtScreenPosition([6, 11])
|
||||
cursor2 = editor.addCursorAtScreenPosition([8, 11])
|
||||
cursor3 = editor.addCursorAtScreenPosition([4, 10])
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
@@ -326,15 +326,15 @@ describe "EditorComponent", ->
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{5 * charWidth}px, #{0 * lineHeightInPixels}px, 0px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate3d(#{10 * charWidth}px, #{4 * lineHeightInPixels}px, 0px)"
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.scrollTop = 4.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
horizontalScrollbarNode.scrollLeft = 3.5 * charWidth
|
||||
horizontalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{(11 - 3.5) * charWidth}px, #{(6 - 2.5) * lineHeightInPixels}px, 0px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate3d(#{(10 - 3.5) * charWidth}px, #{(4 - 2.5) * lineHeightInPixels}px, 0px)"
|
||||
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{(11 - 3.5) * charWidth}px, #{(8 - 4.5) * lineHeightInPixels}px, 0px)"
|
||||
expect(cursorNodes[1].style['-webkit-transform']).toBe "translate3d(#{(10 - 3.5) * charWidth}px, #{(4 - 4.5) * lineHeightInPixels}px, 0px)"
|
||||
|
||||
cursor3.destroy()
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
@@ -364,6 +364,7 @@ describe "EditorComponent", ->
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe true
|
||||
|
||||
advanceClock(component.props.cursorBlinkPeriod / 2)
|
||||
expect(cursorsNode.classList.contains('blink-off')).toBe false
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ CursorComponent = React.createClass
|
||||
displayName: 'CursorComponent'
|
||||
|
||||
render: ->
|
||||
{cursor, scrollTop, scrollLeft} = @props
|
||||
{top, left, height, width} = cursor.getPixelRect()
|
||||
{editor, screenRange, scrollTop, scrollLeft} = @props
|
||||
{top, left, height, width} = editor.pixelRectForScreenRange(screenRange)
|
||||
top -= scrollTop
|
||||
left -= scrollLeft
|
||||
WebkitTransform = "translate3d(#{left}px, #{top}px, 0px)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{debounce, toArray} = require 'underscore-plus'
|
||||
{debounce, toArray, isEqualForProperties, isEqual} = require 'underscore-plus'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
CursorComponent = require './cursor-component'
|
||||
|
||||
@@ -12,7 +12,7 @@ CursorsComponent = React.createClass
|
||||
cursorBlinkIntervalHandle: null
|
||||
|
||||
render: ->
|
||||
{editor, scrollTop, scrollLeft} = @props
|
||||
{editor, cursorScreenRanges, scrollTop, scrollLeft} = @props
|
||||
{blinkOff} = @state
|
||||
|
||||
className = 'cursors'
|
||||
@@ -20,10 +20,8 @@ CursorsComponent = React.createClass
|
||||
|
||||
div {className},
|
||||
if @isMounted()
|
||||
for selection in editor.getSelections()
|
||||
if selection.isEmpty() and editor.selectionIntersectsVisibleRowRange(selection)
|
||||
{cursor} = selection
|
||||
CursorComponent({key: cursor.id, cursor, scrollTop, scrollLeft})
|
||||
for key, screenRange of cursorScreenRanges
|
||||
CursorComponent({key, editor, screenRange, scrollTop, scrollLeft})
|
||||
|
||||
getInitialState: ->
|
||||
blinkOff: false
|
||||
@@ -34,8 +32,12 @@ CursorsComponent = React.createClass
|
||||
componentWillUnmount: ->
|
||||
@stopBlinkingCursors()
|
||||
|
||||
componentWillUpdate: ({cursorsMoved}) ->
|
||||
@pauseCursorBlinking() if cursorsMoved
|
||||
shouldComponentUpdate: (newProps, newState) ->
|
||||
not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft') or
|
||||
not newState.blinkOff is @state.blinkOff
|
||||
|
||||
componentWillUpdate: (newProps) ->
|
||||
@pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges)
|
||||
|
||||
startBlinkingCursors: ->
|
||||
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()
|
||||
|
||||
@@ -45,6 +45,8 @@ EditorComponent = React.createClass
|
||||
if @isMounted()
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
|
||||
selectionScreenRanges = @getSelectionScreenRanges(renderedRowRange)
|
||||
scrollHeight = editor.getScrollHeight()
|
||||
scrollWidth = editor.getScrollWidth()
|
||||
scrollTop = editor.getScrollTop()
|
||||
@@ -79,11 +81,14 @@ EditorComponent = React.createClass
|
||||
onFocus: @onInputFocused
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
CursorsComponent({editor, scrollTop, scrollLeft, @cursorsMoved, @selectionAdded, cursorBlinkPeriod, cursorBlinkResumeDelay})
|
||||
CursorsComponent {
|
||||
editor, scrollTop, scrollLeft, cursorScreenRanges, cursorBlinkPeriod, cursorBlinkResumeDelay,
|
||||
fontSize, fontFamily, lineHeightInPixels
|
||||
}
|
||||
LinesComponent {
|
||||
ref: 'lines', editor, fontSize, fontFamily, lineHeight, lineHeightInPixels,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically,
|
||||
@selectionChanged, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
selectionScreenRanges, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
visible, scrollViewHeight
|
||||
}
|
||||
|
||||
@@ -159,13 +164,61 @@ EditorComponent = React.createClass
|
||||
componentDidUpdate: ->
|
||||
@pendingChanges.length = 0
|
||||
@cursorsMoved = false
|
||||
@selectionChanged = false
|
||||
@selectionAdded = false
|
||||
@refreshingScrollbars = false
|
||||
@measureScrollbars() if @measuringScrollbars
|
||||
@pauseOverflowChangedEvents()
|
||||
@props.parentView.trigger 'editor:display-updated'
|
||||
|
||||
requestUpdate: ->
|
||||
if @batchingUpdates
|
||||
@updateRequested = true
|
||||
else
|
||||
@forceUpdate()
|
||||
|
||||
getRenderedRowRange: ->
|
||||
{editor, lineOverdrawMargin} = @props
|
||||
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
|
||||
renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
|
||||
renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
|
||||
[renderedStartRow, renderedEndRow]
|
||||
|
||||
getHiddenInputPosition: ->
|
||||
{editor} = @props
|
||||
{focused} = @state
|
||||
return {top: 0, left: 0} unless @isMounted() and focused and editor.getCursor()?
|
||||
|
||||
{top, left, height, width} = editor.getCursor().getPixelRect()
|
||||
width = 2 if width is 0 # Prevent autoscroll at the end of longest line
|
||||
top -= editor.getScrollTop()
|
||||
left -= editor.getScrollLeft()
|
||||
top = Math.max(0, Math.min(editor.getHeight() - height, top))
|
||||
left = Math.max(0, Math.min(editor.getWidth() - width, left))
|
||||
{top, left}
|
||||
|
||||
getCursorScreenRanges: (renderedRowRange) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
cursorScreenRanges = {}
|
||||
for selection in editor.getSelections() when selection.isEmpty()
|
||||
{cursor} = selection
|
||||
screenRange = cursor.getScreenRange()
|
||||
if renderedStartRow <= screenRange.start.row < renderedEndRow
|
||||
cursorScreenRanges[cursor.id] = screenRange
|
||||
cursorScreenRanges
|
||||
|
||||
getSelectionScreenRanges: (renderedRowRange) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
selectionScreenRanges = {}
|
||||
for selection, index in editor.getSelections()
|
||||
# Rendering artifacts occur on the lines GPU layer if we remove the last selection
|
||||
screenRange = selection.getScreenRange()
|
||||
if index is 0 or (not screenRange.isEmpty() and screenRange.intersectsRowRange(renderedStartRow, renderedEndRow))
|
||||
selectionScreenRanges[selection.id] = screenRange
|
||||
selectionScreenRanges
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor, 'batched-updates-started', @onBatchedUpdatesStarted
|
||||
@@ -549,12 +602,6 @@ EditorComponent = React.createClass
|
||||
# if the editor's content and dimensions require them to be visible.
|
||||
@requestUpdate()
|
||||
|
||||
requestUpdate: ->
|
||||
if @batchingUpdates
|
||||
@updateRequested = true
|
||||
else
|
||||
@forceUpdate()
|
||||
|
||||
pauseOverflowChangedEvents: ->
|
||||
@overflowChangedEventsPaused = true
|
||||
@resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
|
||||
@@ -672,23 +719,3 @@ EditorComponent = React.createClass
|
||||
top = clientY - scrollViewClientRect.top + editor.getScrollTop()
|
||||
left = clientX - scrollViewClientRect.left + editor.getScrollLeft()
|
||||
{top, left}
|
||||
|
||||
getHiddenInputPosition: ->
|
||||
{editor} = @props
|
||||
{focused} = @state
|
||||
return {top: 0, left: 0} unless @isMounted() and focused and editor.getCursor()?
|
||||
|
||||
{top, left, height, width} = editor.getCursor().getPixelRect()
|
||||
width = 2 if width is 0 # Prevent autoscroll at the end of longest line
|
||||
top -= editor.getScrollTop()
|
||||
left -= editor.getScrollLeft()
|
||||
top = Math.max(0, Math.min(editor.getHeight() - height, top))
|
||||
left = Math.max(0, Math.min(editor.getWidth() - width, left))
|
||||
{top, left}
|
||||
|
||||
getRenderedRowRange: ->
|
||||
{editor, lineOverdrawMargin} = @props
|
||||
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
|
||||
renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin)
|
||||
renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin)
|
||||
[renderedStartRow, renderedEndRow]
|
||||
|
||||
@@ -17,14 +17,14 @@ LinesComponent = React.createClass
|
||||
|
||||
render: ->
|
||||
if @isMounted()
|
||||
{editor, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, scrollViewHeight} = @props
|
||||
{editor, selectionScreenRanges, 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()
|
||||
SelectionsComponent({editor, selectionScreenRanges, lineHeightInPixels}) if @isMounted()
|
||||
|
||||
componentWillMount: ->
|
||||
@measuredLines = new WeakSet
|
||||
@@ -36,11 +36,10 @@ LinesComponent = React.createClass
|
||||
@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', 'selectionScreenRanges', 'fontSize', 'fontFamily', 'lineHeight',
|
||||
'lineHeightInPixels', 'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically',
|
||||
'invisibles', 'visible', 'scrollViewHeight', 'mouseWheelScreenRow'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqual} = require 'underscore-plus'
|
||||
SelectionComponent = require './selection-component'
|
||||
|
||||
module.exports =
|
||||
@@ -10,34 +11,15 @@ SelectionsComponent = React.createClass
|
||||
div className: 'selections', @renderSelections()
|
||||
|
||||
renderSelections: ->
|
||||
{editor, lineHeightInPixels} = @props
|
||||
{editor, selectionScreenRanges, lineHeightInPixels} = @props
|
||||
|
||||
selectionComponents = []
|
||||
for selectionId, screenRange of @selectionRanges
|
||||
for selectionId, screenRange of selectionScreenRanges
|
||||
selectionComponents.push(SelectionComponent({key: selectionId, screenRange, editor, lineHeightInPixels}))
|
||||
selectionComponents
|
||||
|
||||
componentWillMount: ->
|
||||
@selectionRanges = {}
|
||||
|
||||
shouldComponentUpdate: ->
|
||||
{editor} = @props
|
||||
oldSelectionRanges = @selectionRanges
|
||||
newSelectionRanges = {}
|
||||
@selectionRanges = newSelectionRanges
|
||||
|
||||
for selection, index in editor.getSelections()
|
||||
# Rendering artifacts occur on the lines GPU layer if we remove the last selection
|
||||
if index is 0 or (not selection.isEmpty() and editor.selectionIntersectsVisibleRowRange(selection))
|
||||
newSelectionRanges[selection.id] = selection.getScreenRange()
|
||||
|
||||
for id, range of newSelectionRanges
|
||||
if oldSelectionRanges.hasOwnProperty(id)
|
||||
return true unless range.isEqual(oldSelectionRanges[id])
|
||||
else
|
||||
return true
|
||||
|
||||
for id of oldSelectionRanges
|
||||
return true unless newSelectionRanges.hasOwnProperty(id)
|
||||
|
||||
false
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqual(newProps.selectionScreenRanges, @props.selectionScreenRanges)
|
||||
|
||||
Reference in New Issue
Block a user