       top: row * @lineHeight
       decorationClasses: @lineDecorationClassesForRow(row)
 
-  updateCursorsState: ->
-    if @isBatching()
-      @shouldUpdateCursorsState = true
-    else
-      @state.content.cursors = {}
-      @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation
-
-    @emitDidUpdateState()
+  updateCursorsState: -> @batch "shouldUpdateCursorsState", ->
+    @state.content.cursors = {}
+    @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation
 
   updateCursorState: (cursor, destroyOnly = false) ->
     delete @state.content.cursors[cursor.id]

    @updateGutterState()
    @updateLineNumbersState()

  updateFocusedState: ->
    if @isBatching()
      @shouldUpdateFocusedState = true
    else
      @state.focused = @focused

    @emitDidUpdateState()

  updateHeightState: ->
    if @isBatching()
      @shouldUpdateHeightState = true
    else
      if @autoHeight
        @state.height = @contentHeight
      else
        @state.height = null

    @emitDidUpdateState()

  updateVerticalScrollState: ->
    if @isBatching()
      @shouldUpdateVerticalScrollState = true
    else
      @state.content.scrollHeight = @scrollHeight
      @state.gutter.scrollHeight = @scrollHeight
      @state.verticalScrollbar.scrollHeight = @scrollHeight

      @state.content.scrollTop = @scrollTop
      @state.gutter.scrollTop = @scrollTop
      @state.verticalScrollbar.scrollTop = @scrollTop

    @emitDidUpdateState()

  updateHorizontalScrollState: ->
    if @isBatching()
      @shouldUpdateHorizontalScrollState = true
    else
      @state.content.scrollWidth = @scrollWidth
      @state.horizontalScrollbar.scrollWidth = @scrollWidth

      @state.content.scrollLeft = @scrollLeft
      @state.horizontalScrollbar.scrollLeft = @scrollLeft

    @emitDidUpdateState()

  updateScrollbarsState: ->
    if @isBatching()
      @shouldUpdateScrollbarsState = true
    else
      @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0
      @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight
      @state.horizontalScrollbar.right = @verticalScrollbarWidth

      @state.verticalScrollbar.visible = @verticalScrollbarWidth > 0
      @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth
      @state.verticalScrollbar.bottom = @horizontalScrollbarHeight

    @emitDidUpdateState()

  updateHiddenInputState: ->
    if @isBatching()
      @shouldUpdateHiddenInputState = true
    else
      return unless lastCursor = @model.getLastCursor()

      {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange())

      if @focused
        top -= @scrollTop
        left -= @scrollLeft
        @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0)
        @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0)
      else
        @state.hiddenInput.top = 0
        @state.hiddenInput.left = 0

      @state.hiddenInput.height = height
      @state.hiddenInput.width = Math.max(width, 2)

    @emitDidUpdateState()

  updateContentState: ->
    if @isBatching()
      @shouldUpdateContentState = true
    else
      @state.content.scrollWidth = @scrollWidth
      @state.content.scrollLeft = @scrollLeft
      @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide
      @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor
      @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null

    @emitDidUpdateState()

  updateLinesState: ->
    if @isBatching()
      @shouldUpdateLinesState = true
    else
      return unless @startRow? and @endRow? and @lineHeight?

      visibleLineIds = {}
      row = @startRow
      while row < @endRow
        line = @model.tokenizedLineForScreenRow(row)
        unless line?
          throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}")

        visibleLineIds[line.id] = true
        if @state.content.lines.hasOwnProperty(line.id)
          @updateLineState(row, line)
        else
          @buildLineState(row, line)
        row++

      if @mouseWheelScreenRow?
        if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow)
          visibleLineIds[preservedLine.id] = true

      for id, line of @state.content.lines
        unless visibleLineIds.hasOwnProperty(id)
          delete @state.content.lines[id]

    @emitDidUpdateState()

  updateLineState: (row, line) ->
    lineState = @state.content.lines[line.id]

    @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth?
    @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight?

  # Private: Determines whether {TextEditorPresenter} is currently batching changes.
  # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them.
  isBatching: ->
    @updating == false

  # Private: Executes `fn` if `isBatching()` is false, otherwise sets `@[flagName]` to `true` for later processing. In either cases, it calls `emitDidUpdateState`.
  # * `flagName` {String} name of a property of this presenter
  # * `fn` {Function} to call when not batching.
  batch: (flagName, fn) ->
    if @isBatching()
      @[flagName] = true
    else
      fn.apply(this)

    @emitDidUpdateState()

  # Public: Gets this presenter's state, updating it just in time before returning from this function.
  # Returns a state {Object}, useful for rendering to screen.
  getState: ->
    @updating = true

