 
     @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

      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

  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

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

  resumeOverflowChangedEvents: ->
    if @overflowChangedWhilePaused
      @overflowChangedWhilePaused = false
      @requestScrollViewMeasurement()

  resumeOverflowChangedEventsAfterDelay: null

  consolidateSelections: (e) ->
    e.abortKeyBinding() unless @props.editor.consolidateSelections()

  lineNodeForScreenRow: (screenRow) -> @refs.lines.lineNodeForScreenRow(screenRow)

  lineNumberNodeForScreenRow: (screenRow) -> @refs.gutter.lineNumberNodeForScreenRow(screenRow)

  hide: ->
    @setState(visible: false)

                  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]
