Merge pull request #2481 from atom/ns-react-clean-up-measurement

Fix cursor and selection positioning when changing line height, font size, and font family
This commit is contained in:
Nathan Sobo
2014-06-03 19:15:40 +09:00
8 changed files with 192 additions and 128 deletions

View File

@@ -109,12 +109,12 @@ describe "EditorComponent", ->
it "updates the top position of lines when the font family changes", ->
# Can't find a font that changes the line height, but we think one might exist
linesComponent = component.refs.lines
spyOn(linesComponent, 'measureLineHeightInPixelsAndCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
spyOn(linesComponent, 'measureLineHeightAndDefaultCharWidth').andCallFake -> editor.setLineHeightInPixels(10)
initialLineHeightInPixels = editor.getLineHeightInPixels()
component.setFontFamily('sans-serif')
expect(linesComponent.measureLineHeightInPixelsAndCharWidth).toHaveBeenCalled()
expect(linesComponent.measureLineHeightAndDefaultCharWidth).toHaveBeenCalled()
newLineHeightInPixels = editor.getLineHeightInPixels()
expect(newLineHeightInPixels).not.toBe initialLineHeightInPixels
expect(component.lineNodeForScreenRow(1).offsetTop).toBe 1 * newLineHeightInPixels
@@ -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
@@ -383,6 +384,26 @@ describe "EditorComponent", ->
expect(cursorNodes.length).toBe 1
expect(cursorNodes[0].style['-webkit-transform']).toBe "translate3d(#{8 * charWidth}px, #{6 * lineHeightInPixels}px, 0px)"
it "updates cursor positions when the line height changes", ->
editor.setCursorBufferPosition([1, 10])
component.setLineHeight(2)
cursorNode = node.querySelector('.cursor')
expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)"
it "updates cursor positions when the font size changes", ->
editor.setCursorBufferPosition([1, 10])
component.setFontSize(10)
cursorNode = node.querySelector('.cursor')
expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)"
it "updates cursor positions when the font family changes", ->
editor.setCursorBufferPosition([1, 10])
component.setFontFamily('sans-serif')
cursorNode = node.querySelector('.cursor')
{left} = editor.pixelPositionForScreenPosition([1, 10])
expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{left}px, #{editor.getLineHeightInPixels()}px, 0px)"
describe "selection rendering", ->
[scrollViewNode, scrollViewClientLeft] = []
@@ -449,6 +470,26 @@ describe "EditorComponent", ->
expect(node.querySelectorAll('.selection').length).toBe 1
it "updates selections when the line height changes", ->
editor.setSelectedBufferRange([[1, 6], [1, 10]])
component.setLineHeight(2)
selectionNode = node.querySelector('.region')
expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels()
it "updates selections when the font size changes", ->
editor.setSelectedBufferRange([[1, 6], [1, 10]])
component.setFontSize(10)
selectionNode = node.querySelector('.region')
expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels()
expect(selectionNode.offsetLeft).toBe 6 * editor.getDefaultCharWidth()
it "updates selections when the font family changes", ->
editor.setSelectedBufferRange([[1, 6], [1, 10]])
component.setFontFamily('sans-serif')
selectionNode = node.querySelector('.region')
expect(selectionNode.offsetTop).toBe editor.getLineHeightInPixels()
expect(selectionNode.offsetLeft).toBe editor.pixelPositionForScreenPosition([1, 6]).left
describe "hidden input field", ->
it "renders the hidden input field at the position of the last cursor if the cursor is on screen and the editor is focused", ->
editor.setVerticalScrollMargin(0)

View File

@@ -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)"

View File

@@ -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 newState.blinkOff is @state.blinkOff or
not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels', 'defaultCharWidth')
componentWillUpdate: (newProps) ->
@pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges)
startBlinkingCursors: ->
@toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()

View File

