Merge pull request #2477 from atom/ns-react-remove-scroll-view-component

Merge EditorScrollViewComponent into EditorComponent
This commit is contained in:
Corey Johnson
2014-06-02 08:02:49 -07:00
3 changed files with 294 additions and 333 deletions

View File

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

View File

@@ -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'
@@ -104,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
@@ -132,11 +140,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 +163,7 @@ EditorComponent = React.createClass
@selectionAdded = false
@refreshingScrollbars = false
@measureScrollbars() if @measuringScrollbars
@pauseOverflowChangedEvents()
@props.parentView.trigger 'editor:display-updated'
observeEditor: ->
@@ -284,49 +300,8 @@ 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.scrollView.focus()
@refs.input.focus()
onInputFocused: ->
@setState(focused: true)
@@ -382,49 +357,51 @@ EditorComponent = React.createClass
@pendingVerticalScrollDelta = 0
@pendingHorizontalScrollDelta = 0
clearMouseWheelScreenRow: ->
if @mouseWheelScreenRow?
@mouseWheelScreenRow = null
@requestUpdate()
onScrollViewOverflowChanged: ->
if @overflowChangedEventsPaused
@overflowChangedWhilePaused = true
else
@requestScrollViewMeasurement()
clearMouseWheelScreenRowAfterDelay: null # created lazily
onWindowResize: ->
@requestScrollViewMeasurement()
screenRowForNode: (node) ->
while node isnt document
if screenRow = node.dataset.screenRow
return parseInt(screenRow)
node = node.parentNode
null
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)
onStylesheetsChanged: (stylesheet) ->
@refreshScrollbars() if @containsScrollbarSelector(stylesheet)
containsScrollbarSelector: (stylesheet) ->
for rule in stylesheet.cssRules
if rule.selectorText?.indexOf('scrollbar') > -1
return true
false
refreshScrollbars: ->
# Believe it or not, proper handling of changes to scrollbar styles requires
# three DOM updates.
# Scrollbar style changes won't apply to scrollbars that are already
# visible, so first we need to hide scrollbars so we can redisplay them and
# force Chromium to apply updates.
@refreshingScrollbars = true
@requestUpdate()
# Next, we display only the scrollbar corner so we can measure the new
# scrollbar dimensions. The ::measuringScrollbars property will be set back
# to false after the scrollbars are measured.
@measuringScrollbars = true
@requestUpdate()
# Finally, we restore the scrollbars based on the newly-measured dimensions
# if the editor's content and dimensions require them to be visible.
@requestUpdate()
onBatchedUpdatesStarted: ->
@batchingUpdates = true
@@ -456,15 +433,15 @@ EditorComponent = React.createClass
onScrollTopChanged: ->
@scrollingVertically = true
@requestUpdate()
@stopScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100)
@stopScrollingAfterDelay()
@onStoppedScrollingAfterDelay ?= debounce(@onStoppedScrolling, 100)
@onStoppedScrollingAfterDelay()
onStoppedScrolling: ->
@scrollingVertically = false
@mouseWheelScreenRow = null
@requestUpdate()
stopScrollingAfterDelay: null # created lazily
onStoppedScrollingAfterDelay: null # created lazily
onCursorsMoved: ->
@cursorsMoved = true
@@ -472,22 +449,145 @@ EditorComponent = React.createClass
onGutterWidthChanged: (@gutterWidth) ->
@requestUpdate()
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))
requestScrollViewMeasurement: ->
return if @measurementPending
@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
# 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
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)
containsScrollbarSelector: (stylesheet) ->
for rule in stylesheet.cssRules
if rule.selectorText?.indexOf('scrollbar') > -1
return true
false
refreshScrollbars: ->
# Believe it or not, proper handling of changes to scrollbar styles requires
# three DOM updates.
# Scrollbar style changes won't apply to scrollbars that are already
# visible, so first we need to hide scrollbars so we can redisplay them and
# force Chromium to apply updates.
@refreshingScrollbars = true
@requestUpdate()
# Next, we display only the scrollbar corner so we can measure the new
# scrollbar dimensions. The ::measuringScrollbars property will be set back
# to false after the scrollbars are measured.
@measuringScrollbars = true
@requestUpdate()
# Finally, we restore the scrollbars based on the newly-measured dimensions
# if the editor's content and dimensions require them to be visible.
@requestUpdate()
requestUpdate: ->
if @batchingUpdates
@updateRequested = true
else
@forceUpdate()
measureHeightAndWidth: ->
@refs.scrollView.measureHeightAndWidth()
pauseOverflowChangedEvents: ->
@overflowChangedEventsPaused = true
@resumeOverflowChangedEventsAfterDelay ?= debounce(@resumeOverflowChangedEvents, 500)
@resumeOverflowChangedEventsAfterDelay()
resumeOverflowChangedEvents: ->
if @overflowChangedWhilePaused
@overflowChangedWhilePaused = false
@requestScrollViewMeasurement()
resumeOverflowChangedEventsAfterDelay: null
clearMouseWheelScreenRow: ->
if @mouseWheelScreenRow?
@mouseWheelScreenRow = null
@requestUpdate()
clearMouseWheelScreenRowAfterDelay: null # created lazily
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)
screenRowForNode: (node) ->
while node isnt document
if screenRow = node.dataset.screenRow
return parseInt(screenRow)
node = node.parentNode
null
hide: ->
@setState(visible: false)
@@ -528,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]

View File

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