Add sections to editor docs

Add sections to editor docs

--- src/editor.coffee
+++ src/editor.coffee
@@ -50,91 +50,6 @@
 #
 # **When in doubt, just default to buffer coordinates**, then experiment with
 # soft wraps and folds to ensure your code interacts with them correctly.
-#
-# ## Common Tasks
-#
-# This is a subset of methods on this class. Refer to the complete summary for
-# its full capabilities.
-#
-# ### Cursors
-# - {::setCursorBufferPosition}
-# - {::setCursorScreenPosition}
-# - {::moveCursorUp}
-# - {::moveCursorDown}
-# - {::moveCursorLeft}
-# - {::moveCursorRight}
-# - {::moveCursorToBeginningOfWord}
-# - {::moveCursorToEndOfWord}
-# - {::moveCursorToPreviousWordBoundary}
-# - {::moveCursorToNextWordBoundary}
-# - {::moveCursorToBeginningOfNextWord}
-# - {::moveCursorToBeginningOfLine}
-# - {::moveCursorToEndOfLine}
-# - {::moveCursorToFirstCharacterOfLine}
-# - {::moveCursorToTop}
-# - {::moveCursorToBottom}
-#
-# ### Selections
-# - {::getSelectedBufferRange}
-# - {::getSelectedBufferRanges}
-# - {::setSelectedBufferRange}
-# - {::setSelectedBufferRanges}
-# - {::selectUp}
-# - {::selectDown}
-# - {::selectLeft}
-# - {::selectRight}
-# - {::selectToBeginningOfWord}
-# - {::selectToEndOfWord}
-# - {::selectToPreviousWordBoundary}
-# - {::selectToNextWordBoundary}
-# - {::selectWord}
-# - {::selectToBeginningOfLine}
-# - {::selectToEndOfLine}
-# - {::selectToFirstCharacterOfLine}
-# - {::selectToTop}
-# - {::selectToBottom}
-# - {::selectAll}
-# - {::addSelectionForBufferRange}
-# - {::addSelectionAbove}
-# - {::addSelectionBelow}
-# - {::splitSelectionsIntoLines}
-#
-# ### Manipulating Text
-# - {::getText}
-# - {::getSelectedText}
-# - {::setText}
-# - {::setTextInBufferRange}
-# - {::insertText}
-# - {::insertNewline}
-# - {::insertNewlineAbove}
-# - {::insertNewlineBelow}
-# - {::backspace}
-# - {::deleteToBeginningOfWord}
-# - {::deleteToBeginningOfLine}
-# - {::delete}
-# - {::deleteToEndOfLine}
-# - {::deleteToEndOfWord}
-# - {::deleteLine}
-# - {::cutSelectedText}
-# - {::cutToEndOfLine}
-# - {::copySelectedText}
-# - {::pasteText}
-#
-# ### Undo, Redo, and Transactions
-# - {::undo}
-# - {::redo}
-# - {::transact}
-# - {::abortTransaction}
-#
-# ### Markers
-# - {::markBufferRange}
-# - {::markScreenRange}
-# - {::getMarker}
-# - {::findMarkers}
-#
-# ### Decorations
-# - {::decorateMarker}
-# - {::decorationsForScreenRowRange}
 module.exports =
 class Editor extends Model
   Serializable.includeInto(this)
@@ -244,6 +159,12 @@
     @displayBuffer.destroy()
     @languageMode.destroy()

+  # Retrieves the current {TextBuffer}.
+  getBuffer: -> @buffer
+
+  # Retrieves the current buffer's URI.
+  getUri: -> @buffer.getUri()
+
   # Create an {Editor} with its initial state based on this object
   copy: ->
     tabLength = @getTabLength()
@@ -254,6 +175,26 @@
       marker.copy(editorId: newEditor.id, preserveFolds: true)
     newEditor

+  # Controls visibility based on the given {Boolean}.
+  setVisible: (visible) -> @displayBuffer.setVisible(visible)
+
+  setMini: (mini) ->
+    if mini isnt @mini
+      @mini = mini
+      @updateInvisibles()
+
+  # Set the number of characters that can be displayed horizontally in the
+  # editor.
+  #
+  # * `editorWidthInChars` A {Number} representing the width of the {EditorView}
+  # in characters.
+  setEditorWidthInChars: (editorWidthInChars) ->
+    @displayBuffer.setEditorWidthInChars(editorWidthInChars)
+
+  ###
+  Section: File Details
+  ###
+
   # Public: Get the title the editor's title for display in other parts of the
   # UI such as the tabs.
   #
@@ -283,70 +224,495 @@
     else
       'untitled'

-  # Controls visibility based on the given {Boolean}.
-  setVisible: (visible) -> @displayBuffer.setVisible(visible)
-
-  setMini: (mini) ->
-    if mini isnt @mini
-      @mini = mini
-      @updateInvisibles()
-
-  # Set the number of characters that can be displayed horizontally in the
-  # editor.
-  #
-  # * `editorWidthInChars` A {Number} representing the width of the {EditorView}
-  # in characters.
-  setEditorWidthInChars: (editorWidthInChars) ->
-    @displayBuffer.setEditorWidthInChars(editorWidthInChars)
-
-  # Public: Sets the column at which column will soft wrap
-  getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
-
-  # Public: Returns a {Boolean} indicating whether softTabs are enabled for this
-  # editor.
-  getSoftTabs: -> @softTabs
-
-  # Public: Enable or disable soft tabs for this editor.
-  #
-  # * `softTabs` A {Boolean}
-  setSoftTabs: (@softTabs) -> @softTabs
-
-  # Public: Toggle soft tabs for this editor
-  toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs())
-
-  # Public: Get whether soft wrap is enabled for this editor.
-  getSoftWrap: -> @displayBuffer.getSoftWrap()
-
-  # Public: Enable or disable soft wrap for this editor.
-  #
-  # * `softWrap` A {Boolean}
-  setSoftWrap: (softWrap) -> @displayBuffer.setSoftWrap(softWrap)
-
-  # Public: Toggle soft wrap for this editor
-  toggleSoftWrap: -> @setSoftWrap(not @getSoftWrap())
-
-  # Public: Get the text representing a single level of indent.
-  #
-  # If soft tabs are enabled, the text is composed of N spaces, where N is the
-  # tab length. Otherwise the text is a tab character (`\t`).
+  # Public: Returns the {String} path of this editor's text buffer.
+  getPath: -> @buffer.getPath()
+
+  # Public: Saves the editor's text buffer.
+  #
+  # See {TextBuffer::save} for more details.
+  save: -> @buffer.save()
+
+  # Public: Saves the editor's text buffer as the given path.
+  #
+  # See {TextBuffer::saveAs} for more details.
+  #
+  # * `filePath` A {String} path.
+  saveAs: (filePath) -> @buffer.saveAs(filePath)
+
+  # Public: Determine whether the user should be prompted to save before closing
+  # this editor.
+  shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors()
+
+  # Public: Returns {Boolean} `true` if this editor has been modified.
+  isModified: -> @buffer.isModified()
+
+  isEmpty: -> @buffer.isEmpty()
+
+  # Copies the current file path to the native clipboard.
+  copyPathToClipboard: ->
+    if filePath = @getPath()
+      atom.clipboard.write(filePath)
+
+  ###
+  Section: Reading Text
+  ###
+
+  # Public: Returns a {String} representing the entire contents of the editor.
+  getText: -> @buffer.getText()
+
+  # Public: Get the text in the given {Range} in buffer coordinates.
+  #
+  # * `range` A {Range} or range-compatible {Array}.
   #
   # Returns a {String}.
