   # Returns a {Number}.
   getMarkerCount: -> @markers.getMarkerCount()
 
+  destroyMarker: (id) ->
+    @getMarker(id)?.destroy()
+
+  ###
+  Section: History
+  ###
+
+  # Public: Undo the last operation. If a transaction is in progress, aborts it.
+  undo: -> @history.undo()
+
+  # Public: Redo the last operation
+  redo: -> @history.redo()
+
+  # Public: Batch multiple operations as a single undo/redo step.
+  #
+  # Any group of operations that are logically grouped from the perspective of
+  # undoing and redoing should be performed in a transaction. If you want to
+  # abort the transaction, call {::abortTransaction} to terminate the function's
+  # execution and revert any changes performed up to the abortion.
+  #
+  # * `fn` A {Function} to call inside the transaction.
+  transact: (fn) -> @history.transact(fn)
+
+  # Public: Start an open-ended transaction.
+  #
+  # Call {::commitTransaction} or {::abortTransaction} to terminate the
+  # transaction. If you nest calls to transactions, only the outermost
+  # transaction is considered. You must match every begin with a matching
+  # commit, but a single call to abort will cancel all nested transactions.
+  beginTransaction: -> @history.beginTransaction()
+
+  # Public: Commit an open-ended transaction started with {::beginTransaction}
+  # and push it to the undo stack.
+  #
+  # If transactions are nested, only the outermost commit takes effect.
+  commitTransaction: -> @history.commitTransaction()
+
+  # Public: Abort an open transaction, undoing any operations performed so far
+  # within the transaction.
+  abortTransaction: -> @history.abortTransaction()
+
+  # Public: Clear the undo stack.
+  clearUndoStack: -> @history.clearUndoStack()
+
+  ###
+  Section: Search And Replace
+  ###
+
+  # Public: Scan regular expression matches in the entire buffer, calling the
+  # given iterator function on each match.
+  #
+  # If you're programmatically modifying the results, you may want to try
+  # {::backwardsScan} to avoid tripping over your own changes.
+  #
+  # * `regex` A {RegExp} to search for.
+  # * `iterator` A {Function} that's called on each match with an {Object}
+  #   containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
+  scan: (regex, iterator) ->
+    @scanInRange regex, @getRange(), (result) =>
+      result.lineText = @lineForRow(result.range.start.row)
+      result.lineTextOffset = 0
+      iterator(result)
+
+  # Public: Scan regular expression matches in the entire buffer in reverse
+  # order, calling the given iterator function on each match.
+  #
+  # * `regex` A {RegExp} to search for.
+  # * `iterator` A {Function} that's called on each match with an {Object}
+  #   containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
+  backwardsScan: (regex, iterator) ->
+    @backwardsScanInRange regex, @getRange(), (result) =>
+      result.lineText = @lineForRow(result.range.start.row)
+      result.lineTextOffset = 0
+      iterator(result)
+
+  # Public: Scan regular expression matches in a given range , calling the given
+  # iterator function on each match.
+  #
+  # * `regex` A {RegExp} to search for.
+  # * `range` A {Range} in which to search.
+  # * `iterator` A {Function} that's called on each match with an {Object}
+  #   containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
+  scanInRange: (regex, range, iterator, reverse=false) ->
+    range = @clipRange(range)
+    global = regex.global
+    flags = "gm"
+    flags += "i" if regex.ignoreCase
+    regex = new RegExp(regex.source, flags)
+
+    startIndex = @characterIndexForPosition(range.start)
+    endIndex = @characterIndexForPosition(range.end)
+
+    matches = @matchesInCharacterRange(regex, startIndex, endIndex)
+    lengthDelta = 0
+
+    keepLooping = null
+    replacementText = null
+    stop = -> keepLooping = false
+    replace = (text) -> replacementText = text
+
+    matches.reverse() if reverse
+    for match in matches
+      matchLength = match[0].length
+      matchStartIndex = match.index
+      matchEndIndex = matchStartIndex + matchLength
+
+      startPosition = @positionForCharacterIndex(matchStartIndex + lengthDelta)
+      endPosition = @positionForCharacterIndex(matchEndIndex + lengthDelta)
+      range = new Range(startPosition, endPosition)
+      keepLooping = true
+      replacementText = null
+      matchText = match[0]
+      iterator({ match, matchText, range, stop, replace })
+
+      if replacementText?
+        @setTextInRange(range, replacementText)
+        lengthDelta += replacementText.length - matchLength unless reverse
+
+      break unless global and keepLooping
+
+  # Public: Scan regular expression matches in a given range in reverse order,
+  # calling the given iterator function on each match.
+  #
+  # * `regex` A {RegExp} to search for.
+  # * `range` A {Range} in which to search.
+  # * `iterator` A {Function} that's called on each match with an {Object}
+  #   containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
+  backwardsScanInRange: (regex, range, iterator) ->
+    @scanInRange regex, range, iterator, true
+
+  # Public: Replace all regular expression matches in the entire buffer.
+  #
+  # * `regex` A {RegExp} representing the matches to be replaced.
+  # * `replacementText` A {String} representing the text to replace each match.
+  #
+  # Returns a {Number} representing the number of replacements made.
+  replace: (regex, replacementText) ->
+    doSave = !@isModified()
+    replacements = 0
+
+    @transact =>
+      @scan regex, ({matchText, replace}) ->
+        replace(matchText.replace(regex, replacementText))
+        replacements++
+
+    @save() if doSave
+
+    replacements
+
+  # Identifies if a character sequence is within a certain range.
+  #
+  # * `regex` The {RegExp} to match.
+  # * `startIndex` A {Number} representing the starting character offset.
+  # * `endIndex` A {Number} representing the ending character offset.
+  #
+  # Returns an {Array} of matches for the given regex.
+  matchesInCharacterRange: (regex, startIndex, endIndex) ->
+    text = @getText()
+    matches = []
+
+    regex.lastIndex = startIndex
+    while match = regex.exec(text)
+      matchLength = match[0].length
+      matchStartIndex = match.index
+      matchEndIndex = matchStartIndex + matchLength
+
+      if matchEndIndex > endIndex
+        regex.lastIndex = 0
+        if matchStartIndex < endIndex and submatch = regex.exec(text[matchStartIndex...endIndex])
+          submatch.index = matchStartIndex
+          matches.push submatch
+        break
+
+      matchEndIndex++ if matchLength is 0
+      regex.lastIndex = matchEndIndex
+      matches.push match
+
+    matches
+
+  ###
+  Section: Buffer Range Data
+  ###
+
+  # Public: Get the range spanning from `[0, 0]` to {::getEndPosition}.
+  #
+  # Returns a {Range}.
+  getRange: ->
+    new Range(@getFirstPosition(), @getEndPosition())
+
+  # Public: Get the number of lines in the buffer.
+  #
+  # Returns a {Number}.
+  getLineCount: ->
+    @lines.length
+
+  # Public: Get the last 0-indexed row in the buffer.
+  #
+  # Returns a {Number}.
+  getLastRow: ->
+    @getLineCount() - 1
+
+  # Public: Get the first position in the buffer, which is always `[0, 0]`.
+  #
+  # Returns a {Point}.
+  getFirstPosition: ->
+    new Point(0, 0)
+
+  # Public: Get the maximal position in the buffer, where new text would be
+  # appended.
+  #
+  # Returns a {Point}.
+  getEndPosition: ->
+    lastRow = @getLastRow()
+    new Point(lastRow, @lineLengthForRow(lastRow))
+
+  # Public: Get the length of the buffer in characters.
+  #
+  # Returns a {Number}.
+  getMaxCharacterIndex: ->
+    @offsetIndex.totalTo(Infinity, 'rows').characters
+
+  # Public: Get the range for the given row
+  #
+  # * `row` A {Number} representing a 0-indexed row.
+  # * `includeNewline` A {Boolean} indicating whether or not to include the
+  #   newline, which results in a range that extends to the start
+  #   of the next line.
+  #
+  # Returns a {Range}.
+  rangeForRow: (row, includeNewline) ->
+    # Handle deprecated options hash
+    if typeof includeNewline is 'object'
+      Grim.deprecate("The second param is no longer an object, it's a boolean argument named `includeNewline`.")
+      {includeNewline} = includeNewline
+
+    if includeNewline and row < @getLastRow()
+      new Range(new Point(row, 0), new Point(row + 1, 0))
+    else
+      new Range(new Point(row, 0), new Point(row, @lineLengthForRow(row)))
+
+  # Public: Convert a position in the buffer in row/column coordinates to an
+  # absolute character offset, inclusive of line ending characters.
+  #
+  # The position is clipped prior to translating.
+  #
+  # * `position` A {Point}.
+  #
+  # Returns a {Number}.
+  characterIndexForPosition: (position) ->
+    {row, column} = @clipPosition(Point.fromObject(position))
+
+    if row < 0 or row > @getLastRow() or column < 0 or column > @lineLengthForRow(row)
+      throw new Error("Position #{position} is invalid")
+
+    {characters} = @offsetIndex.totalTo(row, 'rows')
+    characters + column
+
+  # Public: Convert an absolute character offset, inclusive of newlines, to a
+  # position in the buffer in row/column coordinates.
+  #
+  # The offset is clipped prior to translating.
+  #
+  # * `offset` A {Number}.
+  #
+  # Returns a {Point}.
+  positionForCharacterIndex: (offset) ->
+    offset = Math.max(0, offset)
+    offset = Math.min(@getMaxCharacterIndex(), offset)
+
+    {rows, characters} = @offsetIndex.totalTo(offset, 'characters')
+    if rows > @getLastRow()
+      @getEndPosition()
+    else
+      new Point(rows, offset - characters)
+
+  # Public: Clip the given range so it starts and ends at valid positions.
+  #
+  # For example, the position `[1, 100]` is out of bounds if the line at row 1 is
+  # only 10 characters long, and it would be clipped to `(1, 10)`.
+  #
+  # * `range` A {Range} or range-compatible {Array} to clip.
+  #
+  # Returns the given {Range} if it is already in bounds, or a new clipped
+  # {Range} if the given range is out-of-bounds.
+  clipRange: (range) ->
+    range = Range.fromObject(range)
+    start = @clipPosition(range.start)
+    end = @clipPosition(range.end)
+    if range.start.isEqual(start) and range.end.isEqual(end)
+      range
+    else
+      new Range(start, end)
+
+  # Public: Clip the given point so it is at a valid position in the buffer.
+  #
+  # For example, the position (1, 100) is out of bounds if the line at row 1 is
+  # only 10 characters long, and it would be clipped to (1, 10)
+  #
+  # * `position` A {Point} or point-compatible {Array}.
+  #
+  # Returns a new {Point} if the given position is invalid, otherwise returns
+  # the given position.
+  clipPosition: (position) ->
+    position = Point.fromObject(position)
+    {row, column} = position
+    if row < 0
+      @getFirstPosition()
+    else if row > @getLastRow()
+      @getEndPosition()
+    else
+      column = Math.min(Math.max(column, 0), @lineLengthForRow(row))
+      if column is position.column
+        position
+      else
+        new Point(row, column)
+
+
+  ###
+  Section: Buffer Operations
+  ###
+
+  # Public: Save the buffer.
+  save: ->
+    @saveAs(@getPath())
+
+  # Public: Save the buffer at a specific path.
+  #
+  # * `filePath` The path to save at.
+  saveAs: (filePath) ->
+    unless filePath then throw new Error("Can't save buffer with no file path")
+
+    @emit 'will-be-saved', this
+    @emitter.emit 'will-save', {path: filePath}
+    @setPath(filePath)
+    @file.write(@getText())
+    @cachedDiskContents = @getText()
+    @conflict = false
+    @emitModifiedStatusChanged(false)
+    @emit 'saved', this
+    @emitter.emit 'did-save', {path: filePath}
+
+  # Public: Reload the buffer's contents from disk.
+  #
+  # Sets the buffer's content to the cached disk contents
+  reload: ->
+    @emit 'will-reload'
+    @emitter.emit 'will-reload'
+    @setTextViaDiff(@cachedDiskContents)
+    @emitModifiedStatusChanged(false)
+    @emit 'reloaded'
+    @emitter.emit 'did-reload'
+
+  # Rereads the contents of the file, and stores them in the cache.
+  updateCachedDiskContentsSync: ->
+    @cachedDiskContents = @file?.readSync() ? ""
+
+  # Rereads the contents of the file, and stores them in the cache.
+  updateCachedDiskContents: ->
+    Q(@file?.read() ? "").then (contents) =>
+      @cachedDiskContents = contents
+
+  ###
+  Section: Private Utility Methods
+  ###
+
   markerCreated: (marker) ->
     @emit 'marker-created', marker
     @emitter.emit 'did-create-marker', marker