@@ -1,6 +1,6 @@
React = require 'react-atom-fork'
{div, span} = require 'reactionary-atom-fork'
{debounce, defaults} = require 'underscore-plus'
{debounce, defaults, isEqualForProperties} = require 'underscore-plus'
scrollbarStyle = require 'scrollbar-style'
GutterComponent = require './gutter-component'
@@ -35,6 +35,7 @@ EditorComponent = React.createClass
scrollViewMeasurementRequested: false
overflowChangedEventsPaused: false
overflowChangedWhilePaused: false
measureLineHeightAndDefaultCharWidthWhenShown: false
render: ->
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state
@@ -45,11 +46,14 @@ 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()
scrollLeft = editor.getScrollLeft()
lineHeightInPixels = editor.getLineHeightInPixels()
defaultCharWidth = editor.getDefaultCharWidth()
scrollViewHeight = editor.getHeight()
horizontalScrollbarHeight = editor.getHorizontalScrollbarHeight()
verticalScrollbarWidth = editor.getVerticalScrollbarWidth()
@@ -65,9 +69,8 @@ EditorComponent = React.createClass
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
GutterComponent {
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits,
scrollTop, scrollHeight, lineHeight, lineHeightInPixels, fontSize, fontFamily,
@pendingChanges, onWidthChanged: @onGutterWidthChanged, mouseWheelScreenRow
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop,
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
}
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
@@ -79,11 +82,14 @@ EditorComponent = React.createClass
onFocus: @onInputFocused
onBlur: @onInputBlurred
CursorsComponent({editor, scrollTop, scrollLeft, @cursorsMoved, @selectionAdded, cursorBlinkPeriod, cursorBlinkResumeDelay})
CursorsComponent {
editor, scrollTop, scrollLeft, cursorScreenRanges, cursorBlinkPeriod, cursorBlinkResumeDelay,
lineHeightInPixels, defaultCharWidth
}
LinesComponent {
ref: 'lines', editor, fontSize, fontFamily, lineHeight, lineHeightInPixels,
ref: 'lines', editor, lineHeightInPixels, defaultCharWidth,
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically,
@selectionChanged, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
selectionScreenRanges, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
visible, scrollViewHeight
}
@@ -133,21 +139,21 @@ EditorComponent = React.createClass
@observeConfig()
componentDidMount: ->
{editor} = @props
@observeEditor()
@listenForDOMEvents()
@listenForCommands()
@measureScrollbars()
@subscribe atom.themes, 'stylesheet-added stylsheet-removed', @onStylesheetsChanged
@subscribe scrollbarStyle.changes, @refreshScrollbars
@props.editor.setVisible(true)
scrollViewNode = @refs.scrollView.getDOMNode()
scrollViewNode.addEventListener 'overflowchanged', @onScrollViewOverflowChanged
scrollViewNode.addEventListener 'scroll', @onScrollViewScroll
window.addEventListener('resize', @onWindowResize)
@measureScrollView()
editor.setVisible(true)
@requestUpdate()
editor.batchUpdates =>
@measureLineHeightAndDefaultCharWidth()
@measureScrollView()
@measureScrollbars()
componentWillUnmount: ->
@unsubscribe()
@@ -156,16 +162,64 @@ EditorComponent = React.createClass
componentWillUpdate: ->
@props.parentView.trigger 'cursor:moved' if @cursorsMoved
componentDidUpdate: ->
componentDidUpdate: (prevProps, prevState) ->
@pendingChanges.length = 0
@cursorsMoved = false
@selectionChanged = false
@selectionAdded = false
@refreshingScrollbars = false
@measureScrollbars() if @measuringScrollbars
@measureLineHeightAndCharWidthsIfNeeded(prevState)
@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
@@ -186,6 +240,11 @@ EditorComponent = React.createClass
node.addEventListener 'mousewheel', @onMouseWheel
node.addEventListener 'focus', @onFocus # For some reason, React's built in focus events seem to bubble
scrollViewNode = @refs.scrollView.getDOMNode()
scrollViewNode.addEventListener 'overflowchanged', @onScrollViewOverflowChanged
scrollViewNode.addEventListener 'scroll', @onScrollViewScroll
window.addEventListener('resize', @onWindowResize)
listenForCommands: ->
{parentView, editor, mini} = @props
@@ -446,9 +505,6 @@ EditorComponent = React.createClass
onCursorsMoved: ->
@cursorsMoved = true
onGutterWidthChanged: (@gutterWidth) ->
@requestUpdate()
selectToMousePositionUntilMouseUp: (event) ->
{editor} = @props
dragging = false
@@ -513,6 +569,37 @@ EditorComponent = React.createClass
clientWidth = scrollViewNode.clientWidth
editor.setWidth(clientWidth) if clientWidth > 0
measureLineHeightAndCharWidthsIfNeeded: (prevState) ->
if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily')
{editor} = @props
editor.batchUpdates =>
oldDefaultCharWidth = editor.getDefaultCharWidth()
if @state.visible
@measureLineHeightAndDefaultCharWidth()
else
@measureLineHeightAndDefaultCharWidthWhenShown = true
unless oldDefaultCharWidth is editor.getDefaultCharWidth()
@remeasureCharacterWidths()
@measureGutter()
else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible
@measureLineHeightAndDefaultCharWidth()
measureLineHeightAndDefaultCharWidth: ->
@measureLineHeightAndDefaultCharWidthWhenShown = false
@refs.lines.measureLineHeightAndDefaultCharWidth()
remeasureCharacterWidths: ->
@refs.lines.remeasureCharacterWidths()
measureGutter: ->
oldGutterWidth = @gutterWidth
@gutterWidth = @refs.gutter.getDOMNode().offsetWidth
@requestUpdate() if @gutterWidth isnt oldGutterWidth
measureScrollbars: ->
@measuringScrollbars = false
@@ -549,12 +636,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 +753,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]