-  getTabText: -> @buildIndentString(1)
-
-  # Public: Get the on-screen length of tab characters.
-  #
-  # Returns a {Number}.
-  getTabLength: -> @displayBuffer.getTabLength()
-
-  # Public: Set the on-screen length of tab characters.
-  setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
+  getTextInBufferRange: (range) ->
+    @buffer.getTextInRange(range)
+
+  # Public: Returns a {Number} representing the number of lines in the editor.
+  getLineCount: -> @buffer.getLineCount()
+
+  # {Delegates to: DisplayBuffer.getLineCount}
+  getScreenLineCount: -> @displayBuffer.getLineCount()
+
+  # Public: Returns a {Number} representing the last zero-indexed buffer row
+  # number of the editor.
+  getLastBufferRow: -> @buffer.getLastRow()
+
+  # {Delegates to: DisplayBuffer.getLastRow}
+  getLastScreenRow: -> @displayBuffer.getLastRow()
+
+  # Public: Returns a {String} representing the contents of the line at the
+  # given buffer row.
+  #
+  # * `row` A {Number} representing a zero-indexed buffer row.
+  lineForBufferRow: (row) -> @buffer.lineForRow(row)
+
+  # {Delegates to: DisplayBuffer.lineForRow}
+  lineForScreenRow: (row) -> @displayBuffer.lineForRow(row)
+
+  # {Delegates to: DisplayBuffer.linesForRows}
+  linesForScreenRows: (start, end) -> @displayBuffer.linesForRows(start, end)
+
+  # Public: Returns a {Number} representing the line length for the given
+  # buffer row, exclusive of its line-ending character(s).
+  #
+  # * `row` A {Number} indicating the buffer row.
+  lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row)
+
+  bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)
+
+  # {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
+  bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
+
+  # {Delegates to: DisplayBuffer.getMaxLineLength}
+  getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
+
+  # Returns the range for the given buffer row.
+  #
+  # * `row` A row {Number}.
+  # * `options` An options hash with an `includeNewline` key.
+  #
+  # Returns a {Range}.
+  bufferRangeForBufferRow: (row, {includeNewline}={}) -> @buffer.rangeForRow(row, includeNewline)
+
+  # Get the text in the given {Range}.
+  #
+  # Returns a {String}.
+  getTextInRange: (range) -> @buffer.getTextInRange(range)
+
+  # {Delegates to: TextBuffer.isRowBlank}
+  isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
+
+  # {Delegates to: TextBuffer.nextNonBlankRow}
+  nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
+
+  # {Delegates to: TextBuffer.getEndPosition}
+  getEofBufferPosition: -> @buffer.getEndPosition()
+
+  # Public: Get the {Range} of the paragraph surrounding the most recently added
+  # cursor.
+  #
+  # Returns a {Range}.
+  getCurrentParagraphBufferRange: ->
+    @getCursor().getCurrentParagraphBufferRange()
+
+
+  ###
+  Section: Mutating Text
+  ###
+
+  # Public: Replaces the entire contents of the buffer with the given {String}.
+  setText: (text) -> @buffer.setText(text)
+
+  # Public: Set the text in the given {Range} in buffer coordinates.
+  #
+  # * `range` A {Range} or range-compatible {Array}.
+  # * `text` A {String}
+  #
+  # Returns the {Range} of the newly-inserted text.
+  setTextInBufferRange: (range, text, normalizeLineEndings) -> @getBuffer().setTextInRange(range, text, normalizeLineEndings)
+
+  # Public: Mutate the text of all the selections in a single transaction.
+  #
+  # All the changes made inside the given {Function} can be reverted with a
+  # single call to {::undo}.
+  #
+  # * `fn` A {Function} that will be called once for each {Selection}. The first
+  #      argument will be a {Selection} and the second argument will be the
+  #      {Number} index of that selection.
+  mutateSelectedText: (fn) ->
+    @transact => fn(selection, index) for selection, index in @getSelections()
+
+  # Move lines intersection the most recent selection up by one row in screen
+  # coordinates.
+  moveLineUp: ->
+    selection = @getSelectedBufferRange()
+    return if selection.start.row is 0
+    lastRow = @buffer.getLastRow()
+    return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is ''
+
+    @transact =>
+      foldedRows = []
+      rows = [selection.start.row..selection.end.row]
+      if selection.start.row isnt selection.end.row and selection.end.column is 0
+        rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
+
+      # Move line around the fold that is directly above the selection
+      precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
+      precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
+      if fold = @largestFoldContainingBufferRow(precedingBufferRow)
+        insertDelta = fold.getBufferRange().getRowCount()
+      else
+        insertDelta = 1
+
+      for row in rows
+        if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
+          bufferRange = fold.getBufferRange()
+          startRow = bufferRange.start.row
+          endRow = bufferRange.end.row
+          foldedRows.push(startRow - insertDelta)
+        else
+          startRow = row
+          endRow = row
+
+        insertPosition = Point.fromObject([startRow - insertDelta])
+        endPosition = Point.min([endRow + 1], @buffer.getEndPosition())
+        lines = @buffer.getTextInRange([[startRow], endPosition])
+        if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
+          lines = "#{lines}\n"
+
+        @buffer.deleteRows(startRow, endRow)
+
+        # Make sure the inserted text doesn't go into an existing fold
+        if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
+          @unfoldBufferRow(insertPosition.row)
+          foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
+
+        @buffer.insert(insertPosition, lines)
+
+      # Restore folds that existed before the lines were moved
+      for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
+        @foldBufferRow(foldedRow)
+
+      @setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true, autoscroll: true)
+
+  # Move lines intersecting the most recent selection down by one row in screen
+  # coordinates.
+  moveLineDown: ->
+    selection = @getSelectedBufferRange()
+    lastRow = @buffer.getLastRow()
+    return if selection.end.row is lastRow
+    return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is ''
+
+    @transact =>
+      foldedRows = []
+      rows = [selection.end.row..selection.start.row]
+      if selection.start.row isnt selection.end.row and selection.end.column is 0
+        rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
+
+      # Move line around the fold that is directly below the selection
+      followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
+      followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
+      if fold = @largestFoldContainingBufferRow(followingBufferRow)
+        insertDelta = fold.getBufferRange().getRowCount()
+      else
+        insertDelta = 1
+
+      for row in rows
+        if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
+          bufferRange = fold.getBufferRange()
+          startRow = bufferRange.start.row
+          endRow = bufferRange.end.row
+          foldedRows.push(endRow + insertDelta)
+        else
+          startRow = row
+          endRow = row
+
+        if endRow + 1 is lastRow
+          endPosition = [endRow, @buffer.lineLengthForRow(endRow)]
+        else
+          endPosition = [endRow + 1]
+        lines = @buffer.getTextInRange([[startRow], endPosition])
+        @buffer.deleteRows(startRow, endRow)
+
+        insertPosition = Point.min([startRow + insertDelta], @buffer.getEndPosition())
+        if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
+          lines = "\n#{lines}"
+
+        # Make sure the inserted text doesn't go into an existing fold
+        if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
+          @unfoldBufferRow(insertPosition.row)
+          foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
+
+        @buffer.insert(insertPosition, lines)
+
+      # Restore folds that existed before the lines were moved
+      for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
+        @foldBufferRow(foldedRow)
+
+      @setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)
+
+  # Duplicate the most recent cursor's current line.
+  duplicateLines: ->
+    @transact =>
+      for selection in @getSelectionsOrderedByBufferPosition().reverse()
+        selectedBufferRange = selection.getBufferRange()
+        if selection.isEmpty()
+          {start} = selection.getScreenRange()
+          selection.selectToScreenPosition([start.row + 1, 0])
+
+        [startRow, endRow] = selection.getBufferRowRange()
+        endRow++
+
+        foldedRowRanges =
+          @outermostFoldsInBufferRowRange(startRow, endRow)
+            .map (fold) -> fold.getBufferRowRange()
+
+        rangeToDuplicate = [[startRow, 0], [endRow, 0]]
+        textToDuplicate = @getTextInBufferRange(rangeToDuplicate)
+        textToDuplicate = '\n' + textToDuplicate if endRow > @getLastBufferRow()
+        @buffer.insert([endRow, 0], textToDuplicate)
+
+        delta = endRow - startRow
+        selection.setBufferRange(selectedBufferRange.translate([delta, 0]))
+        for [foldStartRow, foldEndRow] in foldedRowRanges
+          @createFold(foldStartRow + delta, foldEndRow + delta)
+
+  # Deprecated: Use {::duplicateLines} instead.
+  duplicateLine: ->
+    deprecate("Use Editor::duplicateLines() instead")
+    @duplicateLines()
+
+  replaceSelectedText: (options={}, fn) ->
+    {selectWordIfEmpty} = options
+    @mutateSelectedText (selection) ->
+      range = selection.getBufferRange()
+      if selectWordIfEmpty and selection.isEmpty()
+        selection.selectWord()
+      text = selection.getText()
+      selection.deleteSelectedText()
+      selection.insertText(fn(text))
+      selection.setBufferRange(range)
+
+  # Public: Split multi-line selections into one selection per line.
+  #
+  # Operates on all selections. This method breaks apart all multi-line
+  # selections to create multiple single-line selections that cumulatively cover
+  # the same original area.
+  splitSelectionsIntoLines: ->
+    for selection in @getSelections()
+      range = selection.getBufferRange()
+      continue if range.isSingleLine()
+
+      selection.destroy()
+      {start, end} = range
+      @addSelectionForBufferRange([start, [start.row, Infinity]])
+      {row} = start
+      while ++row < end.row
+        @addSelectionForBufferRange([[row, 0], [row, Infinity]])
+      @addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) unless end.column is 0
+
+  # Public: For each selection, transpose the selected text.
+  #
+  # If the selection is empty, the characters preceding and following the cursor
+  # are swapped. Otherwise, the selected characters are reversed.
+  transpose: ->
+    @mutateSelectedText (selection) ->
+      if selection.isEmpty()
+        selection.selectRight()
+        text = selection.getText()
+        selection.delete()
+        selection.cursor.moveLeft()
+        selection.insertText text
+      else
+        selection.insertText selection.getText().split('').reverse().join('')
+
+  # Public: Convert the selected text to upper case.
+  #
+  # For each selection, if the selection is empty, converts the containing word
+  # to upper case. Otherwise convert the selected text to upper case.
+  upperCase: ->
+    @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toUpperCase()
+
+  # Public: Convert the selected text to lower case.
+  #
+  # For each selection, if the selection is empty, converts the containing word
+  # to upper case. Otherwise convert the selected text to upper case.
+  lowerCase: ->
+    @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toLowerCase()
+
+  # Convert multiple lines to a single line.
+  #
+  # Operates on all selections. If the selection is empty, joins the current
+  # line with the next line. Otherwise it joins all lines that intersect the
+  # selection.
+  #
+  # Joining a line means that multiple lines are converted to a single line with
+  # the contents of each of the original non-empty lines separated by a space.
+  joinLines: ->
+    @mutateSelectedText (selection) -> selection.joinLines()
+
+  ###
+  Section: Adding Text
+  ###
+
+  # Public: For each selection, replace the selected text with the given text.
+  #
+  # ## Events
+  #
+  #  * `will-insert-text` Emit before the text has been inserted.
+  #    * `event` event {Object}
+  #      * `text` {String} text to be inserted
+  #      * `cancel` {Function} Call to prevent the text from being inserted
+  #  * `did-insert-text` Emit after the text has been inserted.
+  #    * `event` event {Object}
+  #      * `text` {String} text to be inserted
+  #
+  # * `text` A {String} representing the text to insert.
+  # * `options` See {Selection::insertText}.
+  #
+  # Returns a {Range} when the text has been inserted
+  # Returns a {Bool} false when the text has not been inserted
+  insertText: (text, options={}) ->
+    willInsert = true
+    cancel = -> willInsert = false
+    @emit('will-insert-text', {cancel, text})
+
+    if willInsert
+      options.autoIndentNewline ?= @shouldAutoIndent()
+      options.autoDecreaseIndent ?= @shouldAutoIndent()
+      @mutateSelectedText (selection) =>
+        range = selection.insertText(text, options)
+        @emit('did-insert-text', {text, range})
+        range
+    else
+      false
+
+  # Public: For each selection, replace the selected text with a newline.
+  insertNewline: ->
+    @insertText('\n')
+
+  # Public: For each cursor, insert a newline at beginning the following line.
+  insertNewlineBelow: ->
+    @transact =>
+      @moveCursorToEndOfLine()
+      @insertNewline()
+
+  # Public: For each cursor, insert a newline at the end of the preceding line.
+  insertNewlineAbove: ->
+    @transact =>
+      bufferRow = @getCursorBufferPosition().row
+      indentLevel = @indentationForBufferRow(bufferRow)
+      onFirstLine = bufferRow is 0
+
+      @moveCursorToBeginningOfLine()
+      @moveCursorLeft()
+      @insertNewline()
+
+      if @shouldAutoIndent() and @indentationForBufferRow(bufferRow) < indentLevel
+        @setIndentationForBufferRow(bufferRow, indentLevel)
+
+      if onFirstLine
+        @moveCursorUp()
+        @moveCursorToEndOfLine()
+
+  ###
+  Section: Removing Text
+  ###
+
+  # Public: For each selection, if the selection is empty, delete the character
+  # preceding the cursor. Otherwise delete the selected text.
+  backspace: ->
+    @mutateSelectedText (selection) -> selection.backspace()
+
+  # Deprecated: Use {::deleteToBeginningOfWord} instead.
+  backspaceToBeginningOfWord: ->
+    deprecate("Use Editor::deleteToBeginningOfWord() instead")
+    @deleteToBeginningOfWord()
+
+  # Deprecated: Use {::deleteToBeginningOfLine} instead.
+  backspaceToBeginningOfLine: ->
+    deprecate("Use Editor::deleteToBeginningOfLine() instead")
+    @deleteToBeginningOfLine()
+
+  # Public: For each selection, if the selection is empty, delete all characters
+  # of the containing word that precede the cursor. Otherwise delete the
+  # selected text.
+  deleteToBeginningOfWord: ->
+    @mutateSelectedText (selection) -> selection.deleteToBeginningOfWord()
+
+  # Public: For each selection, if the selection is empty, delete all characters
+  # of the containing line that precede the cursor. Otherwise delete the
+  # selected text.
+  deleteToBeginningOfLine: ->
+    @mutateSelectedText (selection) -> selection.deleteToBeginningOfLine()
+
+  # Public: For each selection, if the selection is empty, delete the character
+  # preceding the cursor. Otherwise delete the selected text.
+  delete: ->
+    @mutateSelectedText (selection) -> selection.delete()
+
+  # Public: For each selection, if the selection is not empty, deletes the
+  # selection; otherwise, deletes all characters of the containing line
+  # following the cursor. If the cursor is already at the end of the line,
+  # deletes the following newline.
+  deleteToEndOfLine: ->
+    @mutateSelectedText (selection) -> selection.deleteToEndOfLine()
+
+  # Public: For each selection, if the selection is empty, delete all characters
+  # of the containing word following the cursor. Otherwise delete the selected
+  # text.
+  deleteToEndOfWord: ->
+    @mutateSelectedText (selection) -> selection.deleteToEndOfWord()
+
+  # Public: Delete all lines intersecting selections.
+  deleteLine: ->
+    @mutateSelectedText (selection) -> selection.deleteLine()
+
+  ###
+  Section: Searching Text
+  ###
+
+  # {Delegates to: TextBuffer.scan}
+  scan: (args...) -> @buffer.scan(args...)
+
+  # {Delegates to: TextBuffer.scanInRange}
+  scanInBufferRange: (args...) -> @buffer.scanInRange(args...)
+
+  # {Delegates to: TextBuffer.backwardsScanInRange}
+  backwardsScanInBufferRange: (args...) -> @buffer.backwardsScanInRange(args...)
+
+
+  ###
+  Section: Tab Behavior
+  ###

   # Public: Determine if the buffer uses hard or soft tabs.
   #
   # Returns `true` if the first non-comment line with leading whitespace starts
   # with a space character. Returns `false` if it starts with a hard tab (`\t`).
   #