+
+  loadSync: ->
+    @updateCachedDiskContentsSync()
+    @finishLoading()
+
+  load: ->
+    @updateCachedDiskContents().then => @finishLoading()
+
+  finishLoading: ->
+    if @isAlive()
+      @loaded = true
+      if @useSerializedText and @digestWhenLastPersisted is @file?.getDigest()
+        @emitModifiedStatusChanged(true)
+      else
+        @reload()
+      @clearUndoStack()
+    this
+
+  destroy: ->
+    unless @destroyed
+      @cancelStoppedChangingTimeout()
+      @fileSubscriptions?.dispose()
+      @unsubscribe()
+      @destroyed = true
+      @emit 'destroyed'
+      @emitter.emit 'did-destroy'
+
+  isAlive: -> not @destroyed
+
+  isDestroyed: -> @destroyed
+
+  isRetained: -> @refcount > 0
+
+  retain: ->
+    @refcount++
+    this
+
+  release: ->
+    @refcount--
+    @destroy() unless @isRetained()
+    this
+
+  subscribeToFile: ->
+    @fileSubscriptions?.dispose()
+    @fileSubscriptions = new CompositeDisposable
+
+    @fileSubscriptions.add @file.onDidChange =>
+      @conflict = true if @isModified()
+      previousContents = @cachedDiskContents
+
+      # Synchrounously update the disk contents because the {File} has already cached them. If the
+      # contents updated asynchrounously multiple `conlict` events could trigger for the same disk
+      # contents.
+      @updateCachedDiskContentsSync()
+      return if previousContents == @cachedDiskContents
+
+      if @conflict
+        @emit "contents-conflicted"
+        @emitter.emit 'did-conflict'
+      else
+        @reload()
+
+    @fileSubscriptions.add @file.onDidDelete =>
+      modified = @getText() != @cachedDiskContents
+      @wasModifiedBeforeRemove = modified
+      if modified
+        @updateCachedDiskContents()
+      else
+        @destroy()
+
+    @fileSubscriptions.add @file.onDidRename =>
+      @emit "path-changed", this
+      @emitter.emit 'did-change-path', @getPath()
+
+  # Identifies if the buffer belongs to multiple editors.
+  #
+  # For example, if the {EditorView} was split.
+  #
+  # Returns a {Boolean}.
+  hasMultipleEditors: -> @refcount > 1
+
+  cancelStoppedChangingTimeout: ->
+    clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
+
+  scheduleModifiedEvents: ->
+    @cancelStoppedChangingTimeout()
+    stoppedChangingCallback = =>
+      @stoppedChangingTimeout = null
+      modifiedStatus = @isModified()
+      @emit 'contents-modified', modifiedStatus
+      @emitter.emit 'did-stop-changing'
+      @emitModifiedStatusChanged(modifiedStatus)
+    @stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
+
+  emitModifiedStatusChanged: (modifiedStatus) ->
+    return if modifiedStatus is @previousModifiedStatus
+    @previousModifiedStatus = modifiedStatus
+    @emit 'modified-status-changed', modifiedStatus
+    @emitter.emit 'did-change-modified', modifiedStatus
+
+  # Deprecate
+  usesSoftTabs: ->
+    Grim.deprecate("Use Editor::usesSoftTabs instead. TextBuffer doesn't have enough context to determine this.")
+    for row in [0..@getLastRow()]
+      if match = @lineForRow(row).match(/^\s/)
+        return match[0][0] != '\t'
+    undefined
+
+  # Deprecated: Call {::setTextInRange} instead.
+  change: (oldRange, newText, options={}) ->
+    Grim.deprecate("Use TextBuffer::setTextInRange instead.")
+    @setTextInRange(oldRange, newText, options.normalizeLineEndings)
+
+  # Deprecated: Use {::getEndPosition} instead
+  getEofPosition: ->
+    Grim.deprecate("Use TextBuffer::getEndPosition instead.")
+    @getEndPosition()
+
+  logLines: (start=0, end=@getLastRow())->
+    for row in [start..end]
+      line = @lineForRow(row)
+      console.log row, line, line.length

  refcount: 0
  fileSubscriptions: null

  # Public: Create a new buffer with the given params.
  #
  # * `params` A {String} of text or an {Object} with the following keys:
  #   * `load` A {Boolean}, `true` to asynchronously load the buffer from disk
  #     after initialization.
  #   * `text` The initial {String} text of the buffer.

        text += @lineEndingForRow(row)
      text

  # Public: Get the text of all lines in the buffer, without their line endings.
  #
  # Returns an {Array} of {String}s.
  getLines: ->
    @lines.slice()

  # Public: Get the text of the last line of the buffer, without its line
  # ending.
  #
  # Returns a {String}.
  getLastLine: ->
    @lineForRow(@getLastRow())

  # Public: Get the text of the line at the given row, without its line ending.
  #
  # * `row` A {Number} representing a 0-indexed row.
  #
  # Returns a {String}.
  lineForRow: (row) ->
    @lines[row]

  # Public: Get the line ending for the given 0-indexed row.
  #
  # * `row` A {Number} indicating the row.
  #
  # Returns a {String}. The returned newline is represented as a literal string:
  # `'\n'`, `'\r'`, `'\r\n'`, or `''` for the last line of the buffer, which
  # doesn't end in a newline.
  lineEndingForRow: (row) ->
    @lineEndings[row]

  # Public: Get the length of the line for the given 0-indexed row, without its
  # line ending.
  #
  # * `row` A {Number} indicating the row.
  #
  # Returns a {Number}.
  lineLengthForRow: (row) ->
    @lines[row].length

  # Public: Determine if the given row contains only whitespace.
  #
