     else if decoration.isType('overlay')
       @updateOverlaysState()
 
-  updateDecorations: ->
-    if @isBatching()
-      @shouldUpdateDecorations = true
-    else
-      @lineDecorationsByScreenRow = {}
-      @lineNumberDecorationsByScreenRow = {}
-      @highlightDecorationsById = {}
-
-      visibleHighlights = {}
-      return unless 0 <= @startRow <= @endRow <= Infinity
-
-      for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
-        range = @model.getMarker(markerId).getScreenRange()
-        for decoration in decorations
-          if decoration.isType('line') or decoration.isType('line-number')
-            @addToLineDecorationCaches(decoration, range)
-          else if decoration.isType('highlight')
-            visibleHighlights[decoration.id] = @updateHighlightState(decoration)
-
-      for id of @state.content.highlights
-        unless visibleHighlights[id]
-          delete @state.content.highlights[id]
-
-      @emitDidUpdateState()
+  updateDecorations: -> @batch "shouldUpdateDecorations", ->
+    @lineDecorationsByScreenRow = {}
+    @lineNumberDecorationsByScreenRow = {}
+    @highlightDecorationsById = {}
+
+    visibleHighlights = {}
+    return unless 0 <= @startRow <= @endRow <= Infinity
+
+    for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
+      range = @model.getMarker(markerId).getScreenRange()
+      for decoration in decorations
+        if decoration.isType('line') or decoration.isType('line-number')
+          @addToLineDecorationCaches(decoration, range)
+        else if decoration.isType('highlight')
+          visibleHighlights[decoration.id] = @updateHighlightState(decoration)
+
+    for id of @state.content.highlights
+      unless visibleHighlights[id]
+        delete @state.content.highlights[id]
 
 
   removeFromLineDecorationCaches: (decoration, range) ->

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

  updateCursorState: (cursor, destroyOnly = false) ->
    delete @state.content.cursors[cursor.id]

    @updateGutterState()
    @updateLineNumbersState()

  updateFocusedState: -> @batch "shouldUpdateFocusedState", ->
    @state.focused = @focused

  updateHeightState: -> @batch "shouldUpdateHeightState", ->
    if @autoHeight
      @state.height = @contentHeight
    else
      @state.height = null

  updateVerticalScrollState: -> @batch "shouldUpdateVerticalScrollState", ->
    @state.content.scrollHeight = @scrollHeight
    @state.gutter.scrollHeight = @scrollHeight
    @state.verticalScrollbar.scrollHeight = @scrollHeight

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

  updateHorizontalScrollState: -> @batch "shouldUpdateHorizontalScrollState", ->
    @state.content.scrollWidth = @scrollWidth
    @state.horizontalScrollbar.scrollWidth = @scrollWidth

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

  updateScrollbarsState: -> @batch "shouldUpdateScrollbarsState", ->
    @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

  updateHiddenInputState: -> @batch "shouldUpdateHiddenInputState", ->
    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)

  updateContentState: -> @batch "shouldUpdateContentState", ->
    @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

  updateLinesState: -> @batch "shouldUpdateLinesState", ->
    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]

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