-  # Returns a {Boolean},
+  # Returns a {Boolean}
   usesSoftTabs: ->
     for bufferRow in [0..@buffer.getLastRow()]
       continue if @displayBuffer.tokenizedBuffer.lineForScreenRow(bufferRow).isComment()
@@ -354,34 +720,65 @@
         return match[0][0] != '\t'
     undefined

-  # Public: Clip the given {Point} to a valid position in the buffer.
-  #
-  # If the given {Point} describes a position that is actually reachable by the
-  # cursor based on the current contents of the buffer, it is returned
-  # unchanged. If the {Point} does not describe a valid position, the closest
-  # valid position is returned instead.
-  #
-  # ## Examples
-  #
-  # ```coffee
-  # editor.clipBufferPosition([-1, -1]) # -> `[0, 0]`
-  #
-  # # When the line at buffer row 2 is 10 characters long
-  # editor.clipBufferPosition([2, Infinity]) # -> `[2, 10]`
-  # ```
-  #
-  # * `bufferPosition` The {Point} representing the position to clip.
-  #
-  # Returns a {Point}.
-  clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
-
-  # Public: Clip the start and end of the given range to valid positions in the
-  # buffer. See {::clipBufferPosition} for more information.
-  #
-  # * `range` The {Range} to clip.
-  #
-  # Returns a {Range}.
-  clipBufferRange: (range) -> @buffer.clipRange(range)
+  # Public: Returns a {Boolean} indicating whether softTabs are enabled for this
+  # editor.
+  getSoftTabs: -> @softTabs
+
+  # Public: Enable or disable soft tabs for this editor.
+  #
+  # * `softTabs` A {Boolean}
+  setSoftTabs: (@softTabs) -> @softTabs
+
+  # Public: Toggle soft tabs for this editor
+  toggleSoftTabs: -> @setSoftTabs(not @getSoftTabs())
+
+  # Public: Get the text representing a single level of indent.
+  #
+  # If soft tabs are enabled, the text is composed of N spaces, where N is the
+  # tab length. Otherwise the text is a tab character (`\t`).
+  #
+  # Returns a {String}.
+  getTabText: -> @buildIndentString(1)
+
+  # Public: Get the on-screen length of tab characters.
+  #
+  # Returns a {Number}.
+  getTabLength: -> @displayBuffer.getTabLength()
+
+  # Public: Set the on-screen length of tab characters.
+  setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)
+
+  # If soft tabs are enabled, convert all hard tabs to soft tabs in the given
+  # {Range}.
+  normalizeTabsInBufferRange: (bufferRange) ->
+    return unless @getSoftTabs()
+    @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText())
+
+
+
+  ###
+  Section: Soft Wrap Behavior
+  ###
+
+  # Public: Sets the column at which column will soft wrap
+  getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
+
+  # Public: Get whether soft wrap is enabled for this editor.
+  getSoftWrap: -> @displayBuffer.getSoftWrap()
+
+  # Public: Enable or disable soft wrap for this editor.
+  #
+  # * `softWrap` A {Boolean}
+  setSoftWrap: (softWrap) -> @displayBuffer.setSoftWrap(softWrap)
+
+  # Public: Toggle soft wrap for this editor
+  toggleSoftWrap: -> @setSoftWrap(not @getSoftWrap())
+
+
+
+  ###
+  Section: Indentation
+  ###

   # Public: Get the indentation level of the given a buffer row.
   #
@@ -429,6 +826,25 @@
   indentLevelForLine: (line) ->
     @displayBuffer.indentLevelForLine(line)

+  # Indent all lines intersecting selections. See {Selection::indent} for more
+  # information.
+  indent: (options={}) ->
+    options.autoIndent ?= @shouldAutoIndent()
+    @mutateSelectedText (selection) -> selection.indent(options)
+
+  # Public: Indent rows intersecting selections by one level.
+  indentSelectedRows: ->
+    @mutateSelectedText (selection) -> selection.indentSelectedRows()
+
+  # Public: Outdent rows intersecting selections by one level.
+  outdentSelectedRows: ->
+    @mutateSelectedText (selection) -> selection.outdentSelectedRows()
+
+  # Public: Indent rows intersecting selections based on the grammar's suggested
+  # indent level.
+  autoIndentSelectedRows: ->
+    @mutateSelectedText (selection) -> selection.autoIndentSelectedRows()
+
   # Constructs the string used for tabs.
   buildIndentString: (number, column=0) ->
     if @getSoftTabs()
@@ -437,48 +853,199 @@
     else
       _.multiplyString("\t", Math.floor(number))

-  # Public: Saves the editor's text buffer.
-  #
-  # See {TextBuffer::save} for more details.
-  save: -> @buffer.save()
-
-  # Public: Saves the editor's text buffer as the given path.
-  #
-  # See {TextBuffer::saveAs} for more details.
-  #
-  # * `filePath` A {String} path.
-  saveAs: (filePath) -> @buffer.saveAs(filePath)
-
-  # Copies the current file path to the native clipboard.
-  copyPathToClipboard: ->
-    if filePath = @getPath()
-      atom.clipboard.write(filePath)
-
-  # Public: Returns the {String} path of this editor's text buffer.
-  getPath: -> @buffer.getPath()
-
-  # Public: Returns a {String} representing the entire contents of the editor.
-  getText: -> @buffer.getText()
-
-  # Public: Replaces the entire contents of the buffer with the given {String}.
-  setText: (text) -> @buffer.setText(text)
-
-  # Get the text in the given {Range}.
-  #
-  # Returns a {String}.
-  getTextInRange: (range) -> @buffer.getTextInRange(range)
-
-  # Public: Returns a {Number} representing the number of lines in the editor.
-  getLineCount: -> @buffer.getLineCount()
-
-  # Retrieves the current {TextBuffer}.
-  getBuffer: -> @buffer
-
-  # Public: Retrieves the current buffer's URI.
-  getUri: -> @buffer.getUri()
-
-  # {Delegates to: TextBuffer.isRowBlank}
-  isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)
+
+  ###
+  Section: Undo Operations
+  ###
+
+  # Public: Undo the last change.
+  undo: ->
+    @getCursor().needsAutoscroll = true
+    @buffer.undo(this)
+
+  # Public: Redo the last change.
+  redo: ->
+    @getCursor().needsAutoscroll = true
+    @buffer.redo(this)
+
+  ###
+  Section: Text Mutation Transactions
+  ###
+
+  # 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) -> @buffer.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: -> @buffer.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: -> @buffer.commitTransaction()
+
+  # Public: Abort an open transaction, undoing any operations performed so far
+  # within the transaction.
+  abortTransaction: -> @buffer.abortTransaction()
+
+  ###
+  Section: Editor Coordinates
+  ###
+
+  # Public: Convert a position in buffer-coordinates to screen-coordinates.
+  #
+  # The position is clipped via {::clipBufferPosition} prior to the conversion.
+  # The position is also clipped via {::clipScreenPosition} following the
+  # conversion, which only makes a difference when `options` are supplied.
+  #
+  # * `bufferPosition` A {Point} or {Array} of [row, column].
+  # * `options` An options hash for {::clipScreenPosition}.
+  #
+  # Returns a {Point}.
+  screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options)
+
+  # Public: Convert a position in screen-coordinates to buffer-coordinates.
+  #
+  # The position is clipped via {::clipScreenPosition} prior to the conversion.
+  #
+  # * `bufferPosition` A {Point} or {Array} of [row, column].
+  # * `options` An options hash for {::clipScreenPosition}.
+  #
+  # Returns a {Point}.
+  bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options)
+
+  # Public: Convert a range in buffer-coordinates to screen-coordinates.
+  #
+  # Returns a {Range}.
+  screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange)
+
+  # Public: Convert a range in screen-coordinates to buffer-coordinates.
+  #
+  # Returns a {Range}.
+  bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange)
+
+  # Public: Clip the given {Point} to a valid position in the buffer.
+  #
+  # If the given {Point} describes a position that is actually reachable by the
+  # cursor based on the current contents of the buffer, it is returned
+  # unchanged. If the {Point} does not describe a valid position, the closest
+  # valid position is returned instead.
+  #
+  # ## Examples
+  #
+  # ```coffee
+  # editor.clipBufferPosition([-1, -1]) # -> `[0, 0]`
+  #
+  # # When the line at buffer row 2 is 10 characters long
+  # editor.clipBufferPosition([2, Infinity]) # -> `[2, 10]`
+  # ```
+  #
+  # * `bufferPosition` The {Point} representing the position to clip.
+  #
+  # Returns a {Point}.
+  clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
+
+  # Public: Clip the start and end of the given range to valid positions in the
+  # buffer. See {::clipBufferPosition} for more information.
+  #
+  # * `range` The {Range} to clip.
+  #
+  # Returns a {Range}.
+  clipBufferRange: (range) -> @buffer.clipRange(range)
+
+  # Public: Clip the given {Point} to a valid position on screen.
+  #
+  # If the given {Point} describes a position that is actually reachable by the
+  # cursor based on the current contents of the screen, it is returned
+  # unchanged. If the {Point} does not describe a valid position, the closest
+  # valid position is returned instead.
+  #
+  # ## Examples
+  #
+  # ```coffee
+  # editor.clipScreenPosition([-1, -1]) # -> `[0, 0]`
+  #
+  # # When the line at screen row 2 is 10 characters long
+  # editor.clipScreenPosition([2, Infinity]) # -> `[2, 10]`
+  # ```
+  #
+  # * `bufferPosition` The {Point} representing the position to clip.
+  #
+  # Returns a {Point}.
+  clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options)
+
+
+
+
+  ###
+  Section: Grammars
+  ###
+
+  # Public: Get the current {Grammar} of this editor.
+  getGrammar: ->
+    @displayBuffer.getGrammar()
+
+  # Public: Set the current {Grammar} of this editor.
+  #
+  # Assigning a grammar will cause the editor to re-tokenize based on the new
+  # grammar.
+  setGrammar: (grammar) ->
+    @displayBuffer.setGrammar(grammar)
+
+  # Reload the grammar based on the file name.
+  reloadGrammar: ->
+    @displayBuffer.reloadGrammar()
+
+  ###
+  Section: Syntatic Queries
+  ###
+
+  # Public: Get the syntactic scopes for the given position in buffer
+  # coordinates.
+  #
+  # For example, if called with a position inside the parameter list of an
+  # anonymous CoffeeScript function, the method returns the following array:
+  # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
+  #
+  # * `bufferPosition` A {Point} or {Array} of [row, column].
+  #
+  # Returns an {Array} of {String}s.
+  scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
+
+  # Public: Get the range in buffer coordinates of all tokens surrounding the
+  # cursor that match the given scope selector.
+  #
+  # For example, if you wanted to find the string surrounding the cursor, you
+  # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`.
+  #
+  # Returns a {Range}.
+  bufferRangeForScopeAtCursor: (selector) ->
+    @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
+
+  # {Delegates to: DisplayBuffer.tokenForBufferPosition}
+  tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
+
+  # Public: Get the syntactic scopes for the most recently added cursor's
+  # position. See {::scopesForBufferPosition} for more information.
+  #
+  # Returns an {Array} of {String}s.
+  getCursorScopes: -> @getCursor().getScopes()
+
+  logCursorScope: ->
+    console.log @getCursorScopes()
+

   # Public: Determine if the given row is entirely a comment
   isBufferRowCommented: (bufferRow) ->
@@ -486,284 +1053,6 @@
       scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes
       new TextMateScopeSelector('comment.*').matches(scopes)

