From 1bce6263241ea64028c2c47e3c7aeb0fbab19161 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 11:09:29 +0900 Subject: [PATCH 01/15] 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. --- spec/editor-component-spec.coffee | 9 ++-- src/cursor-component.coffee | 4 +- src/cursors-component.coffee | 18 ++++--- src/editor-component.coffee | 87 ++++++++++++++++++++----------- src/lines-component.coffee | 11 ++-- src/selections-component.coffee | 28 ++-------- 6 files changed, 84 insertions(+), 73 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 0fa4d9b9a..def910f35 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -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 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..8f81bc647 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 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() diff --git a/src/editor-component.coffee b/src/editor-component.coffee index b2958cc70..a86098068 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -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] diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 4e2741214..cc90ab149 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -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 diff --git a/src/selections-component.coffee b/src/selections-component.coffee index a32f9bdc7..80744755a 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' +{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) From 3052fe3f3b63eb2302d54f857908b49f0cbbe747 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 11:33:16 +0900 Subject: [PATCH 02/15] Update cursors when the line height in pixels changes --- spec/editor-component-spec.coffee | 6 ++++++ src/cursors-component.coffee | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index def910f35..29ae73fdf 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -384,6 +384,12 @@ 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]) + cursorNode = node.querySelector('.cursor') + component.setLineHeight(2) + expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)" + describe "selection rendering", -> [scrollViewNode, scrollViewClientLeft] = [] diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index 8f81bc647..e762adef3 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -33,8 +33,8 @@ CursorsComponent = React.createClass @stopBlinkingCursors() shouldComponentUpdate: (newProps, newState) -> - not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft') or - not newState.blinkOff is @state.blinkOff + not newState.blinkOff is @state.blinkOff or + not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels') componentWillUpdate: (newProps) -> @pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges) From f467e3eed4d7d61d79424118cb8be3692d313890 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 12:35:07 +0900 Subject: [PATCH 03/15] Move decision to measure lineHeight/charWidths to EditorComponent This prevents the double-update of the lines component when changing the font-size, line-height and font-family. We detect the update of these values in the root component and trigger a measurement. If the measurement determines that the pixel values have changed, *then* we update the lines. --- src/editor-component.coffee | 29 +++++++++++++++++++++++++---- src/lines-component.coffee | 20 +++----------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index a86098068..75cb2031b 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 + measureLineHeightInPixelsAndCharWidthWhenShown: false render: -> {focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state @@ -52,6 +53,7 @@ EditorComponent = React.createClass scrollTop = editor.getScrollTop() scrollLeft = editor.getScrollLeft() lineHeightInPixels = editor.getLineHeightInPixels() + defaultCharWidth = editor.getDefaultCharWidth() scrollViewHeight = editor.getHeight() horizontalScrollbarHeight = editor.getHorizontalScrollbarHeight() verticalScrollbarWidth = editor.getVerticalScrollbarWidth() @@ -86,7 +88,7 @@ EditorComponent = React.createClass fontSize, fontFamily, lineHeightInPixels } LinesComponent { - ref: 'lines', editor, fontSize, fontFamily, lineHeight, lineHeightInPixels, + ref: 'lines', editor, lineHeightInPixels, defaultCharWidth, showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically, selectionScreenRanges, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles, visible, scrollViewHeight @@ -161,11 +163,15 @@ EditorComponent = React.createClass componentWillUpdate: -> @props.parentView.trigger 'cursor:moved' if @cursorsMoved - componentDidUpdate: -> + componentDidUpdate: (prevProps, prevState) -> @pendingChanges.length = 0 - @cursorsMoved = false @refreshingScrollbars = false @measureScrollbars() if @measuringScrollbars + @measureLineHeightInPixelsAndCharWidthIfNeeded(prevState) + unless isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') + @refs.lines.clearScopedCharWidths() + @refs.lines.measureCharactersInNewLines() + @pauseOverflowChangedEvents() @props.parentView.trigger 'editor:display-updated' @@ -566,6 +572,21 @@ EditorComponent = React.createClass clientWidth = scrollViewNode.clientWidth editor.setWidth(clientWidth) if clientWidth > 0 + measureLineHeightInPixelsAndCharWidthIfNeeded: (prevState) -> + unless isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') + if @state.visible + @measureLineHeightInPixelsAndCharWidth() + else + @measureLineHeightInPixelsAndCharWidthWhenShown = true + return + + if @measureLineHeightInPixelsAndCharWidthWhenShown and @state.visible and not prevState.visible + @measureLineHeightInPixelsAndCharWidth() + + measureLineHeightInPixelsAndCharWidth: -> + @measureLineHeightInPixelsAndCharWidthWhenShown = false + @refs.lines.measureLineHeightInPixelsAndCharWidth() + measureScrollbars: -> @measuringScrollbars = false diff --git a/src/lines-component.coffee b/src/lines-component.coffee index cc90ab149..b7b07cf62 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -13,8 +13,6 @@ module.exports = LinesComponent = React.createClass displayName: 'LinesComponent' - measureWhenShown: false - render: -> if @isMounted() {editor, selectionScreenRanges, scrollTop, scrollLeft, scrollHeight, scrollWidth, lineHeightInPixels, scrollViewHeight} = @props @@ -37,9 +35,9 @@ LinesComponent = React.createClass shouldComponentUpdate: (newProps) -> return true unless isEqualForProperties(newProps, @props, - 'renderedRowRange', 'selectionScreenRanges', '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 @@ -52,11 +50,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: -> @@ -204,16 +200,6 @@ 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 node = @getDOMNode() From 8f98f2368b4f78ce69c97cf84aad36eb7fc7431b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 12:38:26 +0900 Subject: [PATCH 04/15] Base gutter updates on defaultCharWidth instead of fontSize/Family --- src/editor-component.coffee | 2 +- src/gutter-component.coffee | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 75cb2031b..fe1b6d0af 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -70,7 +70,7 @@ EditorComponent = React.createClass div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1, GutterComponent { ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, - scrollTop, scrollHeight, lineHeight, lineHeightInPixels, fontSize, fontFamily, + scrollTop, scrollHeight, lineHeight, lineHeightInPixels, defaultCharWidth, @pendingChanges, onWidthChanged: @onGutterWidthChanged, mouseWheelScreenRow } diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 22d9b224a..3e8ec8048 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -34,7 +34,7 @@ GutterComponent = React.createClass # visible row range. shouldComponentUpdate: (newProps) -> return true unless isEqualForProperties(newProps, @props, - 'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'fontSize', + 'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'defaultCharWidth', 'mouseWheelScreenRow' ) @@ -49,7 +49,7 @@ GutterComponent = React.createClass @updateDummyLineNumber() @removeLineNumberNodes() - @measureWidth() unless @lastMeasuredWidth? and isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'fontSize', 'fontFamily') + @measureWidth() unless @lastMeasuredWidth? and isEqualForProperties(oldProps, @props, 'maxLineNumberDigits', 'defaultCharWidth') @clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels @updateLineNumbers() From 77f78d0a1126c63b348e420361bd32864183ee69 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 12:41:22 +0900 Subject: [PATCH 05/15] Update cursors component if the defaultCharWidth changes --- src/cursors-component.coffee | 2 +- src/editor-component.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index e762adef3..62c471c49 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -34,7 +34,7 @@ CursorsComponent = React.createClass shouldComponentUpdate: (newProps, newState) -> not newState.blinkOff is @state.blinkOff or - not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels') + not isEqualForProperties(newProps, @props, 'cursorScreenRanges', 'scrollTop', 'scrollLeft', 'lineHeightInPixels', 'defaultCharWidth') componentWillUpdate: (newProps) -> @pauseCursorBlinking() if @props.cursorScreenRanges and not isEqual(newProps.cursorScreenRanges, @props.cursorScreenRanges) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index fe1b6d0af..d46c0e769 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -85,7 +85,7 @@ EditorComponent = React.createClass CursorsComponent { editor, scrollTop, scrollLeft, cursorScreenRanges, cursorBlinkPeriod, cursorBlinkResumeDelay, - fontSize, fontFamily, lineHeightInPixels + lineHeightInPixels, defaultCharWidth } LinesComponent { ref: 'lines', editor, lineHeightInPixels, defaultCharWidth, From 1cc5ef3479ede44da4e793cd6f28c8aa9fff0a4b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 12:48:19 +0900 Subject: [PATCH 06/15] Update selections when the line height changes --- spec/editor-component-spec.coffee | 6 ++++++ src/selections-component.coffee | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 29ae73fdf..061b4277a 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -456,6 +456,12 @@ 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() + 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/selections-component.coffee b/src/selections-component.coffee index 80744755a..9a7122b9c 100644 --- a/src/selections-component.coffee +++ b/src/selections-component.coffee @@ -1,6 +1,6 @@ React = require 'react-atom-fork' {div} = require 'reactionary-atom-fork' -{isEqual} = require 'underscore-plus' +{isEqualForProperties} = require 'underscore-plus' SelectionComponent = require './selection-component' module.exports = @@ -22,4 +22,4 @@ SelectionsComponent = React.createClass @selectionRanges = {} shouldComponentUpdate: (newProps) -> - not isEqual(newProps.selectionScreenRanges, @props.selectionScreenRanges) + not isEqualForProperties(newProps, @props, 'selectionScreenRanges', 'lineHeightInPixels') From 03463da72928cdb3dbe6aa77be1c09bdcabe3d1b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 17:48:40 +0900 Subject: [PATCH 07/15] Allow Editor::batchUpdates calls to be nested --- src/editor.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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: -> From 4fd07a4cf3b4f3684b2bd99486260f8f76bb55ca Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 17:50:25 +0900 Subject: [PATCH 08/15] Subscribe to scroll view DOM events in ::listenForDOMEvents --- src/editor-component.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index d46c0e769..3a17ecbb3 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -148,10 +148,6 @@ EditorComponent = React.createClass @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() @requestUpdate() @@ -245,6 +241,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 From 3aefa53b33eeea60c1496ae8054fc292655116e2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 17:52:10 +0900 Subject: [PATCH 09/15] Batch together line and character width measurement after font changes This ensures we only perform a single update with the most up-to-date information about line height, default character width, and specific character widths. If this update causes more lines to be drawn we may measure again, but not necessarily. --- spec/editor-component-spec.coffee | 7 +++-- src/editor-component.coffee | 48 +++++++++++++++++-------------- src/lines-component.coffee | 11 +++---- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 061b4277a..c2f923c61 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 @@ -386,8 +386,9 @@ describe "EditorComponent", -> it "updates cursor positions when the line height changes", -> editor.setCursorBufferPosition([1, 10]) - cursorNode = node.querySelector('.cursor') component.setLineHeight(2) + cursorNode = node.querySelector('.cursor') + expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)" expect(cursorNode.style['-webkit-transform']).toBe "translate3d(#{10 * editor.getDefaultCharWidth()}px, #{editor.getLineHeightInPixels()}px, 0px)" describe "selection rendering", -> diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 3a17ecbb3..1660a0f5d 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -35,7 +35,7 @@ EditorComponent = React.createClass scrollViewMeasurementRequested: false overflowChangedEventsPaused: false overflowChangedWhilePaused: false - measureLineHeightInPixelsAndCharWidthWhenShown: false + measureLineHeightAndDefaultCharWidthWhenShown: false render: -> {focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state @@ -140,17 +140,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) - @measureScrollView() + editor.setVisible(true) - @requestUpdate() + editor.batchUpdates => + @measureLineHeightAndDefaultCharWidth() + @measureScrollView() + @measureScrollbars() componentWillUnmount: -> @unsubscribe() @@ -163,11 +167,7 @@ EditorComponent = React.createClass @pendingChanges.length = 0 @refreshingScrollbars = false @measureScrollbars() if @measuringScrollbars - @measureLineHeightInPixelsAndCharWidthIfNeeded(prevState) - unless isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') - @refs.lines.clearScopedCharWidths() - @refs.lines.measureCharactersInNewLines() - + @measureLineHeightAndCharWidthsIfNeeded(prevState) @pauseOverflowChangedEvents() @props.parentView.trigger 'editor:display-updated' @@ -573,20 +573,24 @@ EditorComponent = React.createClass clientWidth = scrollViewNode.clientWidth editor.setWidth(clientWidth) if clientWidth > 0 - measureLineHeightInPixelsAndCharWidthIfNeeded: (prevState) -> - unless isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') - if @state.visible - @measureLineHeightInPixelsAndCharWidth() - else - @measureLineHeightInPixelsAndCharWidthWhenShown = true - return + measureLineHeightAndCharWidthsIfNeeded: (prevState) -> + if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') + @props.editor.batchUpdates => + if @state.visible + @measureLineHeightAndDefaultCharWidth() + else + @measureLineHeightAndDefaultCharWidthWhenShown = true - if @measureLineHeightInPixelsAndCharWidthWhenShown and @state.visible and not prevState.visible - @measureLineHeightInPixelsAndCharWidth() + unless isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') + @refs.lines.clearScopedCharWidths() + @refs.lines.measureCharactersInNewLines() - measureLineHeightInPixelsAndCharWidth: -> - @measureLineHeightInPixelsAndCharWidthWhenShown = false - @refs.lines.measureLineHeightInPixelsAndCharWidth() + else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible + @measureLineHeightAndDefaultCharWidth() + + measureLineHeightAndDefaultCharWidth: -> + @measureLineHeightAndDefaultCharWidthWhenShown = false + @refs.lines.measureLineHeightAndDefaultCharWidth() measureScrollbars: -> @measuringScrollbars = false diff --git a/src/lines-component.coffee b/src/lines-component.coffee index b7b07cf62..9b8aa2cc5 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -30,9 +30,6 @@ LinesComponent = React.createClass @screenRowsByLineId = {} @lineIdsByScreenRow = {} - componentDidMount: -> - @measureLineHeightInPixelsAndCharWidth() - shouldComponentUpdate: (newProps) -> return true unless isEqualForProperties(newProps, @props, 'renderedRowRange', 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth', @@ -200,8 +197,7 @@ LinesComponent = React.createClass lineNodeForScreenRow: (screenRow) -> @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] - measureLineHeightInPixelsAndCharWidth: -> - @measureWhenShown = false + measureLineHeightAndDefaultCharWidth: -> node = @getDOMNode() node.appendChild(DummyLineNode) lineHeightInPixels = DummyLineNode.getBoundingClientRect().height @@ -209,8 +205,9 @@ LinesComponent = React.createClass node.removeChild(DummyLineNode) {editor} = @props - editor.setLineHeightInPixels(lineHeightInPixels) - editor.setDefaultCharWidth(charWidth) + editor.batchUpdates -> + editor.setLineHeightInPixels(lineHeightInPixels) + editor.setDefaultCharWidth(charWidth) measureCharactersInNewLines: -> [visibleStartRow, visibleEndRow] = @props.renderedRowRange From ec65def5d34fb6b5e9564219a5c857bc620cc74b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 17:52:31 +0900 Subject: [PATCH 10/15] Add spec for cursor position updates when the font size changes --- spec/editor-component-spec.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index c2f923c61..da9f5fa5f 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -389,6 +389,11 @@ describe "EditorComponent", -> 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)" describe "selection rendering", -> From 1c177aa2068db25de69e333bff6fb4fa52646967 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 17:55:14 +0900 Subject: [PATCH 11/15] Add spec for cursor position updates when the font family changes --- spec/editor-component-spec.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index da9f5fa5f..1a1d2daa0 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -396,6 +396,14 @@ describe "EditorComponent", -> 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] = [] From 3134bfda95521bdde6b09197bc3443b014211c14 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 2 Jun 2014 18:37:21 +0900 Subject: [PATCH 12/15] Update selections when the font size or font family change --- spec/editor-component-spec.coffee | 14 ++++++++++++++ src/lines-component.coffee | 4 ++-- src/selections-component.coffee | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index 1a1d2daa0..b9c469829 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -476,6 +476,20 @@ describe "EditorComponent", -> 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/lines-component.coffee b/src/lines-component.coffee index 9b8aa2cc5..216129ac3 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -15,14 +15,14 @@ LinesComponent = React.createClass render: -> if @isMounted() - {editor, selectionScreenRanges, 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, selectionScreenRanges, lineHeightInPixels}) if @isMounted() + SelectionsComponent({editor, selectionScreenRanges, lineHeightInPixels, defaultCharWidth}) if @isMounted() componentWillMount: -> @measuredLines = new WeakSet diff --git a/src/selections-component.coffee b/src/selections-component.coffee index 9a7122b9c..0d4484421 100644 --- a/src/selections-component.coffee +++ b/src/selections-component.coffee @@ -22,4 +22,4 @@ SelectionsComponent = React.createClass @selectionRanges = {} shouldComponentUpdate: (newProps) -> - not isEqualForProperties(newProps, @props, 'selectionScreenRanges', 'lineHeightInPixels') + not isEqualForProperties(newProps, @props, 'selectionScreenRanges', 'lineHeightInPixels', 'defaultCharWidth') From be0877327ec602c75e2dd05ab74e552520b86dac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 3 Jun 2014 18:18:53 +0900 Subject: [PATCH 13/15] Kill comment --- src/gutter-component.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 3e8ec8048..dda3a1b90 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -162,7 +162,6 @@ GutterComponent = React.createClass measureWidth: -> lineNumberNode = @refs.lineNumbers.getDOMNode().firstChild - # return unless lineNumberNode? width = lineNumberNode.offsetWidth if width isnt @lastMeasuredWidth From 9511c952afe4f7c225bb1bee30fbccc7d7cd7509 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 3 Jun 2014 18:19:11 +0900 Subject: [PATCH 14/15] :lipstick: --- src/editor-component.coffee | 14 ++++++++++---- src/lines-component.coffee | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 1660a0f5d..7dfc3b830 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -575,15 +575,18 @@ EditorComponent = React.createClass measureLineHeightAndCharWidthsIfNeeded: (prevState) -> if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily') - @props.editor.batchUpdates => + {editor} = @props + + editor.batchUpdates => + oldDefaultCharWidth = editor.getDefaultCharWidth() + if @state.visible @measureLineHeightAndDefaultCharWidth() else @measureLineHeightAndDefaultCharWidthWhenShown = true - unless isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily') - @refs.lines.clearScopedCharWidths() - @refs.lines.measureCharactersInNewLines() + unless oldDefaultCharWidth is editor.getDefaultCharWidth() + @remeasureCharacterWidths() else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible @measureLineHeightAndDefaultCharWidth() @@ -592,6 +595,9 @@ EditorComponent = React.createClass @measureLineHeightAndDefaultCharWidthWhenShown = false @refs.lines.measureLineHeightAndDefaultCharWidth() + remeasureCharacterWidths: -> + @refs.lines.remeasureCharacterWidths() + measureScrollbars: -> @measuringScrollbars = false diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 216129ac3..110e11fdc 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -209,6 +209,10 @@ LinesComponent = React.createClass editor.setLineHeightInPixels(lineHeightInPixels) editor.setDefaultCharWidth(charWidth) + remeasureCharacterWidths: -> + @clearScopedCharWidths() + @measureCharactersInNewLines() + measureCharactersInNewLines: -> [visibleStartRow, visibleEndRow] = @props.renderedRowRange node = @getDOMNode() From b47f6265c71d8df981b1a89b09694e116f76299d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 3 Jun 2014 18:25:25 +0900 Subject: [PATCH 15/15] Move gutter width measurement into EditorComponent --- src/editor-component.coffee | 14 ++++++++------ src/gutter-component.coffee | 12 +----------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 7dfc3b830..64cbc8039 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -69,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, defaultCharWidth, - @pendingChanges, onWidthChanged: @onGutterWidthChanged, mouseWheelScreenRow + ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop, + scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow } div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown, @@ -506,9 +505,6 @@ EditorComponent = React.createClass onCursorsMoved: -> @cursorsMoved = true - onGutterWidthChanged: (@gutterWidth) -> - @requestUpdate() - selectToMousePositionUntilMouseUp: (event) -> {editor} = @props dragging = false @@ -587,6 +583,7 @@ EditorComponent = React.createClass unless oldDefaultCharWidth is editor.getDefaultCharWidth() @remeasureCharacterWidths() + @measureGutter() else if @measureLineHeightAndDefaultCharWidthWhenShown and @state.visible and not prevState.visible @measureLineHeightAndDefaultCharWidth() @@ -598,6 +595,11 @@ EditorComponent = React.createClass remeasureCharacterWidths: -> @refs.lines.remeasureCharacterWidths() + measureGutter: -> + oldGutterWidth = @gutterWidth + @gutterWidth = @refs.gutter.getDOMNode().offsetWidth + @requestUpdate() if @gutterWidth isnt oldGutterWidth + measureScrollbars: -> @measuringScrollbars = false diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index dda3a1b90..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', 'defaultCharWidth', - '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', 'defaultCharWidth') @clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels @updateLineNumbers() @@ -159,10 +156,3 @@ GutterComponent = React.createClass lineNumberNodeForScreenRow: (screenRow) -> @lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]] - - measureWidth: -> - lineNumberNode = @refs.lineNumbers.getDOMNode().firstChild - - width = lineNumberNode.offsetWidth - if width isnt @lastMeasuredWidth - @props.onWidthChanged(@lastMeasuredWidth = width)