diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 0fa4d9b9a..b9c469829 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -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) diff --git a/src/cursor-component.coffee b/src/cursor-component.coffee index 902be9d28..5fa7f10b2 100644 --- a/src/cursor-component.coffee +++ b/src/cursor-component.coffee @@ -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)" diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index 4d3e14deb..62c471c49 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -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() diff --git a/src/editor-component.coffee b/src/editor-component.coffee index b2958cc70..64cbc8039 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -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] diff --git a/src/editor.coffee b/src/editor.coffee index acf03251d..b676130e4 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -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: -> diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 22d9b224a..72581cd0a 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -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) diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 4e2741214..110e11fdc 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 diff --git a/src/selections-component.coffee b/src/selections-component.coffee index a32f9bdc7..0d4484421 100644 --- a/src/selections-component.coffee +++ b/src/selections-component.coffee @@ -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')