-  # {Delegates to: TextBuffer.nextNonBlankRow}
-  nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow)
-
-  # {Delegates to: TextBuffer.getEndPosition}
-  getEofBufferPosition: -> @buffer.getEndPosition()
-
-  # Public: Returns a {Number} representing the last zero-indexed buffer row
-  # number of the editor.
-  getLastBufferRow: -> @buffer.getLastRow()
-
-  # Returns the range for the given buffer row.
-  #
-  # * `row` A row {Number}.
-  # * `options` An options hash with an `includeNewline` key.
-  #
-  # Returns a {Range}.
-  bufferRangeForBufferRow: (row, {includeNewline}={}) -> @buffer.rangeForRow(row, includeNewline)
-
-  # Public: Returns a {String} representing the contents of the line at the
-  # given buffer row.
-  #
-  # * `row` A {Number} representing a zero-indexed buffer row.
-  lineForBufferRow: (row) -> @buffer.lineForRow(row)
-
-  # Public: Returns a {Number} representing the line length for the given
-  # buffer row, exclusive of its line-ending character(s).
-  #
-  # * `row` A {Number} indicating the buffer row.
-  lineLengthForBufferRow: (row) -> @buffer.lineLengthForRow(row)
-
-  # {Delegates to: TextBuffer.scan}
-  scan: (args...) -> @buffer.scan(args...)
-
-  # {Delegates to: TextBuffer.scanInRange}
-  scanInBufferRange: (args...) -> @buffer.scanInRange(args...)
-
-  # {Delegates to: TextBuffer.backwardsScanInRange}
-  backwardsScanInBufferRange: (args...) -> @buffer.backwardsScanInRange(args...)
-
-  # {Delegates to: TextBuffer.isModified}
-  isModified: -> @buffer.isModified()
-
-  isEmpty: -> @buffer.isEmpty()
-
-  # Public: Determine whether the user should be prompted to save before closing
-  # this editor.
-  shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors()
-
-  # Public: Convert a position in buffer-coordinates to screen-coordinates.
-  #
-  # The position is clipped via {::clipBufferPosition} prior to the conversion.
-  # The position is also clipped via {::clipScreenPosition} following the
-  # conversion, which only makes a difference when `options` are supplied.
-  #
-  # * `bufferPosition` A {Point} or {Array} of [row, column].
-  # * `options` An options hash for {::clipScreenPosition}.
-  #
-  # Returns a {Point}.
-  screenPositionForBufferPosition: (bufferPosition, options) -> @displayBuffer.screenPositionForBufferPosition(bufferPosition, options)
-
-  # Public: Convert a position in screen-coordinates to buffer-coordinates.
-  #
-  # The position is clipped via {::clipScreenPosition} prior to the conversion.
-  #
-  # * `bufferPosition` A {Point} or {Array} of [row, column].
-  # * `options` An options hash for {::clipScreenPosition}.
-  #
-  # Returns a {Point}.
-  bufferPositionForScreenPosition: (screenPosition, options) -> @displayBuffer.bufferPositionForScreenPosition(screenPosition, options)
-
-  # Public: Convert a range in buffer-coordinates to screen-coordinates.
-  #
-  # Returns a {Range}.
-  screenRangeForBufferRange: (bufferRange) -> @displayBuffer.screenRangeForBufferRange(bufferRange)
-
-  # Public: Convert a range in screen-coordinates to buffer-coordinates.
-  #
-  # Returns a {Range}.
-  bufferRangeForScreenRange: (screenRange) -> @displayBuffer.bufferRangeForScreenRange(screenRange)
-
-  # Public: Clip the given {Point} to a valid position on screen.
-  #
-  # If the given {Point} describes a position that is actually reachable by the
-  # cursor based on the current contents of the screen, it is returned
-  # unchanged. If the {Point} does not describe a valid position, the closest
-  # valid position is returned instead.
-  #
-  # ## Examples
-  #
-  # ```coffee
-  # editor.clipScreenPosition([-1, -1]) # -> `[0, 0]`
-  #
-  # # When the line at screen row 2 is 10 characters long
-  # editor.clipScreenPosition([2, Infinity]) # -> `[2, 10]`
-  # ```
-  #
-  # * `bufferPosition` The {Point} representing the position to clip.
-  #
-  # Returns a {Point}.
-  clipScreenPosition: (screenPosition, options) -> @displayBuffer.clipScreenPosition(screenPosition, options)
-
-  # {Delegates to: DisplayBuffer.lineForRow}
-  lineForScreenRow: (row) -> @displayBuffer.lineForRow(row)
-
-  # {Delegates to: DisplayBuffer.linesForRows}
-  linesForScreenRows: (start, end) -> @displayBuffer.linesForRows(start, end)
-
-  # {Delegates to: DisplayBuffer.getLineCount}
-  getScreenLineCount: -> @displayBuffer.getLineCount()
-
-  # {Delegates to: DisplayBuffer.getMaxLineLength}
-  getMaxScreenLineLength: -> @displayBuffer.getMaxLineLength()
-
-  # {Delegates to: DisplayBuffer.getLastRow}
-  getLastScreenRow: -> @displayBuffer.getLastRow()
-
-  # {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
-  bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
-
-  bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)
-
-  # Public: Get the syntactic scopes for the given position in buffer
-  # coordinates.
-  #
-  # For example, if called with a position inside the parameter list of an
-  # anonymous CoffeeScript function, the method returns the following array:
-  # `["source.coffee", "meta.inline.function.coffee", "variable.parameter.function.coffee"]`
-  #
-  # * `bufferPosition` A {Point} or {Array} of [row, column].
-  #
-  # Returns an {Array} of {String}s.
-  scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)
-
-  # Public: Get the range in buffer coordinates of all tokens surrounding the
-  # cursor that match the given scope selector.
-  #
-  # For example, if you wanted to find the string surrounding the cursor, you
-  # could call `editor.bufferRangeForScopeAtCursor(".string.quoted")`.
-  #
-  # Returns a {Range}.
-  bufferRangeForScopeAtCursor: (selector) ->
-    @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())
-
-  # {Delegates to: DisplayBuffer.tokenForBufferPosition}
-  tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
-
-  # Public: Get the syntactic scopes for the most recently added cursor's
-  # position. See {::scopesForBufferPosition} for more information.
-  #
-  # Returns an {Array} of {String}s.
-  getCursorScopes: -> @getCursor().getScopes()
-
-  logCursorScope: ->
-    console.log @getCursorScopes()
-
-  # Public: For each selection, replace the selected text with the given text.
-  #
-  # ## Events
-  #
-  #  * `will-insert-text` Emit before the text has been inserted.
-  #    * `event` event {Object}
-  #      * `text` {String} text to be inserted
-  #      * `cancel` {Function} Call to prevent the text from being inserted
-  #  * `did-insert-text` Emit after the text has been inserted.
-  #    * `event` event {Object}
-  #      * `text` {String} text to be inserted
-  #
-  # * `text` A {String} representing the text to insert.
-  # * `options` See {Selection::insertText}.
-  #
-  # Returns a {Range} when the text has been inserted
-  # Returns a {Bool} false when the text has not been inserted
-  insertText: (text, options={}) ->
-    willInsert = true
-    cancel = -> willInsert = false
-    @emit('will-insert-text', {cancel, text})
-
-    if willInsert
-      options.autoIndentNewline ?= @shouldAutoIndent()
-      options.autoDecreaseIndent ?= @shouldAutoIndent()
-      @mutateSelectedText (selection) =>
-        range = selection.insertText(text, options)
-        @emit('did-insert-text', {text, range})
-        range
-    else
-      false
-
-  # Public: For each selection, replace the selected text with a newline.
-  insertNewline: ->
-    @insertText('\n')
-
-  # Public: For each cursor, insert a newline at beginning the following line.
-  insertNewlineBelow: ->
-    @transact =>
-      @moveCursorToEndOfLine()
-      @insertNewline()
-
-  # Public: For each cursor, insert a newline at the end of the preceding line.
-  insertNewlineAbove: ->
-    @transact =>
-      bufferRow = @getCursorBufferPosition().row
-      indentLevel = @indentationForBufferRow(bufferRow)
-      onFirstLine = bufferRow is 0
-
-      @moveCursorToBeginningOfLine()
-      @moveCursorLeft()
-      @insertNewline()
-
-      if @shouldAutoIndent() and @indentationForBufferRow(bufferRow) < indentLevel
-        @setIndentationForBufferRow(bufferRow, indentLevel)
-
-      if onFirstLine
-        @moveCursorUp()
-        @moveCursorToEndOfLine()
-
-  # Indent all lines intersecting selections. See {Selection::indent} for more
-  # information.
-  indent: (options={}) ->
-    options.autoIndent ?= @shouldAutoIndent()
-    @mutateSelectedText (selection) -> selection.indent(options)
-
-  # Public: For each selection, if the selection is empty, delete the character
-  # preceding the cursor. Otherwise delete the selected text.
-  backspace: ->
-    @mutateSelectedText (selection) -> selection.backspace()
-
-  # Deprecated: Use {::deleteToBeginningOfWord} instead.
-  backspaceToBeginningOfWord: ->
-    deprecate("Use Editor::deleteToBeginningOfWord() instead")
-    @deleteToBeginningOfWord()
-
-  # Deprecated: Use {::deleteToBeginningOfLine} instead.
-  backspaceToBeginningOfLine: ->
-    deprecate("Use Editor::deleteToBeginningOfLine() instead")
-    @deleteToBeginningOfLine()
-
-  # Public: For each selection, if the selection is empty, delete all characters
-  # of the containing word that precede the cursor. Otherwise delete the
-  # selected text.
-  deleteToBeginningOfWord: ->
-    @mutateSelectedText (selection) -> selection.deleteToBeginningOfWord()
-
-  # Public: For each selection, if the selection is empty, delete all characters
-  # of the containing line that precede the cursor. Otherwise delete the
-  # selected text.
-  deleteToBeginningOfLine: ->
-    @mutateSelectedText (selection) -> selection.deleteToBeginningOfLine()
-
-  # Public: For each selection, if the selection is empty, delete the character
-  # preceding the cursor. Otherwise delete the selected text.
-  delete: ->
-    @mutateSelectedText (selection) -> selection.delete()
-
-  # Public: For each selection, if the selection is not empty, deletes the
-  # selection; otherwise, deletes all characters of the containing line
-  # following the cursor. If the cursor is already at the end of the line,
-  # deletes the following newline.
-  deleteToEndOfLine: ->
-    @mutateSelectedText (selection) -> selection.deleteToEndOfLine()
-
-  # Public: For each selection, if the selection is empty, delete all characters
-  # of the containing word following the cursor. Otherwise delete the selected
-  # text.
-  deleteToEndOfWord: ->
-    @mutateSelectedText (selection) -> selection.deleteToEndOfWord()
-
-  # Public: Delete all lines intersecting selections.
-  deleteLine: ->
-    @mutateSelectedText (selection) -> selection.deleteLine()
-
-  # Public: Indent rows intersecting selections by one level.
-  indentSelectedRows: ->
-    @mutateSelectedText (selection) -> selection.indentSelectedRows()
-
-  # Public: Outdent rows intersecting selections by one level.
-  outdentSelectedRows: ->
-    @mutateSelectedText (selection) -> selection.outdentSelectedRows()
-
   # Public: Toggle line comments for rows intersecting selections.
   #
   # If the current grammar doesn't support comments, does nothing.
@@ -772,16 +1061,55 @@
   toggleLineCommentsInSelection: ->
     @mutateSelectedText (selection) -> selection.toggleLineComments()