View File

@@ -144,6 +144,7 @@ class Editor extends Model
cursors: null
selections: null
suppressSelectionMerging: false
updateBatchDepth: 0
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
@@ -1842,9 +1843,11 @@ class Editor extends Model
abortTransaction: -> @buffer.abortTransaction()
batchUpdates: (fn) ->
@emit 'batched-updates-started'
@emit 'batched-updates-started' if @updateBatchDepth is 0
@updateBatchDepth++
result = fn()
@emit 'batched-updates-ended'
@updateBatchDepth--
@emit 'batched-updates-ended' if @updateBatchDepth is 0
result
inspect: ->

View File

@@ -10,7 +10,6 @@ GutterComponent = React.createClass
displayName: 'GutterComponent'
mixins: [SubscriberMixin]
lastMeasuredWidth: null
dummyLineNumberNode: null
render: ->
@@ -34,8 +33,7 @@ GutterComponent = React.createClass
# visible row range.
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'fontSize',
'mouseWheelScreenRow'
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow'
)
{renderedRowRange, pendingChanges} = newProps
@@ -49,7 +47,6 @@ GutterComponent = React.createClass
@updateDummyLineNumber()
@removeLineNumberNodes()
@measureWidth() unless @lastMeasuredWidth? and isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'fontSize', 'fontFamily')
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
@updateLineNumbers()
@@ -159,11 +156,3 @@ GutterComponent = React.createClass
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)

View File

@@ -13,18 +13,16 @@ module.exports =
LinesComponent = React.createClass
displayName: 'LinesComponent'
measureWhenShown: false
render: ->
if @isMounted()
{editor, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, scrollViewHeight} = @props
{editor, selectionScreenRanges, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, defaultCharWidth, 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, defaultCharWidth}) if @isMounted()
componentWillMount: ->
@measuredLines = new WeakSet
@@ -32,15 +30,11 @@ LinesComponent = React.createClass
@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', 'mouseWheelScreenRow'
'renderedRowRange', 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth',
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
'scrollViewHeight', 'mouseWheelScreenRow'
)
{renderedRowRange, pendingChanges} = newProps
@@ -53,11 +47,9 @@ LinesComponent = React.createClass
componentDidUpdate: (prevProps) ->
{visible, scrollingVertically} = @props
@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() if visible and not scrollingVertically
clearScreenRowCaches: ->
@@ -205,18 +197,7 @@ LinesComponent = React.createClass
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
measureLineHeightInPixelsAndCharWidthIfNeeded: (prevProps) ->
{visible} = @props
unless isEqualForProperties(prevProps, @props, 'fontSize', 'fontFamily', 'lineHeight')
if visible
@measureLineHeightInPixelsAndCharWidth()
else
@measureWhenShown = true
@measureLineHeightInPixelsAndCharWidth() if visible and not prevProps.visible and @measureWhenShown
measureLineHeightInPixelsAndCharWidth: ->
@measureWhenShown = false
measureLineHeightAndDefaultCharWidth: ->
node = @getDOMNode()
node.appendChild(DummyLineNode)
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
@@ -224,8 +205,13 @@ LinesComponent = React.createClass
node.removeChild(DummyLineNode)
{editor} = @props
editor.setLineHeightInPixels(lineHeightInPixels)
editor.setDefaultCharWidth(charWidth)
editor.batchUpdates ->
editor.setLineHeightInPixels(lineHeightInPixels)
editor.setDefaultCharWidth(charWidth)
remeasureCharacterWidths: ->
@clearScopedCharWidths()
@measureCharactersInNewLines()
measureCharactersInNewLines: ->
[visibleStartRow, visibleEndRow] = @props.renderedRowRange

View File

@@ -1,5 +1,6 @@
React = require 'react-atom-fork'
{div} = require 'reactionary-atom-fork'
{isEqualForProperties} = 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 isEqualForProperties(newProps, @props, 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth')