From d31669c67f02bfe064848af7892983ebd504a02d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 1 Jun 2014 15:24:59 +0900 Subject: [PATCH 1/2] Merge EditorScrollViewComponent into Editor I don't think that this component was really carrying its weight. Its render function basically passed through directly to other components that updated between renders, but didn't contain any content on its own that actually changed after the first render. React components seem to carry overhead, so I want every component we use to count. Also, I'm considering circumventing some of React's standard update logic for performance reasons, and making the structure more shallow will help with that. --- spec/editor-component-spec.coffee | 56 +++---- src/editor-component.coffee | 192 ++++++++++++++++++++-- src/editor-scroll-view-component.coffee | 203 ------------------------ 3 files changed, 206 insertions(+), 245 deletions(-) delete mode 100644 src/editor-scroll-view-component.coffee diff --git a/spec/editor-component-spec.coffee b/spec/editor-component-spec.coffee index e01fbb853..0fa4d9b9a 100644 --- a/spec/editor-component-spec.coffee +++ b/spec/editor-component-spec.coffee @@ -47,7 +47,7 @@ describe "EditorComponent", -> node.style.height = editor.getLineCount() * lineHeightInPixels + 'px' node.style.width = '1000px' - component.measureHeightAndWidth() + component.measureScrollView() afterEach -> contentNode.style.width = '' @@ -55,7 +55,7 @@ describe "EditorComponent", -> describe "line rendering", -> it "renders the currently-visible lines plus the overdraw margin", -> node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() linesNode = node.querySelector('.lines') expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" @@ -108,7 +108,7 @@ 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.scrollView.refs.lines + linesComponent = component.refs.lines spyOn(linesComponent, 'measureLineHeightInPixelsAndCharWidth').andCallFake -> editor.setLineHeightInPixels(10) initialLineHeightInPixels = editor.getLineHeightInPixels() @@ -122,7 +122,7 @@ describe "EditorComponent", -> it "renders the .lines div at the full height of the editor if there aren't enough lines to scroll vertically", -> editor.setText('') node.style.height = '300px' - component.measureHeightAndWidth() + component.measureScrollView() linesNode = node.querySelector('.lines') expect(linesNode.offsetHeight).toBe 300 @@ -166,7 +166,7 @@ describe "EditorComponent", -> editor.setText "a line that wraps " editor.setSoftWrap(true) node.style.width = 15 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() it "doesn't show end of line invisibles at the end of wrapped lines", -> expect(component.lineNodeForScreenRow(0).textContent).toBe "a line that " @@ -230,7 +230,7 @@ describe "EditorComponent", -> describe "gutter rendering", -> it "renders the currently-visible line numbers", -> node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(node.querySelectorAll('.line-number').length).toBe 6 + 2 + 1 # line overdraw margin below + dummy line number expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" @@ -270,7 +270,7 @@ describe "EditorComponent", -> editor.setSoftWrap(true) node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 30 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(node.querySelectorAll('.line-number').length).toBe 6 + lineOverdrawMargin + 1 # 1 dummy line node expect(component.lineNumberNodeForScreenRow(0).textContent).toBe "#{nbsp}1" @@ -309,7 +309,7 @@ describe "EditorComponent", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 20 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() cursorNodes = node.querySelectorAll('.cursor') expect(cursorNodes.length).toBe 1 @@ -457,7 +457,7 @@ describe "EditorComponent", -> inputNode = node.querySelector('.hidden-input') node.style.height = 5 * lineHeightInPixels + 'px' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(editor.getCursorScreenPosition()).toEqual [0, 0] editor.setScrollTop(3 * lineHeightInPixels) @@ -503,7 +503,7 @@ describe "EditorComponent", -> it "moves the cursor to the nearest screen position", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() editor.setScrollTop(3.5 * lineHeightInPixels) editor.setScrollLeft(2 * charWidth) @@ -616,7 +616,7 @@ describe "EditorComponent", -> describe "scrolling", -> it "updates the vertical scrollbar when the scrollTop is changed in the model", -> node.style.height = 4.5 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.scrollTop).toBe 0 @@ -625,7 +625,7 @@ describe "EditorComponent", -> it "updates the horizontal scrollbar and the x transform of the lines based on the scrollLeft of the model", -> node.style.width = 30 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() linesNode = node.querySelector('.lines') expect(linesNode.style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)" @@ -637,7 +637,7 @@ describe "EditorComponent", -> it "updates the scrollLeft of the model when the scrollLeft of the horizontal scrollbar changes", -> node.style.width = 30 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(editor.getScrollLeft()).toBe 0 horizontalScrollbarNode.scrollLeft = 100 @@ -648,7 +648,7 @@ describe "EditorComponent", -> it "does not obscure the last line with the horizontal scrollbar", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() editor.setScrollBottom(editor.getScrollHeight()) lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow()) bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom @@ -657,7 +657,7 @@ describe "EditorComponent", -> # Scroll so there's no space below the last line when the horizontal scrollbar disappears node.style.width = 100 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom bottomOfEditor = node.getBoundingClientRect().bottom expect(bottomOfLastLine).toBe bottomOfEditor @@ -665,7 +665,7 @@ describe "EditorComponent", -> it "does not obscure the last character of the longest line with the vertical scrollbar", -> node.style.height = 7 * lineHeightInPixels + 'px' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() editor.setScrollLeft(Infinity) @@ -679,19 +679,19 @@ describe "EditorComponent", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = '1000px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.display).toBe '' expect(horizontalScrollbarNode.style.display).toBe 'none' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.display).toBe '' expect(horizontalScrollbarNode.style.display).toBe '' node.style.height = 20 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.display).toBe 'none' expect(horizontalScrollbarNode.style.display).toBe '' @@ -699,7 +699,7 @@ describe "EditorComponent", -> it "makes the dummy scrollbar divs only as tall/wide as the actual scrollbars", -> node.style.height = 4 * lineHeightInPixels + 'px' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() atom.themes.applyStylesheet "test", """ ::-webkit-scrollbar { @@ -722,19 +722,19 @@ describe "EditorComponent", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = '1000px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.bottom).toBe '' expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px' expect(scrollbarCornerNode.style.display).toBe 'none' node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px' expect(horizontalScrollbarNode.style.right).toBe verticalScrollbarNode.offsetWidth + 'px' expect(scrollbarCornerNode.style.display).toBe '' node.style.height = 20 * lineHeightInPixels + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.style.bottom).toBe horizontalScrollbarNode.offsetHeight + 'px' expect(horizontalScrollbarNode.style.right).toBe '' expect(scrollbarCornerNode.style.display).toBe 'none' @@ -742,7 +742,7 @@ describe "EditorComponent", -> it "accounts for the width of the gutter in the scrollWidth of the horizontal scrollbar", -> gutterNode = node.querySelector('.gutter') node.style.width = 10 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(horizontalScrollbarNode.scrollWidth).toBe gutterNode.offsetWidth + editor.getScrollWidth() @@ -752,7 +752,7 @@ describe "EditorComponent", -> it "updates the scrollLeft or scrollTop on mousewheel events depending on which delta is greater (x or y)", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 20 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() expect(verticalScrollbarNode.scrollTop).toBe 0 expect(horizontalScrollbarNode.scrollLeft).toBe 0 @@ -769,7 +769,7 @@ describe "EditorComponent", -> it "keeps the line on the DOM if it is scrolled off-screen", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 20 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() lineNode = node.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500) @@ -781,7 +781,7 @@ describe "EditorComponent", -> it "does not set the mouseWheelScreenRow if scrolling horizontally", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 20 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() lineNode = node.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 10, wheelDeltaY: 0) @@ -825,7 +825,7 @@ describe "EditorComponent", -> it "keeps the line number on the DOM if it is scrolled off-screen", -> node.style.height = 4.5 * lineHeightInPixels + 'px' node.style.width = 20 * charWidth + 'px' - component.measureHeightAndWidth() + component.measureScrollView() lineNumberNode = node.querySelectorAll('.line-number')[1] wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -500) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index ede23e203..526a798c5 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -4,7 +4,9 @@ React = require 'react-atom-fork' scrollbarStyle = require 'scrollbar-style' GutterComponent = require './gutter-component' -EditorScrollViewComponent = require './editor-scroll-view-component' +InputComponent = require './input-component' +CursorsComponent = require './cursors-component' +LinesComponent = require './lines-component' ScrollbarComponent = require './scrollbar-component' ScrollbarCornerComponent = require './scrollbar-corner-component' SubscriberMixin = require './subscriber-mixin' @@ -30,6 +32,9 @@ EditorComponent = React.createClass pendingHorizontalScrollDelta: 0 mouseWheelScreenRow: null mouseWheelScreenRowClearDelay: 150 + scrollViewMeasurementRequested: false + overflowChangedEventsPaused: false + overflowChangedWhilePaused: false render: -> {focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, visible} = @state @@ -50,6 +55,8 @@ EditorComponent = React.createClass verticalScrollbarWidth = editor.getVerticalScrollbarWidth() verticallyScrollable = editor.verticallyScrollable() horizontallyScrollable = editor.horizontallyScrollable() + hiddenInputStyle = @getHiddenInputPosition() + hiddenInputStyle.WebkitTransform = 'translateZ(0)' if @mouseWheelScreenRow? and not (renderedStartRow <= @mouseWheelScreenRow < renderedEndRow) mouseWheelScreenRow = @mouseWheelScreenRow @@ -63,14 +70,22 @@ EditorComponent = React.createClass @pendingChanges, onWidthChanged: @onGutterWidthChanged, mouseWheelScreenRow } - EditorScrollViewComponent { - ref: 'scrollView', editor, fontSize, fontFamily, showIndentGuide, - lineHeight, lineHeightInPixels, renderedRowRange, @pendingChanges, - scrollTop, scrollLeft, scrollHeight, scrollWidth, @scrollingVertically, - @cursorsMoved, @selectionChanged, @selectionAdded, cursorBlinkPeriod, - cursorBlinkResumeDelay, @onInputFocused, @onInputBlurred, mouseWheelScreenRow, - invisibles, visible, scrollViewHeight, focused - } + div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown, + InputComponent + ref: 'input' + className: 'hidden-input' + style: hiddenInputStyle + onInput: @onInput + onFocus: @onInputFocused + onBlur: @onInputBlurred + + CursorsComponent({editor, scrollTop, scrollLeft, @cursorsMoved, @selectionAdded, cursorBlinkPeriod, cursorBlinkResumeDelay}) + LinesComponent { + ref: 'lines', editor, fontSize, fontFamily, lineHeight, lineHeightInPixels, + showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft, @scrollingVertically, + @selectionChanged, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles, + visible, scrollViewHeight + } ScrollbarComponent ref: 'verticalScrollbar' @@ -132,11 +147,18 @@ EditorComponent = React.createClass @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() + @requestUpdate() componentWillUnmount: -> @unsubscribe() - @getDOMNode().removeEventListener 'mousewheel', @onMouseWheel + window.removeEventListener('resize', @onWindowResize) componentWillUpdate: -> @props.parentView.trigger 'cursor:moved' if @cursorsMoved @@ -148,6 +170,7 @@ EditorComponent = React.createClass @selectionAdded = false @refreshingScrollbars = false @measureScrollbars() if @measuringScrollbars + @pauseOverflowChangedEvents() @props.parentView.trigger 'editor:display-updated' observeEditor: -> @@ -326,7 +349,7 @@ EditorComponent = React.createClass @setState({showInvisibles}) onFocus: -> - @refs.scrollView.focus() + @refs.input.focus() onInputFocused: -> @setState(focused: true) @@ -389,6 +412,130 @@ EditorComponent = React.createClass clearMouseWheelScreenRowAfterDelay: null # created lazily + onScrollViewOverflowChanged: -> + if @overflowChangedEventsPaused + @overflowChangedWhilePaused = true + else + @requestScrollViewMeasurement() + + onWindowResize: -> + @requestScrollViewMeasurement() + + onScrollViewScroll: -> + console.warn "EditorScrollView scroll position changed, and it shouldn't have. If you can reproduce this, please report it." + scrollViewNode = @refs.scrollView.getDOMNode() + scrollViewNode.scrollTop = 0 + scrollViewNode.scrollLeft = 0 + + onInput: (char, replaceLastCharacter) -> + {editor} = @props + + if replaceLastCharacter + editor.transact -> + editor.selectLeft() + editor.insertText(char) + else + editor.insertText(char) + + onMouseDown: (event) -> + {editor} = @props + {detail, shiftKey, metaKey} = event + screenPosition = @screenPositionForMouseEvent(event) + + if shiftKey + editor.selectToScreenPosition(screenPosition) + else if metaKey + editor.addCursorAtScreenPosition(screenPosition) + else + editor.setCursorScreenPosition(screenPosition) + switch detail + when 2 then editor.selectWord() + when 3 then editor.selectLine() + + @selectToMousePositionUntilMouseUp(event) + + selectToMousePositionUntilMouseUp: (event) -> + {editor} = @props + dragging = false + lastMousePosition = {} + + animationLoop = => + requestAnimationFrame => + if dragging + @selectToMousePosition(lastMousePosition) + animationLoop() + + onMouseMove = (event) -> + lastMousePosition.clientX = event.clientX + lastMousePosition.clientY = event.clientY + + # Start the animation loop when the mouse moves prior to a mouseup event + unless dragging + dragging = true + animationLoop() + + # Stop dragging when cursor enters dev tools because we can't detect mouseup + onMouseUp() if event.which is 0 + + onMouseUp = -> + dragging = false + window.removeEventListener('mousemove', onMouseMove) + window.removeEventListener('mouseup', onMouseUp) + editor.finalizeSelections() + + window.addEventListener('mousemove', onMouseMove) + window.addEventListener('mouseup', onMouseUp) + + selectToMousePosition: (event) -> + @props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event)) + + screenPositionForMouseEvent: (event) -> + pixelPosition = @pixelPositionForMouseEvent(event) + @props.editor.screenPositionForPixelPosition(pixelPosition) + + pixelPositionForMouseEvent: (event) -> + {editor} = @props + {clientX, clientY} = event + + scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect() + 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} + + # Measure explicitly-styled height and width and relay them to the model. If + # these values aren't explicitly styled, we assume the editor is unconstrained + # and use the scrollHeight / scrollWidth as its height and width in + # calculations. + measureScrollView: -> + return unless @isMounted() + + {editor} = @props + editorNode = @getDOMNode() + scrollViewNode = @refs.scrollView.getDOMNode() + {position} = getComputedStyle(editorNode) + {width, height} = editorNode.style + + if position is 'absolute' or height + clientHeight = scrollViewNode.clientHeight + editor.setHeight(clientHeight) if clientHeight > 0 + + if position is 'absolute' or width + clientWidth = scrollViewNode.clientWidth + editor.setWidth(clientWidth) if clientWidth > 0 + screenRowForNode: (node) -> while node isnt document if screenRow = node.dataset.screenRow @@ -478,13 +625,30 @@ EditorComponent = React.createClass else @forceUpdate() - measureHeightAndWidth: -> - @refs.scrollView.measureHeightAndWidth() + requestScrollViewMeasurement: -> + return if @measurementPending + + @scrollViewMeasurementRequested = true + requestAnimationFrame => + @scrollViewMeasurementRequested = false + @measureScrollView() + + pauseOverflowChangedEvents: -> + @overflowChangedEventsPaused = true + @resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500) + @resumeOverflowChangedEventsAfterDelay() + + resumeOverflowChangedEvents: -> + if @overflowChangedWhilePaused + @overflowChangedWhilePaused = false + @requestScrollViewMeasurement() + + resumeOverflowChangedEventsAfterDelay: null consolidateSelections: (e) -> e.abortKeyBinding() unless @props.editor.consolidateSelections() - lineNodeForScreenRow: (screenRow) -> @refs.scrollView.lineNodeForScreenRow(screenRow) + lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow) lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow) diff --git a/src/editor-scroll-view-component.coffee b/src/editor-scroll-view-component.coffee deleted file mode 100644 index 92bc4a04e..000000000 --- a/src/editor-scroll-view-component.coffee +++ /dev/null @@ -1,203 +0,0 @@ -React = require 'react-atom-fork' -{div} = require 'reactionary-atom-fork' -{debounce} = require 'underscore-plus' - -InputComponent = require './input-component' -LinesComponent = require './lines-component' -CursorsComponent = require './cursors-component' -SelectionsComponent = require './selections-component' - -module.exports = -EditorScrollViewComponent = React.createClass - displayName: 'EditorScrollViewComponent' - - measurementPending: false - overflowChangedEventsPaused: false - overflowChangedWhilePaused: false - - render: -> - {editor, fontSize, fontFamily, lineHeight, lineHeightInPixels, showIndentGuide, invisibles, visible} = @props - {renderedRowRange, pendingChanges, scrollTop, scrollLeft, scrollHeight, scrollWidth, scrollViewHeight, scrollingVertically, mouseWheelScreenRow} = @props - {selectionChanged, selectionAdded, cursorBlinkPeriod, cursorBlinkResumeDelay, cursorsMoved, onInputFocused, onInputBlurred} = @props - - if @isMounted() - inputStyle = @getHiddenInputPosition() - inputStyle.WebkitTransform = 'translateZ(0)' - - div className: 'scroll-view', onMouseDown: @onMouseDown, - InputComponent - ref: 'input' - className: 'hidden-input' - style: inputStyle - onInput: @onInput - onFocus: onInputFocused - onBlur: onInputBlurred - - CursorsComponent({editor, scrollTop, scrollLeft, cursorsMoved, selectionAdded, cursorBlinkPeriod, cursorBlinkResumeDelay}) - LinesComponent { - ref: 'lines', editor, fontSize, fontFamily, lineHeight, lineHeightInPixels, - showIndentGuide, renderedRowRange, pendingChanges, scrollTop, scrollLeft, scrollingVertically, - selectionChanged, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles, - visible, scrollViewHeight - } - - componentDidMount: -> - node = @getDOMNode() - - node.addEventListener 'overflowchanged', @onOverflowChanged - window.addEventListener('resize', @onWindowResize) - - node.addEventListener 'scroll', -> - console.warn "EditorScrollView scroll position changed, and it shouldn't have. If you can reproduce this, please report it." - node.scrollTop = 0 - node.scrollLeft = 0 - - @measureHeightAndWidth() - - componentDidUnmount: -> - window.removeEventListener('resize', @onWindowResize) - - componentDidUpdate: -> - @pauseOverflowChangedEvents() - - onOverflowChanged: -> - if @overflowChangedEventsPaused - @overflowChangedWhilePaused = true - else - @requestMeasurement() - - onWindowResize: -> - @requestMeasurement() - - pauseOverflowChangedEvents: -> - @overflowChangedEventsPaused = true - @resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500) - @resumeOverflowChangedEventsAfterDelay() - - resumeOverflowChangedEvents: -> - if @overflowChangedWhilePaused - @overflowChangedWhilePaused = false - @requestMeasurement() - - resumeOverflowChangedEventsAfterDelay: null - - requestMeasurement: -> - return if @measurementPending - - @measurementPending = true - requestAnimationFrame => - @measurementPending = false - @measureHeightAndWidth() - - onInput: (char, replaceLastCharacter) -> - {editor} = @props - - if replaceLastCharacter - editor.transact -> - editor.selectLeft() - editor.insertText(char) - else - editor.insertText(char) - - onMouseDown: (event) -> - {editor} = @props - {detail, shiftKey, metaKey} = event - screenPosition = @screenPositionForMouseEvent(event) - - if shiftKey - editor.selectToScreenPosition(screenPosition) - else if metaKey - editor.addCursorAtScreenPosition(screenPosition) - else - editor.setCursorScreenPosition(screenPosition) - switch detail - when 2 then editor.selectWord() - when 3 then editor.selectLine() - - @selectToMousePositionUntilMouseUp(event) - - selectToMousePositionUntilMouseUp: (event) -> - {editor} = @props - dragging = false - lastMousePosition = {} - - animationLoop = => - requestAnimationFrame => - if dragging - @selectToMousePosition(lastMousePosition) - animationLoop() - - onMouseMove = (event) -> - lastMousePosition.clientX = event.clientX - lastMousePosition.clientY = event.clientY - - # Start the animation loop when the mouse moves prior to a mouseup event - unless dragging - dragging = true - animationLoop() - - # Stop dragging when cursor enters dev tools because we can't detect mouseup - onMouseUp() if event.which is 0 - - onMouseUp = -> - dragging = false - window.removeEventListener('mousemove', onMouseMove) - window.removeEventListener('mouseup', onMouseUp) - editor.finalizeSelections() - - window.addEventListener('mousemove', onMouseMove) - window.addEventListener('mouseup', onMouseUp) - - selectToMousePosition: (event) -> - @props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event)) - - screenPositionForMouseEvent: (event) -> - pixelPosition = @pixelPositionForMouseEvent(event) - @props.editor.screenPositionForPixelPosition(pixelPosition) - - pixelPositionForMouseEvent: (event) -> - {editor} = @props - {clientX, clientY} = event - - editorClientRect = @getDOMNode().getBoundingClientRect() - top = clientY - editorClientRect.top + editor.getScrollTop() - left = clientX - editorClientRect.left + editor.getScrollLeft() - {top, left} - - getHiddenInputPosition: -> - {editor, focused} = @props - 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} - - # Measure explicitly-styled height and width and relay them to the model. If - # these values aren't explicitly styled, we assume the editor is unconstrained - # and use the scrollHeight / scrollWidth as its height and width in - # calculations. - measureHeightAndWidth: -> - return unless @isMounted() - - {editor} = @props - node = @getDOMNode() - editorNode = node.parentNode - {position} = getComputedStyle(editorNode) - {width, height} = editorNode.style - - if position is 'absolute' or height - clientHeight = node.clientHeight - editor.setHeight(clientHeight) if clientHeight > 0 - - if position is 'absolute' or width - clientWidth = node.clientWidth - editor.setWidth(clientWidth) if clientWidth > 0 - - focus: -> - @refs.input.focus() - - lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow) From afe386ce40c23c606b23964b6ce8ae1114e0a2fc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 1 Jun 2014 18:31:47 +0900 Subject: [PATCH 2/2] :lipstick: EditorComponent method order --- src/editor-component.coffee | 284 ++++++++++++++++++------------------ 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/src/editor-component.coffee b/src/editor-component.coffee index 526a798c5..b2958cc70 100644 --- a/src/editor-component.coffee +++ b/src/editor-component.coffee @@ -119,13 +119,6 @@ EditorComponent = React.createClass height: horizontalScrollbarHeight width: verticalScrollbarWidth - getRenderedRowRange: -> - {editor, lineOverdrawMargin} = @props - [visibleStartRow, visibleEndRow] = editor.getVisibleRowRange() - renderedStartRow = Math.max(0, visibleStartRow - lineOverdrawMargin) - renderedEndRow = Math.min(editor.getScreenLineCount(), visibleEndRow + lineOverdrawMargin) - [renderedStartRow, renderedEndRow] - getInitialState: -> visible: true @@ -307,47 +300,6 @@ EditorComponent = React.createClass @subscribe atom.config.observe 'editor.invisibles', @setInvisibles @subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles - measureScrollbars: -> - @measuringScrollbars = false - - {editor} = @props - scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode() - width = (scrollbarCornerNode.offsetWidth - scrollbarCornerNode.clientWidth) or 15 - height = (scrollbarCornerNode.offsetHeight - scrollbarCornerNode.clientHeight) or 15 - editor.setVerticalScrollbarWidth(width) - editor.setHorizontalScrollbarHeight(height) - - setFontSize: (fontSize) -> - @setState({fontSize}) - - setLineHeight: (lineHeight) -> - @setState({lineHeight}) - - setFontFamily: (fontFamily) -> - @setState({fontFamily}) - - setShowIndentGuide: (showIndentGuide) -> - @setState({showIndentGuide}) - - # Public: Defines which characters are invisible. - # - # invisibles - An {Object} defining the invisible characters: - # :eol - The end of line invisible {String} (default: `\u00ac`). - # :space - The space invisible {String} (default: `\u00b7`). - # :tab - The tab invisible {String} (default: `\u00bb`). - # :cr - The carriage return invisible {String} (default: `\u00a4`). - setInvisibles: (invisibles={}) -> - defaults invisibles, - eol: '\u00ac' - space: '\u00b7' - tab: '\u00bb' - cr: '\u00a4' - - @setState({invisibles}) - - setShowInvisibles: (showInvisibles) -> - @setState({showInvisibles}) - onFocus: -> @refs.input.focus() @@ -405,13 +357,6 @@ EditorComponent = React.createClass @pendingVerticalScrollDelta = 0 @pendingHorizontalScrollDelta = 0 - clearMouseWheelScreenRow: -> - if @mouseWheelScreenRow? - @mouseWheelScreenRow = null - @requestUpdate() - - clearMouseWheelScreenRowAfterDelay: null # created lazily - onScrollViewOverflowChanged: -> if @overflowChangedEventsPaused @overflowChangedWhilePaused = true @@ -454,6 +399,56 @@ EditorComponent = React.createClass @selectToMousePositionUntilMouseUp(event) + onStylesheetsChanged: (stylesheet) -> + @refreshScrollbars() if @containsScrollbarSelector(stylesheet) + + onBatchedUpdatesStarted: -> + @batchingUpdates = true + + onBatchedUpdatesEnded: -> + updateRequested = @updateRequested + @updateRequested = false + @batchingUpdates = false + if updateRequested + @requestUpdate() + + onScreenLinesChanged: (change) -> + {editor} = @props + @pendingChanges.push(change) + @requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events + + onSelectionChanged: (selection) -> + {editor} = @props + if editor.selectionIntersectsVisibleRowRange(selection) + @selectionChanged = true + @requestUpdate() + + onSelectionAdded: (selection) -> + {editor} = @props + if editor.selectionIntersectsVisibleRowRange(selection) + @selectionChanged = true + @selectionAdded = true + @requestUpdate() + + onScrollTopChanged: -> + @scrollingVertically = true + @requestUpdate() + @onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100) + @onStoppedScrollingAfterDelay() + + onStoppedScrolling: -> + @scrollingVertically = false + @mouseWheelScreenRow = null + @requestUpdate() + + onStoppedScrollingAfterDelay: null # created lazily + + onCursorsMoved: -> + @cursorsMoved = true + + onGutterWidthChanged: (@gutterWidth) -> + @requestUpdate() + selectToMousePositionUntilMouseUp: (event) -> {editor} = @props dragging = false @@ -489,31 +484,13 @@ EditorComponent = React.createClass selectToMousePosition: (event) -> @props.editor.selectToScreenPosition(@screenPositionForMouseEvent(event)) - screenPositionForMouseEvent: (event) -> - pixelPosition = @pixelPositionForMouseEvent(event) - @props.editor.screenPositionForPixelPosition(pixelPosition) + requestScrollViewMeasurement: -> + return if @measurementPending - pixelPositionForMouseEvent: (event) -> - {editor} = @props - {clientX, clientY} = event - - scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect() - 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} + @scrollViewMeasurementRequested = true + requestAnimationFrame => + @scrollViewMeasurementRequested = false + @measureScrollView() # Measure explicitly-styled height and width and relay them to the model. If # these values aren't explicitly styled, we assume the editor is unconstrained @@ -536,15 +513,15 @@ EditorComponent = React.createClass clientWidth = scrollViewNode.clientWidth editor.setWidth(clientWidth) if clientWidth > 0 - screenRowForNode: (node) -> - while node isnt document - if screenRow = node.dataset.screenRow - return parseInt(screenRow) - node = node.parentNode - null + measureScrollbars: -> + @measuringScrollbars = false - onStylesheetsChanged: (stylesheet) -> - @refreshScrollbars() if @containsScrollbarSelector(stylesheet) + {editor} = @props + scrollbarCornerNode = @refs.scrollbarCorner.getDOMNode() + width = (scrollbarCornerNode.offsetWidth - scrollbarCornerNode.clientWidth) or 15 + height = (scrollbarCornerNode.offsetHeight - scrollbarCornerNode.clientHeight) or 15 + editor.setVerticalScrollbarWidth(width) + editor.setHorizontalScrollbarHeight(height) containsScrollbarSelector: (stylesheet) -> for rule in stylesheet.cssRules @@ -572,67 +549,12 @@ EditorComponent = React.createClass # if the editor's content and dimensions require them to be visible. @requestUpdate() - onBatchedUpdatesStarted: -> - @batchingUpdates = true - - onBatchedUpdatesEnded: -> - updateRequested = @updateRequested - @updateRequested = false - @batchingUpdates = false - if updateRequested - @requestUpdate() - - onScreenLinesChanged: (change) -> - {editor} = @props - @pendingChanges.push(change) - @requestUpdate() if editor.intersectsVisibleRowRange(change.start, change.end + 1) # TODO: Use closed-open intervals for change events - - onSelectionChanged: (selection) -> - {editor} = @props - if editor.selectionIntersectsVisibleRowRange(selection) - @selectionChanged = true - @requestUpdate() - - onSelectionAdded: (selection) -> - {editor} = @props - if editor.selectionIntersectsVisibleRowRange(selection) - @selectionChanged = true - @selectionAdded = true - @requestUpdate() - - onScrollTopChanged: -> - @scrollingVertically = true - @requestUpdate() - @stopScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100) - @stopScrollingAfterDelay() - - onStoppedScrolling: -> - @scrollingVertically = false - @mouseWheelScreenRow = null - @requestUpdate() - - stopScrollingAfterDelay: null # created lazily - - onCursorsMoved: -> - @cursorsMoved = true - - onGutterWidthChanged: (@gutterWidth) -> - @requestUpdate() - requestUpdate: -> if @batchingUpdates @updateRequested = true else @forceUpdate() - requestScrollViewMeasurement: -> - return if @measurementPending - - @scrollViewMeasurementRequested = true - requestAnimationFrame => - @scrollViewMeasurementRequested = false - @measureScrollView() - pauseOverflowChangedEvents: -> @overflowChangedEventsPaused = true @resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500) @@ -645,6 +567,13 @@ EditorComponent = React.createClass resumeOverflowChangedEventsAfterDelay: null + clearMouseWheelScreenRow: -> + if @mouseWheelScreenRow? + @mouseWheelScreenRow = null + @requestUpdate() + + clearMouseWheelScreenRowAfterDelay: null # created lazily + consolidateSelections: (e) -> e.abortKeyBinding() unless @props.editor.consolidateSelections() @@ -652,6 +581,13 @@ EditorComponent = React.createClass lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow) + screenRowForNode: (node) -> + while node isnt document + if screenRow = node.dataset.screenRow + return parseInt(screenRow) + node = node.parentNode + null + hide: -> @setState(visible: false) @@ -692,3 +628,67 @@ EditorComponent = React.createClass ReactPerf.printExclusive() console.log "Wasted" ReactPerf.printWasted() + + setFontSize: (fontSize) -> + @setState({fontSize}) + + setLineHeight: (lineHeight) -> + @setState({lineHeight}) + + setFontFamily: (fontFamily) -> + @setState({fontFamily}) + + setShowIndentGuide: (showIndentGuide) -> + @setState({showIndentGuide}) + + # Public: Defines which characters are invisible. + # + # invisibles - An {Object} defining the invisible characters: + # :eol - The end of line invisible {String} (default: `\u00ac`). + # :space - The space invisible {String} (default: `\u00b7`). + # :tab - The tab invisible {String} (default: `\u00bb`). + # :cr - The carriage return invisible {String} (default: `\u00a4`). + setInvisibles: (invisibles={}) -> + defaults invisibles, + eol: '\u00ac' + space: '\u00b7' + tab: '\u00bb' + cr: '\u00a4' + + @setState({invisibles}) + + setShowInvisibles: (showInvisibles) -> + @setState({showInvisibles}) + + screenPositionForMouseEvent: (event) -> + pixelPosition = @pixelPositionForMouseEvent(event) + @props.editor.screenPositionForPixelPosition(pixelPosition) + + pixelPositionForMouseEvent: (event) -> + {editor} = @props + {clientX, clientY} = event + + scrollViewClientRect = @refs.scrollView.getDOMNode().getBoundingClientRect() + 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]