-  # Public: Indent rows intersecting selections based on the grammar's suggested
-  # indent level.
-  autoIndentSelectedRows: ->
-    @mutateSelectedText (selection) -> selection.autoIndentSelectedRows()
-
-  # If soft tabs are enabled, convert all hard tabs to soft tabs in the given
-  # {Range}.
-  normalizeTabsInBufferRange: (bufferRange) ->
-    return unless @getSoftTabs()
-    @scanInBufferRange /\t/g, bufferRange, ({replace}) => replace(@getTabText())
+
+
+
+
+
+
+  ###
+  Section: Clipboard Operations
+  ###
+
+  # Public: For each selection, copy the selected text.
+  copySelectedText: ->
+    maintainClipboard = false
+    for selection in @getSelections()
+      selection.copy(maintainClipboard)
+      maintainClipboard = true
+
+  # Public: For each selection, replace the selected text with the contents of
+  # the clipboard.
+  #
+  # If the clipboard contains the same number of selections as the current
+  # editor, each selection will be replaced with the content of the
+  # corresponding clipboard selection text.
+  #
+  # * `options` See {Selection::insertText}.
+  pasteText: (options={}) ->
+    {text, metadata} = atom.clipboard.readWithMetadata()
+
+    containsNewlines = text.indexOf('\n') isnt -1
+
+    if metadata?.selections? and metadata.selections.length is @getSelections().length
+      @mutateSelectedText (selection, index) ->
+        text = metadata.selections[index]
+        selection.insertText(text, options)
+
+      return
+
+    else if atom.config.get("editor.normalizeIndentOnPaste") and metadata?.indentBasis?
+      if !@getCursor().hasPrecedingCharactersOnLine() or containsNewlines
+        options.indentBasis ?= metadata.indentBasis
+
+    @insertText(text, options)
+
+  # Public: For each selection, cut the selected text.
+  cutSelectedText: ->
+    maintainClipboard = false
+    @mutateSelectedText (selection) ->
+      selection.cut(maintainClipboard)
+      maintainClipboard = true

   # Public: For each selection, if the selection is empty, cut all characters
   # of the containing line following the cursor. Otherwise cut the selected
@@ -792,55 +1120,10 @@
       selection.cutToEndOfLine(maintainClipboard)
       maintainClipboard = true

-  # Public: For each selection, cut the selected text.
-  cutSelectedText: ->
-    maintainClipboard = false
-    @mutateSelectedText (selection) ->
-      selection.cut(maintainClipboard)
-      maintainClipboard = true
-
-  # Public: For each selection, copy the selected text.
-  copySelectedText: ->
-    maintainClipboard = false
-    for selection in @getSelections()
-      selection.copy(maintainClipboard)
-      maintainClipboard = true
-
-  # Public: For each selection, replace the selected text with the contents of
-  # the clipboard.
-  #
-  # If the clipboard contains the same number of selections as the current
-  # editor, each selection will be replaced with the content of the
-  # corresponding clipboard selection text.
-  #
-  # * `options` See {Selection::insertText}.
-  pasteText: (options={}) ->
-    {text, metadata} = atom.clipboard.readWithMetadata()
-
-    containsNewlines = text.indexOf('\n') isnt -1
-
-    if metadata?.selections? and metadata.selections.length is @getSelections().length
-      @mutateSelectedText (selection, index) ->
-        text = metadata.selections[index]
-        selection.insertText(text, options)
-
-      return
-
-    else if atom.config.get("editor.normalizeIndentOnPaste") and metadata?.indentBasis?
-      if !@getCursor().hasPrecedingCharactersOnLine() or containsNewlines
-        options.indentBasis ?= metadata.indentBasis
-
-    @insertText(text, options)
-
-  # Public: Undo the last change.
-  undo: ->
-    @getCursor().needsAutoscroll = true
-    @buffer.undo(this)
-
-  # Public: Redo the last change.
-  redo: ->
-    @getCursor().needsAutoscroll = true
-    @buffer.redo(this)
+
+  ###
+  Section: Folds
+  ###

   # Public: Fold the most recent cursor's row based on its indentation level.
   #
@@ -959,167 +1242,13 @@
   outermostFoldsInBufferRowRange: (startRow, endRow) ->
     @displayBuffer.outermostFoldsInBufferRowRange(startRow, endRow)

-  # Move lines intersection the most recent selection up by one row in screen
-  # coordinates.
-  moveLineUp: ->
-    selection = @getSelectedBufferRange()
-    return if selection.start.row is 0
-    lastRow = @buffer.getLastRow()
-    return if selection.isEmpty() and selection.start.row is lastRow and @buffer.getLastLine() is ''
-
-    @transact =>
-      foldedRows = []
-      rows = [selection.start.row..selection.end.row]
-      if selection.start.row isnt selection.end.row and selection.end.column is 0
-        rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
-
-      # Move line around the fold that is directly above the selection
-      precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
-      precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
-      if fold = @largestFoldContainingBufferRow(precedingBufferRow)
-        insertDelta = fold.getBufferRange().getRowCount()
-      else
-        insertDelta = 1
-
-      for row in rows
-        if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
-          bufferRange = fold.getBufferRange()
-          startRow = bufferRange.start.row
-          endRow = bufferRange.end.row
-          foldedRows.push(startRow - insertDelta)
-        else
-          startRow = row
-          endRow = row
-
-        insertPosition = Point.fromObject([startRow - insertDelta])
-        endPosition = Point.min([endRow + 1], @buffer.getEndPosition())
-        lines = @buffer.getTextInRange([[startRow], endPosition])
-        if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
-          lines = "#{lines}\n"
-
-        @buffer.deleteRows(startRow, endRow)
-
-        # Make sure the inserted text doesn't go into an existing fold
-        if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
-          @unfoldBufferRow(insertPosition.row)
-          foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
-
-        @buffer.insert(insertPosition, lines)
-
-      # Restore folds that existed before the lines were moved
-      for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
-        @foldBufferRow(foldedRow)
-
-      @setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true, autoscroll: true)
-
-  # Move lines intersecting the most recent selection down by one row in screen
-  # coordinates.
-  moveLineDown: ->
-    selection = @getSelectedBufferRange()
-    lastRow = @buffer.getLastRow()
-    return if selection.end.row is lastRow
-    return if selection.end.row is lastRow - 1 and @buffer.getLastLine() is ''
-
-    @transact =>
-      foldedRows = []
-      rows = [selection.end.row..selection.start.row]
-      if selection.start.row isnt selection.end.row and selection.end.column is 0
-        rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
-
-      # Move line around the fold that is directly below the selection
-      followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
-      followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
-      if fold = @largestFoldContainingBufferRow(followingBufferRow)
-        insertDelta = fold.getBufferRange().getRowCount()
-      else
-        insertDelta = 1
-
-      for row in rows
-        if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
-          bufferRange = fold.getBufferRange()
-          startRow = bufferRange.start.row
-          endRow = bufferRange.end.row
-          foldedRows.push(endRow + insertDelta)
-        else
-          startRow = row
-          endRow = row
-
-        if endRow + 1 is lastRow
-          endPosition = [endRow, @buffer.lineLengthForRow(endRow)]
-        else
-          endPosition = [endRow + 1]
-        lines = @buffer.getTextInRange([[startRow], endPosition])
-        @buffer.deleteRows(startRow, endRow)
-
-        insertPosition = Point.min([startRow + insertDelta], @buffer.getEndPosition())
-        if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
-          lines = "\n#{lines}"
-
-        # Make sure the inserted text doesn't go into an existing fold
-        if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
-          @unfoldBufferRow(insertPosition.row)
-          foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
-
-        @buffer.insert(insertPosition, lines)
-
-      # Restore folds that existed before the lines were moved
-      for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
-        @foldBufferRow(foldedRow)
-
-      @setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true, autoscroll: true)
-
-  # Duplicate the most recent cursor's current line.
-  duplicateLines: ->
-    @transact =>
-      for selection in @getSelectionsOrderedByBufferPosition().reverse()
-        selectedBufferRange = selection.getBufferRange()
-        if selection.isEmpty()
-          {start} = selection.getScreenRange()
-          selection.selectToScreenPosition([start.row + 1, 0])
-
-        [startRow, endRow] = selection.getBufferRowRange()
-        endRow++
-
-        foldedRowRanges =
-          @outermostFoldsInBufferRowRange(startRow, endRow)
-            .map (fold) -> fold.getBufferRowRange()
-
-        rangeToDuplicate = [[startRow, 0], [endRow, 0]]
-        textToDuplicate = @getTextInBufferRange(rangeToDuplicate)
-        textToDuplicate = '\n' + textToDuplicate if endRow > @getLastBufferRow()
-        @buffer.insert([endRow, 0], textToDuplicate)
-
-        delta = endRow - startRow
-        selection.setBufferRange(selectedBufferRange.translate([delta, 0]))
-        for [foldStartRow, foldEndRow] in foldedRowRanges
-          @createFold(foldStartRow + delta, foldEndRow + delta)
-
-  # Deprecated: Use {::duplicateLines} instead.
-  duplicateLine: ->
-    deprecate("Use Editor::duplicateLines() instead")
-    @duplicateLines()
-
-  # Public: Mutate the text of all the selections in a single transaction.
-  #
-  # All the changes made inside the given {Function} can be reverted with a
-  # single call to {::undo}.
-  #
-  # * `fn` A {Function} that will be called once for each {Selection}. The first
-  #      argument will be a {Selection} and the second argument will be the
-  #      {Number} index of that selection.
-  mutateSelectedText: (fn) ->
-    @transact => fn(selection, index) for selection, index in @getSelections()
-
-  replaceSelectedText: (options={}, fn) ->
-    {selectWordIfEmpty} = options
-    @mutateSelectedText (selection) ->
-      range = selection.getBufferRange()
-      if selectWordIfEmpty and selection.isEmpty()
-        selection.selectWord()
-      text = selection.getText()
-      selection.deleteSelectedText()
-      selection.insertText(fn(text))
-      selection.setBufferRange(range)
+
+
+
+
+  ###
+  Section: Decorations
+  ###

   # Public: Get all the decorations within a screen row range.
   #
@@ -1178,6 +1307,10 @@
   decorationForId: (id) ->
     @displayBuffer.decorationForId(id)

+  ###
+  Section: Markers
+  ###
+
   # Public: Get the {DisplayBufferMarker} for the given marker id.
   getMarker: (id) ->
     @displayBuffer.getMarker(id)
@@ -1254,6 +1387,11 @@
   getMarkerCount: ->
     @buffer.getMarkerCount()

+
+  ###
+  Section: Cursors
+  ###
+
   # Public: Determine if there are multiple cursors.
   hasMultipleCursors: ->
     @getCursors().length > 1
@@ -1292,6 +1430,164 @@
   # Remove the given cursor from this editor.
   removeCursor: (cursor) ->
     _.remove(@cursors, cursor)
+
+  # Public: Move the cursor to the given position in screen coordinates.
+  #
+  # If there are multiple cursors, they will be consolidated to a single cursor.
+  #
+  # * `position` A {Point} or {Array} of `[row, column]`
+  # * `options` An {Object} combining options for {::clipScreenPosition} with:
+  #   * `autoscroll` Determines whether the editor scrolls to the new cursor's
+  #     position. Defaults to true.
+  setCursorScreenPosition: (position, options) ->
+    @moveCursors (cursor) -> cursor.setScreenPosition(position, options)
+
+  # Public: Get the position of the most recently added cursor in screen
+  # coordinates.
+  #
+  # Returns a {Point}.
+  getCursorScreenPosition: ->
+    @getCursor().getScreenPosition()
+
+  # Public: Get the row of the most recently added cursor in screen coordinates.
+  #
+  # Returns the screen row {Number}.
+  getCursorScreenRow: ->
+    @getCursor().getScreenRow()
+
+  # Public: Move the cursor to the given position in buffer coordinates.
+  #
+  # If there are multiple cursors, they will be consolidated to a single cursor.
+  #
+  # * `position` A {Point} or {Array} of `[row, column]`
+  # * `options` An {Object} combining options for {::clipScreenPosition} with:
+  #   * `autoscroll` Determines whether the editor scrolls to the new cursor's
+  #     position. Defaults to true.
+  setCursorBufferPosition: (position, options) ->
+    @moveCursors (cursor) -> cursor.setBufferPosition(position, options)
+
+  # Public: Get the position of the most recently added cursor in buffer
+  # coordinates.
+  #
+  # Returns a {Point}.
+  getCursorBufferPosition: ->
+    @getCursor().getBufferPosition()
+
+  # Public: Returns the word surrounding the most recently added cursor.
+  #
+  # * `options` See {Cursor::getBeginningOfCurrentWordBufferPosition}.
+  getWordUnderCursor: (options) ->
+    @getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options))
+
+  # Public: Move every cursor up one row in screen coordinates.
+  moveCursorUp: (lineCount) ->
+    @moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)
+
+  # Public: Move every cursor down one row in screen coordinates.
+  moveCursorDown: (lineCount) ->
+    @moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)
+
+  # Public: Move every cursor left one column.
+  moveCursorLeft: ->
+    @moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true)
+
+  # Public: Move every cursor right one column.
+  moveCursorRight: ->
+    @moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true)
+
+  # Public: Move every cursor to the top of the buffer.
+  #
+  # If there are multiple cursors, they will be merged into a single cursor.
+  moveCursorToTop: ->
+    @moveCursors (cursor) -> cursor.moveToTop()
+
+  # Public: Move every cursor to the bottom of the buffer.
+  #
+  # If there are multiple cursors, they will be merged into a single cursor.
+  moveCursorToBottom: ->
+    @moveCursors (cursor) -> cursor.moveToBottom()
+
+  # Public: Move every cursor to the beginning of its line in screen coordinates.
+  moveCursorToBeginningOfScreenLine: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfScreenLine()
+
+  # Public: Move every cursor to the beginning of its line in buffer coordinates.
+  moveCursorToBeginningOfLine: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfLine()
+
+  # Public: Move every cursor to the first non-whitespace character of its line.
+  moveCursorToFirstCharacterOfLine: ->
+    @moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()
+
+  # Public: Move every cursor to the end of its line in screen coordinates.
+  moveCursorToEndOfScreenLine: ->
+    @moveCursors (cursor) -> cursor.moveToEndOfScreenLine()
+
+  # Public: Move every cursor to the end of its line in buffer coordinates.
+  moveCursorToEndOfLine: ->
+    @moveCursors (cursor) -> cursor.moveToEndOfLine()
+
+  # Public: Move every cursor to the beginning of its surrounding word.
+  moveCursorToBeginningOfWord: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfWord()
+
+  # Public: Move every cursor to the end of its surrounding word.
+  moveCursorToEndOfWord: ->
+    @moveCursors (cursor) -> cursor.moveToEndOfWord()
+
+  # Public: Move every cursor to the beginning of the next word.
+  moveCursorToBeginningOfNextWord: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()
+
+  # Public: Move every cursor to the previous word boundary.
+  moveCursorToPreviousWordBoundary: ->
+    @moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()
+
+  # Public: Move every cursor to the next word boundary.
+  moveCursorToNextWordBoundary: ->
+    @moveCursors (cursor) -> cursor.moveToNextWordBoundary()
+
+  # Public: Move every cursor to the beginning of the next paragraph.
+  moveCursorToBeginningOfNextParagraph: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfNextParagraph()
+
+  # Public: Move every cursor to the beginning of the previous paragraph.
+  moveCursorToBeginningOfPreviousParagraph: ->
+    @moveCursors (cursor) -> cursor.moveToBeginningOfPreviousParagraph()
+
+  moveCursors: (fn) ->
+    @movingCursors = true
+    fn(cursor) for cursor in @getCursors()
+    @mergeCursors()
+    @movingCursors = false
+    @emit 'cursors-moved'
+
+  cursorMoved: (event) ->
+    @emit 'cursor-moved', event
+    @emit 'cursors-moved' unless @movingCursors
+
+  # Merge cursors that have the same screen position
+  mergeCursors: ->
+    positions = []
+    for cursor in @getCursors()
+      position = cursor.getBufferPosition().toString()
+      if position in positions
+        cursor.destroy()
+      else
+        positions.push(position)
+
+  preserveCursorPositionOnBufferReload: ->
+    cursorPosition = null
+    @subscribe @buffer, "will-reload", =>
+      cursorPosition = @getCursorBufferPosition()
+    @subscribe @buffer, "reloaded", =>
+      @setCursorBufferPosition(cursorPosition) if cursorPosition
+      cursorPosition = null
+
+
+  ###
+  Section: Selections
+  ###

   # Add a {Selection} based on the given {DisplayBufferMarker}.
   #
@@ -1442,48 +1738,6 @@
     _.any @getSelections(), (selection) ->
       selection.intersectsBufferRange(bufferRange)

-  # Public: Move the cursor to the given position in screen coordinates.
-  #
-  # If there are multiple cursors, they will be consolidated to a single cursor.
-  #
-  # * `position` A {Point} or {Array} of `[row, column]`
-  # * `options` An {Object} combining options for {::clipScreenPosition} with:
-  #   * `autoscroll` Determines whether the editor scrolls to the new cursor's
-  #     position. Defaults to true.
-  setCursorScreenPosition: (position, options) ->
-    @moveCursors (cursor) -> cursor.setScreenPosition(position, options)
-
-  # Public: Get the position of the most recently added cursor in screen
-  # coordinates.
-  #
-  # Returns a {Point}.
-  getCursorScreenPosition: ->
-    @getCursor().getScreenPosition()
-
-  # Public: Get the row of the most recently added cursor in screen coordinates.
-  #
-  # Returns the screen row {Number}.
-  getCursorScreenRow: ->
-    @getCursor().getScreenRow()
-
-  # Public: Move the cursor to the given position in buffer coordinates.
-  #
-  # If there are multiple cursors, they will be consolidated to a single cursor.
-  #
-  # * `position` A {Point} or {Array} of `[row, column]`
-  # * `options` An {Object} combining options for {::clipScreenPosition} with:
-  #   * `autoscroll` Determines whether the editor scrolls to the new cursor's
-  #     position. Defaults to true.
-  setCursorBufferPosition: (position, options) ->
-    @moveCursors (cursor) -> cursor.setBufferPosition(position, options)
-
-  # Public: Get the position of the most recently added cursor in buffer
-  # coordinates.
-  #
-  # Returns a {Point}.
-  getCursorBufferPosition: ->
-    @getCursor().getBufferPosition()
-
   # Public: Get the {Range} of the most recently added selection in screen
   # coordinates.
   #
@@ -1519,150 +1773,6 @@
   # Returns a {String}.
   getSelectedText: ->
     @getLastSelection().getText()
-
-  # Public: Get the text in the given {Range} in buffer coordinates.
-  #
-  # * `range` A {Range} or range-compatible {Array}.
-  #
-  # Returns a {String}.
-  getTextInBufferRange: (range) ->
-    @buffer.getTextInRange(range)
-
-  # Public: Set the text in the given {Range} in buffer coordinates.
-  #
-  # * `range` A {Range} or range-compatible {Array}.
-  # * `text` A {String}
-  #
-  # Returns the {Range} of the newly-inserted text.
-  setTextInBufferRange: (range, text, normalizeLineEndings) -> @getBuffer().setTextInRange(range, text, normalizeLineEndings)
-
-  # Public: Get the {Range} of the paragraph surrounding the most recently added
-  # cursor.
-  #
-  # Returns a {Range}.
-  getCurrentParagraphBufferRange: ->
-    @getCursor().getCurrentParagraphBufferRange()
-
-  # Public: Returns the word surrounding the most recently added cursor.
-  #
-  # * `options` See {Cursor::getBeginningOfCurrentWordBufferPosition}.
-  getWordUnderCursor: (options) ->
-    @getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options))
-
-  # Public: Move every cursor up one row in screen coordinates.
-  moveCursorUp: (lineCount) ->
-    @moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)
-
-  # Public: Move every cursor down one row in screen coordinates.
-  moveCursorDown: (lineCount) ->
-    @moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)
-
-  # Public: Move every cursor left one column.
-  moveCursorLeft: ->
-    @moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true)
-
-  # Public: Move every cursor right one column.
-  moveCursorRight: ->
-    @moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true)
-
-  # Public: Move every cursor to the top of the buffer.
-  #
-  # If there are multiple cursors, they will be merged into a single cursor.
-  moveCursorToTop: ->
-    @moveCursors (cursor) -> cursor.moveToTop()
-
-  # Public: Move every cursor to the bottom of the buffer.
-  #
-  # If there are multiple cursors, they will be merged into a single cursor.
-  moveCursorToBottom: ->
-    @moveCursors (cursor) -> cursor.moveToBottom()
-
-  # Public: Move every cursor to the beginning of its line in screen coordinates.
-  moveCursorToBeginningOfScreenLine: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfScreenLine()
-
-  # Public: Move every cursor to the beginning of its line in buffer coordinates.
-  moveCursorToBeginningOfLine: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfLine()
-
-  # Public: Move every cursor to the first non-whitespace character of its line.
-  moveCursorToFirstCharacterOfLine: ->
-    @moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()
-
-  # Public: Move every cursor to the end of its line in screen coordinates.
-  moveCursorToEndOfScreenLine: ->
-    @moveCursors (cursor) -> cursor.moveToEndOfScreenLine()
-
-  # Public: Move every cursor to the end of its line in buffer coordinates.
-  moveCursorToEndOfLine: ->
-    @moveCursors (cursor) -> cursor.moveToEndOfLine()
-
-  # Public: Move every cursor to the beginning of its surrounding word.
-  moveCursorToBeginningOfWord: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfWord()
-
-  # Public: Move every cursor to the end of its surrounding word.
-  moveCursorToEndOfWord: ->
-    @moveCursors (cursor) -> cursor.moveToEndOfWord()
-
-  # Public: Move every cursor to the beginning of the next word.
-  moveCursorToBeginningOfNextWord: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()
-
-  # Public: Move every cursor to the previous word boundary.
-  moveCursorToPreviousWordBoundary: ->
-    @moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()
-
-  # Public: Move every cursor to the next word boundary.
-  moveCursorToNextWordBoundary: ->
-    @moveCursors (cursor) -> cursor.moveToNextWordBoundary()
-
-  # Public: Move every cursor to the beginning of the next paragraph.
-  moveCursorToBeginningOfNextParagraph: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfNextParagraph()
-
-  # Public: Move every cursor to the beginning of the previous paragraph.
-  moveCursorToBeginningOfPreviousParagraph: ->
-    @moveCursors (cursor) -> cursor.moveToBeginningOfPreviousParagraph()
-
-  # Public: Scroll the editor to reveal the most recently added cursor if it is
-  # off-screen.
-  #
-  # * `options` An optional hash of options.
-  #   * `center` Center the editor around the cursor if possible. Defauls to true.
-  scrollToCursorPosition: (options) ->
-    @getCursor().autoscroll(center: options?.center ? true)
-
-  pageUp: ->
-    newScrollTop = @getScrollTop() - @getHeight()
-    @moveCursorUp(@getRowsPerPage())
-    @setScrollTop(newScrollTop)
-
-  pageDown: ->
-    newScrollTop = @getScrollTop() + @getHeight()
-    @moveCursorDown(@getRowsPerPage())
-    @setScrollTop(newScrollTop)
-
-  selectPageUp: ->
-    @selectUp(@getRowsPerPage())
-
-  selectPageDown: ->
-    @selectDown(@getRowsPerPage())
-
-  # Returns the number of rows per page
-  getRowsPerPage: ->
-    Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels()))
-
-  moveCursors: (fn) ->
-    @movingCursors = true
-    fn(cursor) for cursor in @getCursors()
-    @mergeCursors()
-    @movingCursors = false
-    @emit 'cursors-moved'
-
-  cursorMoved: (event) ->
-    @emit 'cursor-moved', event
-    @emit 'cursors-moved' unless @movingCursors

   # Public: Select from the current cursor position to the given position in
   # screen coordinates.
@@ -1788,64 +1898,6 @@
   addSelectionAbove: ->
     @expandSelectionsBackward (selection) -> selection.addSelectionAbove()

-  # Public: Split multi-line selections into one selection per line.
-  #
-  # Operates on all selections. This method breaks apart all multi-line
-  # selections to create multiple single-line selections that cumulatively cover
-  # the same original area.
-  splitSelectionsIntoLines: ->
-    for selection in @getSelections()
-      range = selection.getBufferRange()
-      continue if range.isSingleLine()
-
-      selection.destroy()
-      {start, end} = range
-      @addSelectionForBufferRange([start, [start.row, Infinity]])
-      {row} = start
-      while ++row < end.row
-        @addSelectionForBufferRange([[row, 0], [row, Infinity]])
-      @addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) unless end.column is 0
-
-  # Public: For each selection, transpose the selected text.
-  #
-  # If the selection is empty, the characters preceding and following the cursor
-  # are swapped. Otherwise, the selected characters are reversed.
-  transpose: ->
-    @mutateSelectedText (selection) ->
-      if selection.isEmpty()
-        selection.selectRight()
-        text = selection.getText()
-        selection.delete()
-        selection.cursor.moveLeft()
-        selection.insertText text
-      else
-        selection.insertText selection.getText().split('').reverse().join('')
-
-  # Public: Convert the selected text to upper case.
-  #
-  # For each selection, if the selection is empty, converts the containing word
-  # to upper case. Otherwise convert the selected text to upper case.
-  upperCase: ->
-    @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toUpperCase()
-
-  # Public: Convert the selected text to lower case.
-  #
-  # For each selection, if the selection is empty, converts the containing word
-  # to upper case. Otherwise convert the selected text to upper case.
-  lowerCase: ->
-    @replaceSelectedText selectWordIfEmpty:true, (text) -> text.toLowerCase()
-
-  # Convert multiple lines to a single line.
-  #
-  # Operates on all selections. If the selection is empty, joins the current
-  # line with the next line. Otherwise it joins all lines that intersect the
-  # selection.
-  #
-  # Joining a line means that multiple lines are converted to a single line with
-  # the contents of each of the original non-empty lines separated by a space.
-  joinLines: ->
-    @mutateSelectedText (selection) -> selection.joinLines()
-
   # Public: Expand selections to the beginning of their containing word.
   #
   # Operates on all selections. Moves the cursor to the beginning of the
@@ -1895,16 +1947,6 @@
       range = marker.getBufferRange()
       @setSelectedBufferRange(range)
       range
-
-  # Merge cursors that have the same screen position
-  mergeCursors: ->
-    positions = []
-    for cursor in @getCursors()
-      position = cursor.getBufferPosition().toString()
-      if position in positions
-        cursor.destroy()
-      else
-        positions.push(position)

   # Calls the given function with each selection, then merges selections
   expandSelectionsForward: (fn) ->
@@ -1944,28 +1986,44 @@

     _.reduce(@getSelections(), reducer, [])

-  preserveCursorPositionOnBufferReload: ->
-    cursorPosition = null
-    @subscribe @buffer, "will-reload", =>
-      cursorPosition = @getCursorBufferPosition()
-    @subscribe @buffer, "reloaded", =>
-      @setCursorBufferPosition(cursorPosition) if cursorPosition
-      cursorPosition = null
-
-  # Public: Get the current {Grammar} of this editor.
-  getGrammar: ->
-    @displayBuffer.getGrammar()
-
-  # Public: Set the current {Grammar} of this editor.
-  #
-  # Assigning a grammar will cause the editor to re-tokenize based on the new
-  # grammar.
-  setGrammar: (grammar) ->
-    @displayBuffer.setGrammar(grammar)
-
-  # Reload the grammar based on the file name.
-  reloadGrammar: ->
-    @displayBuffer.reloadGrammar()
+
+
+  ###
+  Section: Scrolling the Editor
+  ###
+
+  # Public: Scroll the editor to reveal the most recently added cursor if it is
+  # off-screen.
+  #
+  # * `options` An optional hash of options.
+  #   * `center` Center the editor around the cursor if possible. Defauls to true.
+  scrollToCursorPosition: (options) ->
+    @getCursor().autoscroll(center: options?.center ? true)
+
+  pageUp: ->
+    newScrollTop = @getScrollTop() - @getHeight()
+    @moveCursorUp(@getRowsPerPage())
+    @setScrollTop(newScrollTop)
+
+  pageDown: ->
+    newScrollTop = @getScrollTop() + @getHeight()
+    @moveCursorDown(@getRowsPerPage())
+    @setScrollTop(newScrollTop)
+
+  selectPageUp: ->
+    @selectUp(@getRowsPerPage())
+
+  selectPageDown: ->
+    @selectDown(@getRowsPerPage())
+
+  # Returns the number of rows per page
+  getRowsPerPage: ->
+    Math.max(1, Math.ceil(@getHeight() / @getLineHeightInPixels()))
+
+
+  ###
+  Section: Config
+  ###

   shouldAutoIndent: ->
     atom.config.get("editor.autoIndent")
@@ -1979,38 +2037,10 @@
     else
       @displayBuffer.setInvisibles(null)

-  # 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) -> @buffer.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: -> @buffer.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: -> @buffer.commitTransaction()
-
-  # Public: Abort an open transaction, undoing any operations performed so far
-  # within the transaction.
-  abortTransaction: -> @buffer.abortTransaction()
-
-  inspect: ->
-    "<Editor #{@id}>"
-
-  logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
+
+  ###
+  Section: Event Handlers
+  ###

   handleTokenization: ->
     @softTabs = @usesSoftTabs() ? @softTabs
@@ -2022,6 +2052,10 @@
   handleMarkerCreated: (marker) =>
     if marker.matchesAttributes(@getSelectionMarkerAttributes())
       @addSelection(marker)
+
+  ###
+  Section: Editor Rendering
+  ###

   getSelectionMarkerAttributes: ->
     type: 'selection', editorId: @id, invalidate: 'never'
@@ -2104,3 +2138,12 @@
   joinLine: ->
     deprecate("Use Editor::joinLines() instead")
     @joinLines()
+
+  ###
+  Section: Utility
+  ###
+
+  inspect: ->
+    "<Editor #{@id}>"
+
+  logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)


Partially render the gutter when possible.

--- src/gutter.coffee
+++ src/gutter.coffee
@@ -15,8 +15,11 @@
     @div class: 'gutter', =>
       @div outlet: 'lineNumbers', class: 'line-numbers'

-  firstScreenRow: Infinity
-  lastScreenRow: -1
+  firstScreenRow: null
+  lastScreenRow: null
+
+  initialize: ->
+    @elementBuilder = document.createElement('div')

   afterAttach: (onDom) ->
     return if @attached or not onDom
@@ -66,7 +69,7 @@
   #
   # Returns a list of {HTMLElement}s.
   getLineNumberElements: ->
-    @lineNumbers[0].childNodes
+    @lineNumbers[0].children

   # Get all the line-number divs.
   #
@@ -134,25 +137,64 @@

   ### Internal ###

-  updateLineNumbers: (changes, renderFrom, renderTo) ->
-    if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
-      performUpdate = true
-    else if @getEditor().getLastScreenRow() < @lastScreenRow
-      performUpdate = true
+  updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
+    # Check that we have something already rendered that overlaps the requested range
+    updateAllLines = not (startScreenRow? and endScreenRow?)
+    updateAllLines |= endScreenRow <= @firstScreenRow or startScreenRow >= @lastScreenRow
+
+    for change in changes
+      # When there is a change to the bufferRow -> screenRow map (i.e. a fold),
+      # then rerender everything.
+      if (change.screenDelta or change.bufferDelta) and change.screenDelta != change.bufferDelta
+        updateAllLines = true
+        break
+
+    if updateAllLines
+      @lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
+
     else
-      for change in changes
-        if change.screenDelta or change.bufferDelta
-          performUpdate = true
-          break
-
-    @renderLineNumbers(renderFrom, renderTo) if performUpdate
-
-  renderLineNumbers: (startScreenRow, endScreenRow) ->
-    @lineNumbers[0].innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
+      # When scrolling or adding/removing lines, we just add/remove lines from the ends.
+      if startScreenRow < @firstScreenRow
+        @prependLineElements(@buildLineElements(startScreenRow, @firstScreenRow-1))
+      else if startScreenRow != @firstScreenRow
+        @removeLineElements(startScreenRow - @firstScreenRow)
+
+      if endScreenRow > @lastScreenRow
+        @appendLineElements(@buildLineElements(@lastScreenRow+1, endScreenRow))
+      else if endScreenRow != @lastScreenRow
+        @removeLineElements(endScreenRow - @lastScreenRow)
+
     @firstScreenRow = startScreenRow
     @lastScreenRow = endScreenRow
     @highlightedRows = null
     @highlightLines()
+
+  prependLineElements: (lineElements) ->
+    anchor = @lineNumbers[0].children[0]
+    return appendLineElements(lineElements) unless anchor?
+    @lineNumbers[0].insertBefore(lineElements[0], anchor) while lineElements.length > 0
+    null
+
+  appendLineElements: (lineElements) ->
+    @lineNumbers[0].appendChild(lineElements[0]) while lineElements.length > 0
+    null
+
+  removeLineElements: (numberOfElements) ->
+    children = @getLineNumberElements()
+
+    if numberOfElements < 0
+      while numberOfElements
+        @lineNumbers[0].removeChild(children[children.length-1])
+        numberOfElements++
+
+    else if numberOfElements > 0
+      while numberOfElements
+        @lineNumbers[0].removeChild(children[0])
+        numberOfElements--
+
+  buildLineElements: (startScreenRow, endScreenRow) ->
+    @elementBuilder.innerHTML = @buildLineElementsHtml(startScreenRow, endScreenRow)
+    @elementBuilder.children

   buildLineElementsHtml: (startScreenRow, endScreenRow) =>
     editor = @getEditor()


fixed prototype additions for suffixed methods

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/DocuSignDev/Jeeves.git/'


:lipstick:

:lipstick:

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -146,8 +146,7 @@
     otherPlatforms = platforms.filter (name) -> name != process.platform

     for filePath in fs.listSync(directoryPath, ['.cson', '.json'])
-      platform = path.basename(filePath).match(/^(\w+)\.\w+$/)?[1]
-      continue if platform in otherPlatforms
+      continue if path.basename(filePath, path.extname(filePath)) in otherPlatforms
       @load(filePath)

   load: (path) ->


environments: change resource quantities of default VM packs

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/andrewjcasal/koding.git/'


Do not update app script if not provided

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -215,7 +215,7 @@

   defineApp:(name, script)->

-    KDApps[name] = script
+    KDApps[name] = script if script

   getAppScript:(manifest, callback = noop)->


adding a test for multiline-array-comprehension-with-filter

--- test/fixtures/execution/test_array_comprehension.coffee
+++ test/fixtures/execution/test_array_comprehension.coffee
@@ -1,14 +1,21 @@
 nums:    n * n for n in [1, 2, 3] when n % 2 isnt 0
 results: n * 2 for n in nums
+
+print(results.join(',') is '2,18')
+

 obj:   {one: 1, two: 2, three: 3}
 names: key + '!' for value, key in obj
 odds:  key + '!' for value, key in obj when value % 2 isnt 0

-# next: for n in [1, 2, 3] if n % 2 isnt 0
-#   print('hi') if false
-#   n * n * 2
+print(names.join(' ')   is "one! two! three!")
+print(odds.join(' ')    is "one! three!")

-print(results.join(',') is '2,18')
-print(names.join(' ')   is "one! two! three!")
-print(odds.join(' ')    is "one! three!")+
+evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
+           num *= -1
+           num -= 2
+           num * -1
+
+print(evens.join(', ') is '4, 6, 8')
+


increasing default ami build size

increasing default ami build size

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/bubblegroup/bubblebot.git/'


Initial commit

Initial commit

--- Dom.coffee
+++ Dom.coffee
@@ -0,0 +1,102 @@
+class Dom
+  ###
+  Stores display data for elements (used for `show()` and `hide()`).
+  ###
+  @elemData = {}
+
+  @hide: (el) ->
+    ###
+    Hides the given `el`, storing its display value in order to be able to
+    restore it when `show()`ing the element.
+    ###
+    Dom.elemData[el] ||= {}
+    if !el.style.display || el.style.display == 'none'
+      Dom.elemData[el].oldDisplay = 'block'
+    else
+      Dom.elemData[el].oldDisplay = el.style.display
+    el.style.display = 'none'
+
+  @show: (el) ->
+    ###
+    Shows the given `el`, using its previous display value if we have it,
+    or a default of "block".
+    ###
+    if Dom.elemData[el]? && Dom.elemData[el].oldDisplay?
+      el.style.display = Dom.elemData[el].oldDisplay
+    else
+      el.style.display = 'block'
+
+  @empty: (el) ->
+    ###
+    Empties `el`'s contents.
+    ###
+    el.innerHTML = ''
+
+  @html: (el, html) ->
+    ###
+    Sets `el`'s HTML to `html`.
+    ###
+    el.innerHTML = html
+
+  @append: (elParent, elChild) ->
+    ###
+    Appends `elChild` to `elParent`.
+    ###
+    elParent.appendChild(elChild)
+
+  @appendHTML: (el, html) ->
+    ###
+    Appends `html` to `el`'s HTML.
+    ###
+    el.innerHTML ||= ''
+    el.innerHTML += html
+
+  @delete: (el) ->
+    ###
+    Deletes element `el`.
+    ###
+    el.parentNode.removeChild(el)
+
+  @disable: (el) ->
+    ###
+    Sets `el`'s "disabled" attribute to "disabled".
+    ###
+    el.setAttribute('disabled', 'disabled')
+
+  @enable: (el) ->
+    ###
+    Removes `el`'s "disabled" attribute".
+    ###
+    el.removeAttribute('disabled')
+
+  @attachEventListener = (el, type, handler) ->
+    ###
+    Attach a handler of type `type` with function `handler` to element `el`.
+    ###
+    return if !el?
+    if el.addEventListener
+      el.addEventListener(type, handler, false)
+    else if el.attachEvent
+      el.attachEvent("on" + type, handler)
+    else
+      el['on' + type] = handler
+
+  @isNode: (el) ->
+    ###
+    Returns true if `el` is a DOM Node.
+    ###
+    if typeof Node == 'object'
+      el instanceof Node
+    else
+      el && typeof el == 'object' && typeof el.nodeType == 'number' &&
+      typeof el.nodeName == 'string'
+
+  @isElement: (el) ->
+    ###
+    Returns true if `el` is a DOM element.
+    ###
+    if typeof HTMLElement == 'object'
+      el instanceof HTMLElement
+    else
+      el && typeof el == 'object' && el != null && el.nodeType == 1 &&
+      typeof el.nodeName == 'string'


proof of concept

proof of concept


add deferred ext

add deferred ext

error: pathspec 'lib/client/javascripts/nali/deferred.js.coffee' did not match any file(s) known to git

--- lib/client/javascripts/nali/deferred.js.coffee
+++ lib/client/javascripts/nali/deferred.js.coffee
@@ -0,0 +1,16 @@
+Nali.extend Deferred:
+
+  new: ( callback ) ->
+    obj         = Object.create @
+    obj.defer   = callback
+    obj.awaits  = []
+    obj.results = []
+    obj
+
+  await: ( callback ) ->
+    wrapper = ( args... ) =>
+      @results.push callback args...
+      @awaits.splice @awaits.indexOf( wrapper ), 1
+      @defer @results unless @awaits.length
+    @awaits.push wrapper
+    wrapper


xss: make sure to escape incoming realtime events

--- client/Main/maincontroller/groupscontroller.coffee
+++ client/Main/maincontroller/groupscontroller.coffee
@@ -29,19 +29,27 @@
     throw 'FIXME: array should never be passed'  if Array.isArray @currentGroupData.data
     return @currentGroupData.data

+  filterXssAndForwardEvents: (target, events) ->
+    events.forEach (event) =>
+      target.on event, (rest...) =>
+        rest = KD.remote.revive rest
+        @emit event, rest...
+
   openGroupChannel:(group, callback=->)->
     @groupChannel = KD.remote.subscribe "group.#{group.slug}",
       serviceType : 'group'
       group       : group.slug
       isExclusive : yes

-    @forwardEvent @groupChannel, "MemberJoinedGroup"
-    @forwardEvent @groupChannel, "FollowHappened"
-    @forwardEvent @groupChannel, "LikeIsAdded"
-    @forwardEvent @groupChannel, "PostIsCreated"
-    @forwardEvent @groupChannel, "ReplyIsAdded"
-    @forwardEvent @groupChannel, "PostIsDeleted"
-    @forwardEvent @groupChannel, "LikeIsRemoved"
+    @filterXssAndForwardEvents @groupChannel, [
+      "MemberJoinedGroup"
+      "FollowHappened"
+      "LikeIsAdded"
+      "PostIsCreated"
+      "ReplyIsAdded"
+      "PostIsDeleted"
+      "LikeIsRemoved"
+    ]

     @groupChannel.once 'setSecretNames', callback


Providers: Google: update instance names, add f1-micro and g1-small

--- client/app/lib/providers/instance-types/gce.coffee
+++ client/app/lib/providers/instance-types/gce.coffee
@@ -1,27 +1,35 @@
 module.exports =
-  'standard-1':
+  'f1-micro':
+    ram: '0.6GB RAM'
+    cpu: '0.2x CPU'
+    description: 'Standard 0.2 vCPU machine type with 0.6 GB of memory.'
+  'g1-small':
+    ram: '1.7GB RAM'
+    cpu: '0.5x CPU'
+    description: 'Standard 0.5 vCPU machine type with 1.7 GB of memory.'
+  'n1-standard-1':
     ram: '3.75GB RAM'
     cpu: '1x CPU'
     description: 'Standard 1 CPU machine type with 1 virtual CPU and 3.75 GB of memory.'
-  'standard-2':
+  'n1-standard-2':
     ram: '7.5GB RAM'
     cpu: '2x CPU'
     description: 'Standard 2 CPU machine type with 2 virtual CPUs and 7.5 GB of memory.'
-  'standard-4':
+  'n1-standard-4':
     ram: '15GB RAM'
     cpu: '4x CPU'
     description: 'Standard 4 CPU machine type with 4 virtual CPUs and 15 GB of memory.'
-  'standard-8':
+  'n1-standard-8':
     ram: '30GB RAM'
     cpu: '8x CPU'
     description:'Standard 8 CPU machine type with 8 virtual CPUs and 30 GB of memory.'
-  'standard-16':
+  'n1-standard-16':
     ram: '60GB RAM'
     cpu: '16x CPU'
     description: 'Standard 16 CPU machine type with 16 virtual CPUs and 60 GB of memory.'
-  'standard-32':
+  'n1-standard-32':
     ram: '120GB RAM'
     cpu: '32x CPU'
     description: 'Standard 32 CPU machine type with 32 virtual CPUs and 120 GB of memory.'

-  'base-vm': 'standard-1'
+  'base-vm': 'f1-micro'


Templates: Marathon use optional values as userInputs


social: remove unwanted console logs

social: remove unwanted console logs


Add callback parameter to Tracker.track function

--- workers/social/lib/social/models/tracker.coffee
+++ workers/social/lib/social/models/tracker.coffee
@@ -21,7 +21,7 @@
   @set
     sharedMethods:
       static:
-        track: (signature String, Object)
+        track: (signature String, Object, Function)

   KodingError = require '../error'

@@ -63,13 +63,15 @@
       console.error "flushing identify failed: #{err} @sent-hil"  if err

-  @track$ = secure (client, subject, options = {}) ->
+  @track$ = secure (client, subject, options = {}, callback) ->

     { profile: { nickname } } = client.connection.delegate

     event = { subject }

     @track nickname, event, options
+
+    callback()

   @track = (username, event, options = {}) ->


space all of the things

space all of the things

error: pathspec 'src/scripts/archer.coffee' did not match any file(s) known to git

--- src/scripts/archer.coffee
+++ src/scripts/archer.coffee
@@ -0,0 +1,55 @@
+# Make hubot fetch quotes pertaining to the world's best secret agent, Archer.
+# Hubot.
+# HUBOT.
+# HUBOT!!!!
+# WHAT?
+# DANGER ZONE.
+#
+# archer quote
+
+# REQUIRED MODULES
+# sudo npm install htmlparser
+# sudo npm install soupselect
+# sudo npm install jsdom
+# sudo npm install underscore
+
+Select     = require("soupselect").select
+HtmlParser = require "htmlparser"
+JsDom      = require "jsdom"
+_          = require("underscore")
+
+module.exports = (robot) ->
+
+  robot.respond /archer quote$/i, (msg) ->
+    msg
+      .http("http://en.wikiquote.org/wiki/Archer_(TV_series)")
+      .header("User-Agent: Archerbot for Hubot (+https://github.com/github/hubot-scripts)")
+      .get() (err, res, body) ->
+        quotes = parse_html(body, "dl")
+        quote = get_quote msg, quotes
+
+get_quote = (msg, quotes) ->
+
+  nodeChildren = _.flatten childern_of_type(quotes[Math.floor(Math.random() * quotes.length)])
+  quote = (textNode.data for textNode in nodeChildren).join(' ').replace(/^\s+|\s+$/g, '')
+
+  msg.send quote
+
+# Helpers
+parse_html = (html, selector) ->
+  handler = new HtmlParser.DefaultHandler((() ->), ignoreWhitespace: true)
+  parser  = new HtmlParser.Parser handler
+
+  parser.parseComplete html
+  Select handler.dom, selector
+
+childern_of_type = (root) ->
+  return [root] if root?.type is "text"
+
+  if root?.children?.length > 0
+    return (childern_of_type(child) for child in root.children)
+
+get_dom = (xml) ->
+  body = JsDom.jsdom(xml)
+  throw Error("No XML data returned.") if body.getElementsByTagName("FilterReturn")[0].childNodes.length == 0
+  return body


yo mama!

yo mama!

error: pathspec 'src/scripts/yomama.coffee' did not match any file(s) known to git

--- src/scripts/yomama.coffee
+++ src/scripts/yomama.coffee
@@ -0,0 +1,14 @@
+# Returns a random Yo Mama joke
+#
+# yo mama joke me
+
+module.exports = (robot) ->
+  robot.respond /(yo\s?mama( joke)?)( me)?/i, (msg) ->
+    yoMama msg, (url) ->
+      msg.send url
+
+yoMama = (msg, cb) ->
+  msg.http('http://www.yomamajokesdb.com/random-joke/')
+    .get() (err, res, body) ->
+      cb body.match(/<div id="random">([^\<]+)<\/div>/)?[1] || "Sorry, this joke is very inappropriate (or could not be found)"
+