Reformat comments in edit-session.coffee

--- src/edit-session.coffee
+++ src/edit-session.coffee
@@ -13,7 +13,31 @@
 Subscriber = require 'subscriber'
 TextMateScopeSelector = require('first-mate').ScopeSelector

-# An `EditSession` manages the states between {Editor}s, {TextBuffer}s, and the project as a whole.
+# Public: The core model of Atom.
+#
+# An {EditSession} represents a unique view of each document, with it's own
+# {Cursor}s and scroll position.
+#
+# For instance if a user creates a split, Atom creates a second {EditSession}
+# but both {EditSessions} interact with the same buffer underlying buffer. So
+# if you type in either buffer it immediately appears in both but if you scroll
+# in one it doesn't scroll the other.
+#
+# Almost all extension will interact primiarily with this class as it provides
+# access to objects you'll most commonly interact with. To access it you'll
+# want to register a callback on {RootView} which will be fired once for every
+# existing {EditSession} as well as any future {EditSession}s.
+#
+# ## Example
+# ```coffeescript
+# global.rootView.eachEditSession (editSession) ->
+#   editSession.insertText('Hello World')
+# ```
+#
+# ## Collaboration builtin
+#
+# FIXME: Describe how there are both local and remote cursors and selections and
+# why that is.
 module.exports =
 class EditSession
   _.extend @prototype, EventEmitter
@@ -22,8 +46,6 @@
   @acceptsDocuments: true

   registerDeserializer(this)
-
-  ### Internal ###

   @version: 5

@@ -39,6 +61,7 @@
   remoteSelections: null
   suppressSelectionMerging: false

+  # Private:
   constructor: (optionsOrState) ->
     @cursors = []
     @remoteCursors = []
@@ -85,6 +108,7 @@

     project.addEditSession(this) if registerEditSession

+  # Private:
   setBuffer: (@buffer) ->
     @buffer.retain()
     @subscribe @buffer, "path-changed", =>
@@ -95,6 +119,7 @@
     @subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
     @preserveCursorPositionOnBufferReload()

+  # Private:
   setDisplayBuffer: (@displayBuffer) ->
     @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
     @subscribe @displayBuffer, "changed", (e) => @trigger 'screen-lines-changed', e
@@ -102,9 +127,11 @@
     @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
     @subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @trigger 'soft-wrap-changed', args...

+  # Private:
   getViewClass: ->
     require 'editor'

+  # Private:
   destroy: ->
     return if @destroyed
     @destroyed = true
@@ -117,10 +144,13 @@
     @trigger 'destroyed'
     @off()

+  # Private:
   serialize: -> @state.clone()
+
+  # Private:
   getState: -> @state

-  # Creates an {EditSession} with the same initial state
+  # Private: Creates an {EditSession} with the same initial state
   copy: ->
     tabLength = @getTabLength()
     displayBuffer = @displayBuffer.copy()
@@ -132,9 +162,7 @@
       marker.copy(editSessionId: newEditSession.id, preserveFolds: true)
     newEditSession

-  ### Public ###
-
-  # Retrieves the filename of the open file.
+  # Public: Retrieves the filename of the open file.
   #
   # This is `'untitled'` if the file is new and not saved to the disk.
   #
@@ -145,9 +173,10 @@
     else
       'untitled'

-  # Retrieves the filename of the open file, followed by a dash, then the file's directory.
-  #
-  # If the file is brand new, the title is `untitled`.
+  # Public: Retrieves the filename and path of the open file.
+  #
+  # It has the follows the following format, `<filename> - <directory>`. If the
+  # file is brand new, the title is `untitled`.
   #
   # Returns a {String}.
   getLongTitle: ->
@@ -158,7 +187,7 @@
     else
       'untitled'

-  # Compares two `EditSession`s to determine equality.
+  # Public: Compares two `EditSession`s to determine equality.
   #
   # Equality is based on the condition that:
   #
@@ -174,27 +203,19 @@
       @getScrollLeft() == other.getScrollLeft() and
       @getCursorScreenPosition().isEqual(other.getCursorScreenPosition())

+  # Public: Controls visiblity based on the given Boolean.
   setVisible: (visible) -> @displayBuffer.setVisible(visible)

-  # Defines the value of the `EditSession`'s `scrollTop` property.
-  #
-  # scrollTop - A {Number} defining the `scrollTop`, in pixels.
+  # Public: FIXME: I don't understand this.
   setScrollTop: (scrollTop) -> @state.set('scrollTop', scrollTop)

-  # Gets the value of the `EditSession`'s `scrollTop` property.
-  #
-  # Returns a {Number} defining the `scrollTop`, in pixels.
-  getScrollTop: ->
-    @state.get('scrollTop') ? 0
-
-  # Defines the value of the `EditSession`'s `scrollLeft` property.
-  #
-  # scrollLeft - A {Number} defining the `scrollLeft`, in pixels.
+  # Public: Returns the current `scrollTop` value
+  getScrollTop: -> @state.get('scrollTop') ? 0
+
+  # Public: FIXME: I don't understand this.
   setScrollLeft: (scrollLeft) -> @state.set('scrollLeft', scrollLeft)

-  # Gets the value of the `EditSession`'s `scrollLeft` property.
-  #
-  # Returns a {Number} defining the `scrollLeft`, in pixels.
+  # Public: Returns the current `scrollLeft` value
   getScrollLeft: -> @state.get('scrollLeft')

   # Set the number of characters that can be displayed horizontally in the
@@ -204,90 +225,85 @@
   setEditorWidthInChars: (editorWidthInChars) ->
     @displayBuffer.setEditorWidthInChars(editorWidthInChars)

+  # Public: Sets the column at which columsn will soft wrap
   getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()

-  getSoftTabs: ->
-    @state.get('softTabs')
-
-  # Defines whether to use soft tabs.
-  #
-  # softTabs - A {Boolean} which, if `true`, indicates that you want soft tabs.
+  # Public: Returns whether soft tabs are enabled or not.
+  getSoftTabs: -> @state.get('softTabs')
+
+  # Public: Controls whether soft tabs are enabled or not.
   setSoftTabs: (softTabs) ->
     @state.set('softTabs', softTabs)

-  # Retrieves whether soft tabs are enabled.
-  #
-  # Returns a {Boolean}.
+  # Public: Returns whether soft wrap is enabled or not.
   getSoftWrap: -> @displayBuffer.getSoftWrap()

-  # Defines whether to use soft wrapping of text.
-  #
-  # softTabs - A {Boolean} which, if `true`, indicates that you want soft wraps.
+  # Public: Controls whether soft tabs are enabled or not.
   setSoftWrap: (softWrap) -> @displayBuffer.setSoftWrap(softWrap)

-  # Retrieves that character used to indicate a tab.
+  # Public: Returns that String used to indicate a tab.
   #
   # If soft tabs are enabled, this is a space (`" "`) times the {.getTabLength} value.
   # Otherwise, it's a tab (`\t`).
-  #
-  # Returns a {String}.
   getTabText: -> @buildIndentString(1)

-  # Retrieves the current tab length.
-  #
-  # Returns a {Number}.
+  # Public: Returns the current tab length.
   getTabLength: -> @displayBuffer.getTabLength()

-  # Specifies the tab length.
-  #
-  # tabLength - A {Number} that defines the new tab length.
+  # Public: Sets the current tab length.
   setTabLength: (tabLength) -> @displayBuffer.setTabLength(tabLength)

-  # Given a position, this clips it to a real position.
+  # Public: Given a position, this clips it to a real position.
   #
   # For example, if `position`'s row exceeds the row count of the buffer,
   # or if its column goes beyond a line's length, this "sanitizes" the value
   # to a real position.
   #
-  # position - The {Point} to clip
-  #
-  # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
+  # * position:
+  #   The {Point} to clip
+  #
+  # Returns the new, clipped {Point}. Note that this could be the same as
+  #   `position` if no clipping was performed.
   clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)

-  # Given a range, this clips it to a real range.
+  # Public: Given a range, this clips it to a real range.
   #
   # For example, if `range`'s row exceeds the row count of the buffer,
   # or if its column goes beyond a line's length, this "sanitizes" the value
   # to a real range.
   #
-  # range - The {Point} to clip
-  #
-  # Returns the new, clipped {Point}. Note that this could be the same as `range` if no clipping was performed.
+  # * range:
+  #   The {Range} to clip
+  #
+  # Returns the new, clipped {Range}. Note that this could be the same as
+  #   `range` if no clipping was performed.
   clipBufferRange: (range) -> @buffer.clipRange(range)

-  # Given a buffer row, this retrieves the indentation level.
-  #
-  # bufferRow - A {Number} indicating the buffer row.
-  #
-  # Returns the indentation level as a {Number}.
+  # Public: Returns the indentation level of the given a buffer row
+  #
+  # * bufferRow:
+  #   A Number indicating the buffer row.
   indentationForBufferRow: (bufferRow) ->
     @indentLevelForLine(@lineForBufferRow(bufferRow))

-  # This specifies the new indentation level for a buffer row.
-  #
-  # bufferRow - A {Number} indicating the buffer row.
-  # newLevel - A {Number} indicating the new indentation level.
+  # Public: Sets the indentation level for the given buffer row.
+  #
+  # * bufferRow:
+  #   A {Number} indicating the buffer row.
+  # * newLevel:
+  #   A {Number} indicating the new indentation level.
   setIndentationForBufferRow: (bufferRow, newLevel) ->
     currentLevel = @indentationForBufferRow(bufferRow)
     currentIndentString = @buildIndentString(currentLevel)
     newIndentString = @buildIndentString(newLevel)
     @buffer.change([[bufferRow, 0], [bufferRow, currentIndentString.length]], newIndentString)

-  # Given a line, this retrieves the indentation level.
-  #
-  # line - A {String} in the current {TextBuffer}.
-  #
-  # Returns a {Number}.
+  # Public: Returns the indentation level of the given line of text.
+  #
+  # * line:
+  #   A {String} in the current buffer.
+  #
+  # Returns a {Number} or 0 if the text isn't found within the buffer.
   indentLevelForLine: (line) ->
     if match = line.match(/^[\t ]+/)
       leadingWhitespace = match[0]
@@ -297,7 +313,7 @@
     else
       0

-  # Constructs the string used for tabs.
+  # Private: Constructs the string used for tabs.
   buildIndentString: (number) ->
     if @getSoftTabs()
       _.multiplyString(" ", number * @getTabLength())
@@ -325,22 +341,16 @@
   # {Delegates to: TextBuffer.setText}
   setText: (text) -> @buffer.setText(text)

-  # Retrieves the current buffer.
-  #
-  # Returns a {TextBuffer}.
+  # Private: Retrieves the current {TextBuffer}.
   getBuffer: -> @buffer

-  # Retrieves the current buffer's URI.
-  #
-  # Returns a {String}.
+  # Public: Retrieves the current buffer's URI.
   getUri: -> @buffer.getUri()

   # {Delegates to: TextBuffer.isRowBlank}
   isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow)

-  # Test if an entire row is a comment
-  #
-  # Returns a {Boolean}.
+  # Public: Determine if the given row is entirely a comment
   isBufferRowCommented: (bufferRow) ->
     if match = @lineForBufferRow(bufferRow).match(/\S/)
       scopes = @tokenForBufferPosition([bufferRow, match.index]).scopes
@@ -373,10 +383,7 @@
   # {Delegates to: TextBuffer.isModified}
   isModified: -> @buffer.isModified()

-  # Identifies if the modified buffer should let you know if it's closing
-  # without being saved.
-  #
-  # Returns a {Boolean}.
+  # Public: Determines if the user should be prompted to save before closing.
   shouldPromptToSave: -> @isModified() and not @buffer.hasMultipleEditors()

   # {Delegates to: DisplayBuffer.screenPositionForBufferPosition}
@@ -415,37 +422,41 @@
   # {Delegates to: DisplayBuffer.scopesForBufferPosition}
   scopesForBufferPosition: (bufferPosition) -> @displayBuffer.scopesForBufferPosition(bufferPosition)

+  # Public: ?
   bufferRangeForScopeAtCursor: (selector) ->
     @displayBuffer.bufferRangeForScopeAtPosition(selector, @getCursorBufferPosition())

   # {Delegates to: DisplayBuffer.tokenForBufferPosition}
   tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)

-  # Retrieves the grammar's token scopes for the line with the most recently added cursor.
+  # Public: Retrieves the grammar's token scopes for the line with the most
+  # recently added cursor.
   #
   # Returns an {Array} of {String}s.
   getCursorScopes: -> @getCursor().getScopes()

-  # Inserts text at the current cursor positions
-  #
-  # text - A {String} representing the text to insert.
-  # options - A set of options equivalent to {Selection.insertText}
+  # Public: Inserts text at the current cursor positions
+  #
+  # * text:
+  #   A String representing the text to insert.
+  # * options:
+  #   + A set of options equivalent to {Selection.insertText}
   insertText: (text, options={}) ->
     options.autoIndentNewline ?= @shouldAutoIndent()
     options.autoDecreaseIndent ?= @shouldAutoIndent()
     @mutateSelectedText (selection) -> selection.insertText(text, options)

-  # Inserts a new line at the current cursor positions.
+  # Public: Inserts a new line at the current cursor positions.
   insertNewline: ->
     @insertText('\n')

-  # Inserts a new line below the current cursor positions.
+  # Public: Inserts a new line below the current cursor positions.
   insertNewlineBelow: ->
     @transact =>
       @moveCursorToEndOfLine()
       @insertNewline()

-  # Inserts a new line above the current cursor positions.
+  # Public: Inserts a new line above the current cursor positions.
   insertNewlineAbove: ->
     @transact =>
       onFirstLine = @getCursorBufferPosition().row is 0
@@ -454,91 +465,99 @@
       @insertNewline()
       @moveCursorUp() if onFirstLine

-  # Indents the current line.
-  #
-  # options - A set of options equivalent to {Selection.indent}.
+  # Public: Indents the current line.
+  #
+  # * options
+  #    + A set of options equivalent to {Selection.indent}.
   indent: (options={})->
     options.autoIndent ?= @shouldAutoIndent()
     @mutateSelectedText (selection) -> selection.indent(options)

-  # Performs a backspace, removing the character found behind the cursor position.
+  # Public: Removes the character found behind the current cursor position.
+  #
+  # FIXME: Does this remove content from all cursors or the last one?
   backspace: ->
     @mutateSelectedText (selection) -> selection.backspace()

-  # Performs a backspace to the beginning of the current word, removing characters found there.
+  # Public: Removes all characters from the current cursor position until the
+  # beginging of the current word.
   backspaceToBeginningOfWord: ->
     @mutateSelectedText (selection) -> selection.backspaceToBeginningOfWord()

-  # Performs a backspace to the beginning of the current line, removing characters found there.
+  # Public: Removes all characters from the current cursor position to the start
+  # of the line.
   backspaceToBeginningOfLine: ->
     @mutateSelectedText (selection) -> selection.backspaceToBeginningOfLine()

-  # Performs a delete, removing the character found ahead of the cursor position.
+  # Public: Removes the current selection or the next character after the
+  # cursor.
   delete: ->
     @mutateSelectedText (selection) -> selection.delete()

-  # Performs a delete to the end of the current word, removing characters found there.
+  # Public: Removes all characters from the cursor until the end of the current
+  # word.
   deleteToEndOfWord: ->
     @mutateSelectedText (selection) -> selection.deleteToEndOfWord()

-  # Deletes the entire line.
+  # Public: Deletes the entire line.
   deleteLine: ->
     @mutateSelectedText (selection) -> selection.deleteLine()

-  # Indents the selected rows.
+  # Public: Indents the currently selected rows.
+  #
+  # FIXME: what does this do if no selection?
   indentSelectedRows: ->
     @mutateSelectedText (selection) -> selection.indentSelectedRows()

-  # Outdents the selected rows.
+  # Public: Outdents the selected rows.
+  #
+  # FIXME: what does this do if no selection?
   outdentSelectedRows: ->
     @mutateSelectedText (selection) -> selection.outdentSelectedRows()

-  # Wraps the lines within a selection in comments.
+  # Public: Wraps the lines within a selection in comments.
   #
   # If the language doesn't have comments, nothing happens.
-  #
-  # selection - The {Selection} to comment
   #
   # Returns an {Array} of the commented {Ranges}.
   toggleLineCommentsInSelection: ->
     @mutateSelectedText (selection) -> selection.toggleLineComments()

+  # Public: Indents selected lines based on grammar's suggested indent levels.
   autoIndentSelectedRows: ->
     @mutateSelectedText (selection) -> selection.autoIndentSelectedRows()

-  # Given a buffer range, this converts all `\t` characters to the appropriate {.getTabText} value.
-  #
-  # bufferRange - The {Range} to perform the replace in
+  # Public: Converts all indents to the current {.tabTabText} given a {Range}.
   normalizeTabsInBufferRange: (bufferRange) ->
     return unless @getSoftTabs()
     @scanInBufferRange /\t/, bufferRange, ({replace}) => replace(@getTabText())

-  # Performs a cut to the end of the current line.
-  #
-  # Characters are removed, but the text remains in the clipboard.
+  # Public: Copies and removes all characters from cursor to the end of the
+  # line.
   cutToEndOfLine: ->
     maintainPasteboard = false
     @mutateSelectedText (selection) ->
       selection.cutToEndOfLine(maintainPasteboard)
       maintainPasteboard = true

-  # Cuts the selected text.
+  # Public: Cuts the selected text.
   cutSelectedText: ->
     maintainPasteboard = false
     @mutateSelectedText (selection) ->
       selection.cut(maintainPasteboard)
       maintainPasteboard = true

-  # Copies the selected text.
+  # Public: Copies the selected text.
   copySelectedText: ->
     maintainPasteboard = false
     for selection in @getSelections()
       selection.copy(maintainPasteboard)
       maintainPasteboard = true

-  # Pastes the text in the clipboard.
-  #
-  # options - A set of options equivalent to {Selection.insertText}.
+  # Public: Pastes the text in the clipboard.
+  #
+  # * options:
+  #    + A set of options equivalent to {Selection.insertText}.
   pasteText: (options={}) ->
     [text, metadata] = pasteboard.read()

@@ -547,48 +566,45 @@

     @insertText(text, options)

-  # Undos the last {TextBuffer} change.
+  # Public: Undoes the last change.
   undo: ->
     @buffer.undo(this)

-  # Redos the last {TextBuffer} change.
+  # Pulic: Redoes the last change.
   redo: ->
     @buffer.redo(this)

-  # Folds all the rows.
+  # Public: Folds all the rows.
   foldAll: ->
     @languageMode.foldAll()

-  # Unfolds all the rows.
+  # Public: Unfolds all the rows.
   unfoldAll: ->
     @languageMode.unfoldAll()

+  # Public: Creates a fold for each section at the given indent level.
   foldAllAtIndentLevel: (indentLevel) ->
     @languageMode.foldAllAtIndentLevel(indentLevel)

-  # Folds the current row.
+  # Public: Folds the current row.
   foldCurrentRow: ->
     bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row
     @foldBufferRow(bufferRow)

-  # Given a buffer row, this folds it.
-  #
-  # bufferRow - A {Number} indicating the buffer row
+  # Public: Folds a give buffer row.
   foldBufferRow: (bufferRow) ->
     @languageMode.foldBufferRow(bufferRow)

-  # Unfolds the current row.
+  # Public: Unfolds the current row.
   unfoldCurrentRow: ->
     bufferRow = @bufferPositionForScreenPosition(@getCursorScreenPosition()).row
     @unfoldBufferRow(bufferRow)

-  # Given a buffer row, this unfolds it.
-  #
-  # bufferRow - A {Number} indicating the buffer row
+  # Public: Unfolds a given a buffer row.
   unfoldBufferRow: (bufferRow) ->
     @languageMode.unfoldBufferRow(bufferRow)

-  # Folds all selections.
+  # Public: Folds all selections.
   foldSelection: ->
     selection.fold() for selection in @getSelections()

@@ -604,32 +620,20 @@
   destroyFoldsContainingBufferRow: (bufferRow) ->
     @displayBuffer.destroyFoldsContainingBufferRow(bufferRow)

-  # Removes any {Fold}s found that intersect the given buffer row.
-  #
-  # bufferRow - The buffer row {Number} to check against
+  # Public: Removes any {Fold}s found that intersect the given buffer row.
   destroyFoldsIntersectingBufferRange: (bufferRange) ->
     for row in [bufferRange.start.row..bufferRange.end.row]
       @destroyFoldsContainingBufferRow(row)

-  # Determines if the given row that the cursor is at is folded.
-  #
-  # Returns `true` if the row is folded, `false` otherwise.
+  # Public: Returns whether the current row is folded.
   isFoldedAtCursorRow: ->
     @isFoldedAtScreenRow(@getCursorScreenRow())

-  # Determines if the given buffer row is folded.
-  #
-  # bufferRow - A {Number} indicating the buffer row.
-  #
-  # Returns `true` if the buffer row is folded, `false` otherwise.
+  # Public: Returns whether a given buffer row if folded
   isFoldedAtBufferRow: (bufferRow) ->
     @displayBuffer.isFoldedAtBufferRow(bufferRow)

-  # Determines if the given screen row is folded.
-  #
-  # screenRow - A {Number} indicating the screen row.
-  #
-  # Returns `true` if the screen row is folded, `false` otherwise.
+  # Public: Returns whether a given screen row if folded
   isFoldedAtScreenRow: (screenRow) ->
     @displayBuffer.isFoldedAtScreenRow(screenRow)

@@ -641,47 +645,39 @@
   largestFoldStartingAtScreenRow: (screenRow) ->
     @displayBuffer.largestFoldStartingAtScreenRow(screenRow)

-  # Given a buffer row, this returns a suggested indentation level.
-  #
-  # The indentation level provided is based on the current language.
-  #
-  # bufferRow - A {Number} indicating the buffer row
-  #
-  # Returns a {Number}.
+  # Public: Suggests the indent for the given buffer row.
   suggestedIndentForBufferRow: (bufferRow) ->
     @languageMode.suggestedIndentForBufferRow(bufferRow)

-  # Indents all the rows between two buffer rows.
-  #
-  # startRow - The row {Number} to start at
-  # endRow - The row {Number} to end at
+  # Public: Indents all the rows between two buffer rows.
+  #
+  # * startRow: The row {Number} to start at (inclusive)
+  # * endRow: The row {Number} to end at (inclusive)
   autoIndentBufferRows: (startRow, endRow) ->
     @languageMode.autoIndentBufferRows(startRow, endRow)

-  # Given a buffer row, this indents it.
-  #
-  # bufferRow - The row {Number}
+  # Public: Indents the given buffer row to it's suggested level.
   autoIndentBufferRow: (bufferRow) ->
     @languageMode.autoIndentBufferRow(bufferRow)

-  # Given a buffer row, this decreases the indentation.
-  #
-  # bufferRow - The row {Number}
+  # Public:
+  #
+  # FIXME: What does this do?
   autoDecreaseIndentForBufferRow: (bufferRow) ->
     @languageMode.autoDecreaseIndentForBufferRow(bufferRow)

-  # Wraps the lines between two rows in comments.
+  # Public: Wraps the lines between two rows in comments.
   #
   # If the language doesn't have comments, nothing happens.
   #
-  # startRow - The row {Number} to start at
-  # endRow - The row {Number} to end at
+  # startRow - The row {Number} to start at (inclusive)
+  # endRow - The row {Number} to end at (inclusive)
   #
   # Returns an {Array} of the commented {Ranges}.
   toggleLineCommentsForBufferRows: (start, end) ->
     @languageMode.toggleLineCommentsForBufferRows(start, end)

-  # Moves the selected line up one row.
+  # Public: Moves the selected line up one row.
   moveLineUp: ->
     selection = @getSelectedBufferRange()
     return if selection.start.row is 0
@@ -715,7 +711,7 @@

       @setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)

-  # Moves the selected line down one row.
+  # Public: Moves the selected line down one row.
   moveLineDown: ->
     selection = @getSelectedBufferRange()
     lastRow = @buffer.getLastRow()
@@ -753,9 +749,10 @@

       @setSelectedBufferRange(selection.translate([1]), preserveFolds: true)

-  # Duplicates the current line.
-  #
-  # If more than one cursor is present, only the most recently added one is considered.
+  # Public: Duplicates the current line.
+  #
+  # If more than one cursor is present, only the most recently added one is
+  # duplicated.
   duplicateLine: ->
     return unless @getSelection().isEmpty()

@@ -779,11 +776,11 @@
       @setCursorScreenPosition(@getCursorScreenPosition().translate([1]))
       @foldCurrentRow() if cursorRowFolded

-  ### Internal ###
-
+  # Private:
   mutateSelectedText: (fn) ->
     @transact => fn(selection) for selection in @getSelections()

+  # Private:
   replaceSelectedText: (options={}, fn) ->
     {selectWordIfEmpty} = options
     @mutateSelectedText (selection) ->
@@ -795,15 +792,15 @@
       selection.insertText(fn(text))
       selection.setBufferRange(range)

-  ### Public ###
-
-  # Returns a valid {DisplayBufferMarker} object for the given id if one exists.
+  # Public: Returns a valid {DisplayBufferMarker} object for the given id.
   getMarker: (id) ->
     @displayBuffer.getMarker(id)

+  # Public: Returns all {DisplayBufferMarker}s.
   getMarkers: ->
     @displayBuffer.getMarkers()

+  # Public: Returns all {DisplayBufferMarkers} that match all given attributes.
   findMarkers: (attributes) ->
     @displayBuffer.findMarkers(attributes)

@@ -831,51 +828,37 @@
   getMarkerCount: ->
     @buffer.getMarkerCount()

-  # Returns `true` if there are multiple cursors in the edit session.
-  #
-  # Returns a {Boolean}.
+  # Public: Determines if there are multiple cursors.
   hasMultipleCursors: ->
     @getCursors().length > 1

+  # Public: Returns an Array of all {Cursor}s, including cursors representing
+  # remote users.
   getAllCursors: ->
     @getCursors().concat(@getRemoteCursors())

-  # Retrieves all the cursors.
-  #
-  # Returns an {Array} of {Cursor}s.
+  # Public: Returns an Array of all local {Cursor}s.
   getCursors: -> new Array(@cursors...)

-  # Retrieves the most recently added cursor.
-  #
-  # Returns a {Cursor}.
+  # Public: Returns the most recently added {Cursor}.
   getCursor: ->
     _.last(@cursors)

+  # Public: Returns an Array of all remove {Cursor}s.
   getRemoteCursors: -> new Array(@remoteCursors...)

-  # Adds a cursor at the provided `screenPosition`.
-  #
-  # screenPosition - An {Array} of two numbers: the screen row, and the screen column.
-  #
-  # Returns the new {Cursor}.
+  # Public: Adds and returns a cursor at the given screen position.
   addCursorAtScreenPosition: (screenPosition) ->
     @markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
     @getLastSelection().cursor

-  # Adds a cursor at the provided `bufferPosition`.
-  #
-  # bufferPosition - An {Array} of two numbers: the buffer row, and the buffer column.
-  #
-  # Returns the new {Cursor}.
+  # Public: Adds and returns a cursor at the given buffer position.
   addCursorAtBufferPosition: (bufferPosition) ->
     @markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
     @getLastSelection().cursor

-  # Adds a cursor to the `EditSession`.
-  #
-  # marker - The marker where the cursor should be added
-  #
-  # Returns the new {Cursor}.
+  # Public: Adds and returns a cursor at the given {DisplayBufferMarker}
+  # position.
   addCursor: (marker) ->
     cursor = new Cursor(editSession: this, marker: marker)
     if marker.isLocal()
@@ -885,18 +868,16 @@
     @trigger 'cursor-added', cursor
     cursor

-  # Removes a cursor from the `EditSession`.
-  #
-  # cursor - The cursor to remove
-  #
-  # Returns the removed {Cursor}.
+  # Public: Removes and returns a cursor from the `EditSession`.
   removeCursor: (cursor) ->
     _.remove(@cursors, cursor)

-  # Creates a new selection at the given marker.
-  #
-  # marker - The marker to highlight
-  # options - A hash of options that pertain to the {Selection} constructor.
+  # Public: Creates a new selection at the given marker.
+  #
+  # * marker:
+  #   The {DisplayBufferMarker} to highlight
+  # * options:
+  #   + A hash of options that pertain to the {Selection} constructor.
   #
   # Returns the new {Selection}.
   addSelection: (marker, options={}) ->
@@ -920,27 +901,35 @@
       @trigger 'selection-added', selection
       selection

-  # Given a buffer range, this adds a new selection for it.
-  #
-  # bufferRange - A {Range} in the buffer
-  # options - A hash of options
+  # Public: Given a buffer range, this adds a new selection for it.
+  #
+  # * bufferRange:
+  #   A {Range} in the buffer
+  # * options:
+  #    + A hash of options for {.markBufferRange}
   #
   # Returns the new {Selection}.
   addSelectionForBufferRange: (bufferRange, options={}) ->
     @markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
     @getLastSelection()

-  # Given a buffer range, this removes all previous selections and creates a new selection for it.
-  #
-  # bufferRange - A {Range} in the buffer
-  # options - A hash of options
+  # Public: Given a buffer range, this removes all previous selections and
+  # creates a new selection for it.
+  #
+  # * bufferRange:
+  #   A {Range} in the buffer
+  # * options:
+  #    + A hash of options for {.setSelectedBufferRanges}
   setSelectedBufferRange: (bufferRange, options) ->
     @setSelectedBufferRanges([bufferRange], options)

-  # Given an array of buffer ranges, this removes all previous selections and creates new selections for them.
-  #
-  # bufferRanges - An {Array} of {Range}s in the buffer
-  # options - A hash of options
+  # Public: Given an array of buffer ranges, this removes all previous
+  # selections and creates new selections for them.
+  #
+  # * bufferRange:
+  #   A {Range} in the buffer
+  # * options:
+  #    + A hash of options for {.setSelectedBufferRanges}
   setSelectedBufferRanges: (bufferRanges, options={}) ->
     throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length

@@ -955,20 +944,25 @@
         else
           @addSelectionForBufferRange(bufferRange, options)

-  # Unselects a given selection.
-  #
-  # selection - The {Selection} to remove.
+  # Public: Unselects a given selection.
+  #
+  # * selection - The {Selection} to remove.
   removeSelection: (selection) ->
     if selection.isLocal()
       _.remove(@selections, selection)
     else
       _.remove(@remoteSelections, selection)

-  # Clears every selection. TODO
+  # Public: Clears every selection.
+  #
+  # TODO: Is this still to be done?
   clearSelections: ->
     @consolidateSelections()
     @getSelection().clear()

+  # Public:
+  #
+  # FIXME: What does this do?
   consolidateSelections: ->
     selections = @getSelections()
     if selections.length > 1
@@ -977,270 +971,278 @@
     else
       false

+  # Public: Returns all selections, including remote selections.
   getAllSelections: ->
     @getSelections().concat(@getRemoteSelections())

-  # Gets all the selections.
+  # Public: Gets all local selections.
   #
   # Returns an {Array} of {Selection}s.
   getSelections: -> new Array(@selections...)

-  # Gets the selection at the specified index.
-  #
-  # index - The id {Number} of the selection
-  #
-  # Returns a {Selection}.
+  # Public: Returns the selection at the specified index.
   getSelection: (index) ->
     index ?= @selections.length - 1
     @selections[index]

-  # Gets the last selection, _i.e._ the most recently added.
-  #
-  # Returns a {Selection}.
+  # Public: Returns the most recently added {Selection}
   getLastSelection: ->
     _.last(@selections)

+  # Public: Returns all remote selections.
   getRemoteSelections: -> new Array(@remoteSelections...)

-  # Gets all selections, ordered by their position in the buffer.
+  # Public: Gets all local selections, ordered by their position in the buffer.
   #
   # Returns an {Array} of {Selection}s.
   getSelectionsOrderedByBufferPosition: ->
     @getSelections().sort (a, b) -> a.compare(b)

+  # Public: Gets all remote selections, ordered by their position in the buffer.
+  #
+  # Returns an {Array} of {Selection}s.
   getRemoteSelectionsOrderedByBufferPosition: ->
     @getRemoteSelections().sort (a, b) -> a.compare(b)

-  # Gets the very last selection, as it's ordered in the buffer.
+  # Public: Gets the very last local selection in the buffer.
   #
   # Returns a {Selection}.
   getLastSelectionInBuffer: ->
     _.last(@getSelectionsOrderedByBufferPosition())

-  # Determines if a given buffer range is included in a {Selection}.
-  #
-  # bufferRange - The {Range} you're checking against
+  # Public: Determines if a given buffer range is included in a {Selection}.
+  #
+  # * bufferRange:
+  #   The {Range} you're checking against
   #
   # Returns a {Boolean}.
   selectionIntersectsBufferRange: (bufferRange) ->
     _.any @getSelections(), (selection) ->
       selection.intersectsBufferRange(bufferRange)

-  # Moves every cursor to a given screen position.
-  #
-  # position - An {Array} of two numbers: the screen row, and the screen column.
-  # options - An object with properties based on {Cursor.setScreenPosition}
-  #
+  # Public: Moves every local cursor to a given screen position.
+  #
+  # * position:
+  #   An {Array} of two numbers: the screen row, and the screen column.
+  # * options:
+  #   An object with properties based on {Cursor.setScreenPosition}
   setCursorScreenPosition: (position, options) ->
     @moveCursors (cursor) -> cursor.setScreenPosition(position, options)

-  # Gets the current screen position of the most recently added {Cursor}.
+  # Public: Gets the current screen position of the most recently added
+  # local {Cursor}.
   #
   # Returns an {Array} of two numbers: the screen row, and the screen column.
   getCursorScreenPosition: ->
     @getCursor().getScreenPosition()

-  # Gets the screen row of the most recently added {Cursor}.
+  # Public: Gets the screen row of the most recently added local {Cursor}.
   #
   # Returns the screen row {Number}.
   getCursorScreenRow: ->
     @getCursor().getScreenRow()

-  # Moves every cursor to a given buffer position.
-  #
-  # position - An {Array} of two numbers: the buffer row, and the buffer column.
-  # options - An object with properties based on {Cursor.setBufferPosition}
-  #
+  # Public: Moves every cursor to a given buffer position.
+  #
+  # * position:
+  #   An {Array} of two numbers: the buffer row, and the buffer column.
+  # * options:
+  #    + An object with properties based on {Cursor.setBufferPosition}
   setCursorBufferPosition: (position, options) ->
     @moveCursors (cursor) -> cursor.setBufferPosition(position, options)

-  # Gets the current buffer position of the most recently added {Cursor}.
+  # Public: Gets the current buffer position of the most recently added {Cursor}.
   #
   # Returns an {Array} of two numbers: the buffer row, and the buffer column.
   getCursorBufferPosition: ->
     @getCursor().getBufferPosition()

-  # Gets the screen range of the most recently added {Selection}.
-  #
-  # Returns a {Range}.
+  # Public: Returns the screen {Range} of the most recently added local
+  # {Selection}.
   getSelectedScreenRange: ->
     @getLastSelection().getScreenRange()

-  # Gets the buffer range of the most recently added {Selection}.
-  #
-  # Returns a {Range}.
+  # Public: Returns the buffer {Range} of the most recently added local
+  # {Selection}.
   getSelectedBufferRange: ->
     @getLastSelection().getBufferRange()

-  # Gets the buffer ranges of all the {Selection}s.
-  #
-  # This is ordered by their position in the file itself.
-  #
-  # Returns an {Array} of {Range}s.
+  # Public: Gets an Array of buffer {Range}s of all the local {Selection}s.
+  #
+  # Sorted by their position in the file itself.
   getSelectedBufferRanges: ->
     selection.getBufferRange() for selection in @getSelectionsOrderedByBufferPosition()

+  # Public: Gets an Array of buffer {Range}s of all the remote {Selection}s.
+  #
+  # Sorted by their position in the file itself.
   getRemoteSelectedBufferRanges: ->
     selection.getBufferRange() for selection in @getRemoteSelectionsOrderedByBufferPosition()

-  # Gets the selected text of the most recently added {Selection}.
-  #
-  # Returns a {String}.
+  # Public: Returns the selected text of the most recently added local {Selection}.
   getSelectedText: ->
     @getLastSelection().getText()

-  # Given a buffer range, this retrieves the text in that range.
-  #
-  # range - The {Range} you're interested in
-  #
-  # Returns a {String} of the combined lines.
+  # Public: Returns the text within a given a buffer {Range}
   getTextInBufferRange: (range) ->
     @buffer.getTextInRange(range)

   setTextInBufferRange: (range, text) -> @getBuffer().change(range, text)

-  # Retrieves the range for the current paragraph.
-  #
-  # A paragraph is defined as a block of text surrounded by empty lines.
-  #
-  # Returns a {Range}.
+  # Public: Returns the text of the most recent local cursor's surrounding
+  # paragraph.
   getCurrentParagraphBufferRange: ->
     @getCursor().getCurrentParagraphBufferRange()

-  # Gets the word located under the most recently added {Cursor}.
-  #
-  # options - An object with properties based on {Cursor.getBeginningOfCurrentWordBufferPosition}.
-  #
-  # Returns a {String}.
+  # Public: Returns the word under the most recently added local {Cursor}.
+  #
+  # * options:
+  #    + An object with properties based on
+  #      {Cursor.getBeginningOfCurrentWordBufferPosition}.
   getWordUnderCursor: (options) ->
     @getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options))

-  # Moves every cursor up one row.
+  # Public: Moves every local cursor up one row.
   moveCursorUp: (lineCount) ->
     @moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)

-  # Moves every cursor down one row.
+  # Public: Moves every local cursor down one row.
   moveCursorDown: (lineCount) ->
     @moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)

-  # Moves every cursor left one column.
+  # Public: Moves every local cursor left one column.
   moveCursorLeft: ->
     @moveCursors (cursor) -> cursor.moveLeft(moveToEndOfSelection: true)

-  # Moves every cursor right one column.
+  # Public: Moves every local cursor right one column.
   moveCursorRight: ->
     @moveCursors (cursor) -> cursor.moveRight(moveToEndOfSelection: true)

-  # Moves every cursor to the top of the buffer.
+  # Public: Moves every local cursor to the top of the buffer.
   moveCursorToTop: ->
     @moveCursors (cursor) -> cursor.moveToTop()

-  # Moves every cursor to the bottom of the buffer.
+  # Public: Moves every local cursor to the bottom of the buffer.
   moveCursorToBottom: ->
     @moveCursors (cursor) -> cursor.moveToBottom()

-  # Moves every cursor to the beginning of the line.
+  # Public: Moves every local cursor to the beginning of the line.
   moveCursorToBeginningOfLine: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfLine()

-  # Moves every cursor to the first non-whitespace character of the line.
+  # Public: Moves every local cursor to the first non-whitespace character of the line.
   moveCursorToFirstCharacterOfLine: ->
     @moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()

-  # Moves every cursor to the end of the line.
+  # Public: Moves every local cursor to the end of the line.
   moveCursorToEndOfLine: ->
     @moveCursors (cursor) -> cursor.moveToEndOfLine()

-  # Moves every cursor to the beginning of the current word.
+  # Public: Moves every local cursor to the beginning of the current word.
   moveCursorToBeginningOfWord: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfWord()

-  # Moves every cursor to the end of the current word.
+  # Public: Moves every local cursor to the end of the current word.
   moveCursorToEndOfWord: ->
     @moveCursors (cursor) -> cursor.moveToEndOfWord()

-  # Moves every cursor to the beginning of the next word.
+  # Public: Moves every local cursor to the beginning of the next word.
   moveCursorToBeginningOfNextWord: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()

+  # Public: Moves every local cursor to the previous word boundary.
   moveCursorToPreviousWordBoundary: ->
     @moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()

+  # Public: Moves every local cursor to the next word boundary.
   moveCursorToNextWordBoundary: ->
     @moveCursors (cursor) -> cursor.moveToNextWordBoundary()

-  # Internal:
+  # Internal: Executes given function on all local cursors.
   moveCursors: (fn) ->
     fn(cursor) for cursor in @getCursors()
     @mergeCursors()

-  # Selects the text from the current cursor position to a given screen position.
-  #
-  # position - An instance of {Point}, with a given `row` and `column`.
+  # Public: Selects the text from the current cursor position to a given screen
+  # position.
+  #
+  # * position:
+  #   An instance of {Point}, with a given `row` and `column`.
   selectToScreenPosition: (position) ->
     lastSelection = @getLastSelection()
     lastSelection.selectToScreenPosition(position)
     @mergeIntersectingSelections(isReversed: lastSelection.isReversed())

-  # Selects the text one position right of the cursor.
+  # Public: Selects the text one position right of all local cursors.
   selectRight: ->
     @expandSelectionsForward (selection) => selection.selectRight()

-  # Selects the text one position left of the cursor.
+  # Public: Selects the text one position left of all local cursors.
   selectLeft: ->
     @expandSelectionsBackward (selection) => selection.selectLeft()

-  # Selects all the text one position above the cursor.
+  # Public: Selects all the text one position above all local cursors.
   selectUp: ->
     @expandSelectionsBackward (selection) => selection.selectUp()

-  # Selects all the text one position below the cursor.
+  # Public: Selects all the text one position below all local cursors.
   selectDown: ->
     @expandSelectionsForward (selection) => selection.selectDown()

-  # Selects all the text from the current cursor position to the top of the buffer.
+  # Public: Selects all the text from all local cursors to the top of the
+  # buffer.
   selectToTop: ->
     @expandSelectionsBackward (selection) => selection.selectToTop()

-  # Selects all the text in the buffer.
+  # Public: Selects all the text in the buffer.
   selectAll: ->
     @expandSelectionsForward (selection) => selection.selectAll()

-  # Selects all the text from the current cursor position to the bottom of the buffer.
+  # Public: Selects all the text from all local cursors to the bottom of the
+  # buffer.
   selectToBottom: ->
     @expandSelectionsForward (selection) => selection.selectToBottom()

-  # Selects all the text from the current cursor position to the beginning of the line.
+  # Public: Selects all the text from all local cursors to the beginning of each
+  # of their lines.
   selectToBeginningOfLine: ->
     @expandSelectionsBackward (selection) => selection.selectToBeginningOfLine()

-  # Selects to the first non-whitespace character of the line.
+  # Public: Selects to the first non-whitespace character of the line of all
+  # local cursors.
   selectToFirstCharacterOfLine: ->
     @expandSelectionsBackward (selection) => selection.selectToFirstCharacterOfLine()

-  # Selects all the text from the current cursor position to the end of the line.
+  # Public: Selects all the text from each local cursor to the end of their
+  # lines.
   selectToEndOfLine: ->
     @expandSelectionsForward (selection) => selection.selectToEndOfLine()

+  # Public: Selects all text from each local cursor to their previous word
+  # boundary.
   selectToPreviousWordBoundary: ->
     @expandSelectionsBackward (selection) => selection.selectToPreviousWordBoundary()

+  # Public: Selects all text from each local cursor to their next word
+  # boundary.
   selectToNextWordBoundary: ->
     @expandSelectionsForward (selection) => selection.selectToNextWordBoundary()

-  # Selects the current line.
+  # Public: Selects the current line from each local cursor.
   selectLine: ->
     @expandSelectionsForward (selection) => selection.selectLine()

-  # Moves the current selection down one row.
+  # Public: Moves each local selection down one row.
   addSelectionBelow: ->
     @expandSelectionsForward (selection) => selection.addSelectionBelow()

-  # Moves the current selection up one row.
+  # Public: Moves each local selection up one row.
   addSelectionAbove: ->
     @expandSelectionsBackward (selection) => selection.addSelectionAbove()

-  # Transposes the current text selections.
+  # Public: Transposes the current text selections.
+  #
+  # FIXME: I have no idea what this function does.
   #
   # This only works if there is more than one selection. Each selection is transferred
   # to the position of the selection after it. The last selection is transferred to the
@@ -1256,44 +1258,55 @@
       else
         selection.insertText selection.getText().split('').reverse().join('')

-  # Turns the current selection into upper case.
+  # Public: Uppercases all locally selected text.
   upperCase: ->
     @replaceSelectedText selectWordIfEmpty:true, (text) => text.toUpperCase()

-  # Turns the current selection into lower case.
+  # Public: Lowercases all locally selected text.
   lowerCase: ->
     @replaceSelectedText selectWordIfEmpty:true, (text) => text.toLowerCase()

-  # Joins the current line with the one below it.
+  # Public: Joins the current line with the one below it.
+  #
+  # FIXME: Needs more clarity.
   #
   # Multiple cursors are considered equally. If there's a selection in the editor,
   # all the lines are joined together.
   joinLine: ->
     @mutateSelectedText (selection) -> selection.joinLine()

+  # Public:
+  #
+  # FIXME: No idea what this does.
   expandLastSelectionOverLine: ->
     @getLastSelection().expandOverLine()

-  # Selects all the text from the current cursor position to the beginning of the word.
+  # Public: Selects all the text from all local cursors to the beginning of
+  # their current words.
   selectToBeginningOfWord: ->
     @expandSelectionsBackward (selection) => selection.selectToBeginningOfWord()

-  # Selects all the text from the current cursor position to the end of the word.
+  # Public: Selects all the text from all local cursors to the end of
+  # their current words.
   selectToEndOfWord: ->
     @expandSelectionsForward (selection) => selection.selectToEndOfWord()

-  # Selects all the text from the current cursor position to the beginning of the next word.
+  # Public: Selects all the text from all local cursors to the beginning of
+  # the next word.
   selectToBeginningOfNextWord: ->
     @expandSelectionsForward (selection) => selection.selectToBeginningOfNextWord()

-  # Selects the current word.
+  # Public: Selects the current word of each local cursor.
   selectWord: ->
     @expandSelectionsForward (selection) => selection.selectWord()

+  # Public:
+  #
+  # FIXME: No idea what this does.
   expandLastSelectionOverWord: ->
     @getLastSelection().expandOverWord()

-  # Selects the range associated with the given marker if it is valid.
+  # Public: Selects the range associated with the given marker if it is valid.
   #
   # Returns the selected {Range} or a falsy value if the marker is invalid.
   selectMarker: (marker) ->
@@ -1302,6 +1315,9 @@
       @setSelectedBufferRange(range)
       range

+  # Public:
+  #
+  # FIXME: Not sure how to describe what this does.
   mergeCursors: ->
     positions = []
     for cursor in @getCursors()
@@ -1311,19 +1327,29 @@
       else
         positions.push(position)

+  # Public:
+  #
+  # FIXME: Not sure how to describe what this does.
   expandSelectionsForward: (fn) ->
     @mergeIntersectingSelections =>
       fn(selection) for selection in @getSelections()

+  # Public:
+  #
+  # FIXME: Not sure how to describe what this does.
   expandSelectionsBackward: (fn) ->
     @mergeIntersectingSelections isReversed: true, =>
       fn(selection) for selection in @getSelections()

+  # Public:
+  #
+  # FIXME: No idea what this does.
   finalizeSelections: ->
     selection.finalize() for selection in @getSelections()

-  # Merges intersecting selections. If passed a function, it executes the function
-  # with merging suppressed, then merges intersecting selections afterward.
+  # Private: Merges intersecting selections. If passed a function, it executes
+  # the function with merging suppressed, then merges intersecting selections
+  # afterward.
   mergeIntersectingSelections: (args...) ->
     fn = args.pop() if _.isFunction(_.last(args))
     options = args.pop() ? {}
@@ -1345,6 +1371,7 @@

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

+  # Private:
   preserveCursorPositionOnBufferReload: ->
     cursorPosition = null
     @subscribe @buffer, "will-reload", =>
@@ -1365,35 +1392,48 @@
   reloadGrammar: ->
     @displayBuffer.reloadGrammar()

-  ### Internal ###
-
+  # Private:
   shouldAutoIndent: ->
     config.get("editor.autoIndent")

+  # Public: Performs all editor actions from the given function within a single
+  # undo step.
+  #
+  # Useful for implementing complex operations while still ensuring that the
+  # undo stack remains relevant.
   transact: (fn) -> @buffer.transact(fn)

+  # Private:
   beginTransaction: -> @buffer.beginTransaction()

+  # Private:
   commitTransaction: -> @buffer.commitTransaction()

+  # Private:
   abortTransaction: -> @buffer.abortTransaction()

+  # Private:
   inspect: ->
     JSON.stringify @state.toObject()

+  # Private:
   logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)

+  # Private:
   handleGrammarChange: ->
     @unfoldAll()
     @trigger 'grammar-changed'

+  # Private:
   handleMarkerCreated: (marker) =>
     if marker.matchesAttributes(@getSelectionMarkerAttributes())
       @addSelection(marker)

+  # Private:
   getSelectionMarkerAttributes: ->
     type: 'selection', editSessionId: @id, invalidation: 'never'

+  # Private:
   getDebugSnapshot: ->
     [
       @displayBuffer.getDebugSnapshot()


remove stray log

remove stray log

--- apps/order/client/checkout_form.coffee
+++ apps/order/client/checkout_form.coffee
@@ -17,7 +17,6 @@
     if @$('.credit-card-form-checkbox input').is(':checked')
       @$('.credit-card-form-hidden').hide()
     else
-      console.log 'show'
       @$('.credit-card-form-hidden').show().find(':input').val('')

   setUpFields: ->


Add missing /v argument

Add missing /v argument

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -13,7 +13,6 @@
 directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom'
 backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom'
 environmentKeyPath = 'HKCU\\Environment'
-pathKeyPath = "#{environmentKeyPath}\\Path"

 spawn = (command, args, callback) ->
   spawnedProcess = ChildProcess.spawn(command, args)
@@ -86,7 +85,7 @@

         console.log 'updating'
         console.log segments.join(';')
-        args = ['add', pathKeyPath, segments.join(';'), '/f']
+        args = ['add', environmentKeyPath, '/v', 'Path', segments.join(';'), '/f']
         spawnReg(args, callback)

 exports.spawn = spawnUpdate


[DOC] Add documentation src/common/config.coffee

--- src/common/config.coffee
+++ src/common/config.coffee
@@ -17,6 +17,7 @@

 Path    = require 'path'

+# Define internal et external access adress.
 DefaultHostname = (hostname) =>
     h =
         'Internal' : 'localhost',
@@ -30,12 +31,17 @@
             h.External = hostname.External if hostname.External?
     return h

+# This class configure the entire project.
 class _Config
+    # Initialize Voicious config with basic value if configuration file doesn't
+    # contain the required informations.
     checkCoreConfig : () ->
         @Voicious.Enabled  = 0           if not @Voicious.Enabled?
         @Voicious.Hostname = DefaultHostname @Voicious.Hostname
         @Voicious.Port     = 4242        if not @Voicious.Port?

+    # Initialize the Rest API config with basic value if configuration file doesn't
+    # contain the required informations.
     checkRestConfig : () ->
         @Restapi.Enabled = 0 if not @Restapi.Enabled?
         if @Restapi.Enabled
@@ -59,11 +65,14 @@
             protocol     = if @Restapi.Ssl.Enabled then 'https' else 'http'
             @Restapi.Url = "#{protocol}://#{@Restapi.Hostname.Internal}:#{@Restapi.Port}/api"

+    # Initialize the WebSocket server config with basic value if configuration file doesn't
+    # contain the required informations.
     checkWebsocketConfig : () ->
         @Websocket.Enabled  = 0           if not @Websocket.Enabled?
         @Websocket.Hostname = DefaultHostname @Websocket.Hostname
         @Websocket.Port     = 1337        if not @Websocket.Port?

+    # Load the configuration file.
     loadJSONConfig : () ->
         fileToOpen  = 'config'
         tmpJSON     = require (Path.join @Paths.Config, fileToOpen + ".json")


Make coffeelint happy again

Make coffeelint happy again

--- src/coffee/csv/categorysort.coffee
+++ src/coffee/csv/categorysort.coffee
@@ -23,7 +23,7 @@

     # if parentBy is externalId or ID just return value from a row by headerIndex
     if cons.HEADER_SLUG == parentBy
-      parentBy = cons.HEADER_SLUG+'.'+@options.language
+      parentBy = cons.HEADER_SLUG + '.' + @options.language

     @getValueByHeader(row, header, parentBy)

@@ -75,7 +75,7 @@

       if data.length == dataToProcess.length and dataToProcess.length
         @logger.debug("SortingCycle::Could not find parents anymore,"
-          + " flushing the res of #{dataToProcess.length} rows")
+          +" flushing the res of #{dataToProcess.length} rows")

         outBuffer = outBuffer.concat _.pluck(dataToProcess, 'row')
         dataToProcess = []


Added exists

Added exists

--- src/fs.coffee
+++ src/fs.coffee
@@ -19,6 +19,11 @@
   # Set the current working directory to `path`.
   changeWorkingDirectory: (path) ->
     OSX.NSFileManager.defaultManager.changeCurrentDirectoryPath path
+
+  # Returns true if the file specified by path exists
+  exists: (path) ->
+   exists = OSX.NSFileManager.defaultManager.
+      fileExistsAtPath_isDirectory path, null

   # Returns true if the file specified by path exists and is a
   # directory.
@@ -70,4 +75,4 @@

   # Return the path name of the current working directory.
   workingDirectory: ->
-    OSX.NSFileManager.defaultManager.currentDirectoryPath.toString()+    OSX.NSFileManager.defaultManager.currentDirectoryPath.toString()


fix linting error

fix linting error

--- src/components/exercise/index.cjsx
+++ src/components/exercise/index.cjsx
@@ -102,7 +102,7 @@

     <ExFooter {...canContinueControlProps} {...footerProps} panel='review'/>

-  renderIdLink: () ->
+  renderIdLink: ->
     {parts} = @props
     step = _.last(parts)


data model

data model

--- src/devtools/panel/app/data.coffee
+++ src/devtools/panel/app/data.coffee
@@ -1 +1,252 @@
-# 负责数据模型的维护
+# 基础数据模型
+# 不再直接使用游戏数据，以提升程序的稳定性
+((exports)->
+  exports.tohken.data = {
+    # 玩家信息
+    player: {
+      "filling": false
+      # 玩家名
+      "name":  null
+      # 等级
+      "level": null
+      # 经验
+      "exp": null
+      # 最大刀剑持有量
+      "max_sword": null
+      # 当前刀剑持有量
+      "now_sword": null
+      # 最大刀装持有量
+      "equip_max": null
+      # 当前刀装持有量
+      "equip_now": null
+      # 小判
+      "money": null
+      # 秘书刀
+      "secretary": null
+      # 秘书刀的SID
+      "secretary_serial_id": null
+      # 最大队伍数
+      "max_party": null
+      # 最大锻刀数
+      "forge_slot": null
+      # 最大手入数
+      "repair_slot": null
+      # 服务器
+      "world": null
+      # 创建日期
+      "created_at": null
+      # 成就
+      "complete": {
+        # 全刀剑
+        "sword": null
+        # 短刀
+        "dagger": null
+        # 脇差
+        "short": null
+        # 打刀
+        "normal": null
+        # 太刀
+        "long": null
+        # 大太刀
+        "large": null
+        # 槍
+        "spear": null
+        # 薙刀
+        "partisan": null
+        # 刀装
+        "equip": null
+        # 马
+        "horse": null
+        # 回想
+        "scene": null
+        # 景趣
+        "furniture": null
+      }
+      # 战绩
+      "summary": {
+        # 出阵次数
+        "battle": null
+        # 出阵获胜次数
+        "battle_win": null
+        # 演练次数
+        "practice": null
+        # 演练获胜次数
+        "practice_win": null
+        # 远征次数
+        "conquest": null
+        # 远征成功次数
+        "conquest_success": null
+      }
+      # 上次更新时间
+      "last_update": null
+    }
+    # 资源信息
+    resource: {
+      "filling": false
+      # 依頼札
+      "bill":         null
+      # 手伝い札
+      "bonemeal":     null
+      # 木炭
+      "charcoal":     null
+      # 玉鋼
+      "steel":        null
+      # 冷却材
+      "coolant":      null
+      # 砥石
+      "file":         null
+      # 最大资源储量
+      "max_resource": null
+    }
+    # 刀剑
+    sword: {
+      "filling": false
+      # 基准模型
+      "template": {
+        # 序列号
+        "serial_id": null
+        # 图鉴ID
+        "sword_id": null
+        # 稀有度
+        "rarity": null
+        # 等级
+        "level": null
+        # 累计经验
+        "exp": null
+        # 进阶次数
+        "evol_num": null
+        # HP
+        "hp": null
+        # HP 上限
+        "hp_max": null
+        # 打撃        範囲
+        "atk": null
+        # 統率
+        "def": null
+        # 機動
+        "mobile": null
+        # 衝力
+        "back": null
+        # 偵察
+        "scout": null
+        # 隠蔽
+        "hide": null
+        # 生存提升上限
+        "hp_up": null
+        # 打撃提升上限
+        "atk_up": null
+        # 統率提升上限
+        "def_up": null
+        # 機動提升上限
+        "mobile_up": null
+        # 衝力提升上限
+        "back_up": null
+        # 偵察提升上限
+        "scout_up": null
+        # 隠蔽提升上限
+        "hide_up": null
+        # 必殺
+        "loyalties": null
+        # 疲劳
+        "fatigue": null
+        # 刀装位
+        "equip_serial_id1": null
+        "equip_serial_id2": null
+        "equip_serial_id3": null
+        # 马
+        "horse_serial_id": null
+        # 氪金道具
+        "item_id": null
+        # 写保护
+        "protect": null
+        # 状态
+        # 0：正常，1：手入，2：軽傷，3：中傷，4：重傷
+        "status": null
+        # * 尚不明确
+        "recovered_at": null
+        # 获得时间
+        "created_at": null
+      }
+      # 数据填充位置
+      "data": {}
+    }
+    # 刀装
+    equip: {
+      "filling": false
+      "template": {
+        # 序列号
+        "serial_id": null
+        # 装备ID，10001及以上为马匹
+        "equip_id": null
+        # 持有兵数
+        "soldier": null
+      }
+      # 数据填充位置
+      "data": {}
+    }
+    # 队伍
+    party: {
+      "filling": false
+      "template": {
+        # 队伍编号
+        "party_no": null
+        # 状态
+        # 0：未开放，1：放置，2：远征
+        "status": "2"
+        # 队伍名
+        "party_name": null
+        # 远征结束时间
+        "finished_at": null
+        # 编组信息
+        "slot": {
+          "1": {
+            "serial_id": null
+          }
+          "2": {
+            "serial_id": null
+          }
+          "3": {
+            "serial_id": null
+          }
+          "4": {
+            "serial_id": null
+          }
+          "6": {
+            "serial_id": null
+          }
+          "5": {
+            "serial_id": null
+          }
+        }
+      }
+      # 数据填充位置
+      "data": {}
+    }
+    # 锻造
+    forge: {
+      "filling": false
+      # 锻造位
+      "open": null
+      # 数据填充位置
+      "data": {}
+    }
+    # 手入
+    repair: {
+      "filling": false
+      # 手入位
+      "open": null
+      # 数据填充位置
+      "data": {}
+    }
+    # 远征
+    conquest: {
+      "filling": false
+      "template": {
+        "party": null
+        "return": null
+      }
+      # 数据填充位置
+      "data": {}
+    }
+  }
+)(window)


authorize spaces in kick reason, fixes #98

--- src/frontend/commandManager.coffee
+++ src/frontend/commandManager.coffee
@@ -31,12 +31,12 @@
         switch  cmd[0]
             when 'kick' then do () =>
                 if cmd[1]?
+                    reason = ''
                     if cmd[2]?
-                        @kick cmd[1], cmd[2]
-                    else
-                        @kick cmd[1], ""
+                        reason = (cmd.splice 2).join ' '
+                    @kick cmd[1], reason
                 else
-                    @emitter.trigger 'chat.error', { text: cmd[0] + ": usage: /kick user reason"}
+                    @emitter.trigger 'chat.error', { text: cmd[0] + ": usage: /kick user [reason]"}
             else
                 @emitter.trigger 'chat.error', { text: cmd[0] + ": command not found." }


fixed poly shuffler for length == 1

fixed poly shuffler for length == 1

--- app/assets/javascripts/building.coffee
+++ app/assets/javascripts/building.coffee
@@ -418,7 +418,7 @@
 		@showNextPolygon()

 	shufflePolygons: (a) ->
-		if a.length == 1 return a
+		return a if a.length == 1
 		# from: http://coffeescriptcookbook.com/chapters/arrays/shuffling-array-elements
 		# From the end of the list to the beginning, pick element `i`.
 		for i in [a.length-1..1]


remove debug logging

remove debug logging

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -1,4 +1,4 @@
-$(document).ready ->
+initPage = ->
   if $(".toggle-container").length
     $(".toggle-container").hide()
     $(".box-toggle").on "click", ".trigger", (e) ->
@@ -6,10 +6,8 @@
       self.toggleClass("active").next().stop(true, true).slideToggle "slow"
       e.preventDefault()

-$(document).ready ->
   $('.datepicker').datepicker()

-$(document).ready ->
   notifications = $('body').find($('.flash_notification'))
   if notifications.length > 0
     bar = $('

')
@@ -31,8 +29,6 @@
       notification.html($(this).html())

       notification.click ->
-        console.log $(this).height()
-        console.log bar.height()
         bar.animate({
           'height': "#{bar.height() - $(this).height() - 7}px"
         }, 'fast', 'linear', ->
@@ -57,6 +53,6 @@
     e.preventDefault()
     window.open($(this).attr('href'))

-
 $(document).ready ->
+  initPage()
   externalLinks()


add user_ip_address

add user_ip_address

--- app/assets/javascripts/users.js.coffee
+++ app/assets/javascripts/users.js.coffee
@@ -14,6 +14,7 @@
   # console.log(flash_version)
   # console.log("Flash Version: #{flash_version}")
   user_id = $('#user_id').text()
+  user_ip_address = $('#user_ip_address').text()
   # console.log(user_id)
   audio = Modernizr.audio
   video = Modernizr.video
@@ -21,7 +22,10 @@
   video_formats = "ogg: #{video.ogg}, h264: #{video.h264}, webm: #{video.webm}"

   get_proxy = ->
-    $.ajax "http://winmxunlimited.net/api/proxydetection/v1/query/?ip=#{user.ip}"
+    $.ajax "http://winmxunlimited.net/api/proxydetection/v1/query/?ip=#{user_ip_address}",
+      success: (result) ->
+        console.log(result)
+

   update_info = ->
     $.ajax '/update',
@@ -36,5 +40,5 @@
         audio_formats: audio_formats
         video_formats: video_formats

-  update_info()
+  get_proxy()


fix userstory detail, error 500

fix userstory detail, error 500

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -198,7 +198,7 @@
             "status": selectedStatuses,
             "tags": selectedTags,
             "project": @scope.projectId
-            "milestone": "null"
+            "milestone": null
         })

     prepareBulkUpdateData: (uses) ->


remove repeated code

remove repeated code

--- app/coffee/modules/backlog/main.coffee
+++ app/coffee/modules/backlog/main.coffee
@@ -356,9 +356,6 @@

                 # Remove moving us from backlog userstories lists.
                 for us, key in usList
-                    r = @scope.userstories.indexOf(us)
-                    @scope.userstories.splice(r, 1)
-
                     r = @scope.userstories.indexOf(us)
                     @scope.userstories.splice(r, 1)


Check permissions in tg-task-status-button

--- app/coffee/modules/tasks/detail.coffee
+++ app/coffee/modules/tasks/detail.coffee
@@ -177,7 +177,7 @@
 ## Task status button directive
 #############################################################################

-TaskStatusButtonDirective = ($rootScope, $repo) ->
+TaskStatusButtonDirective = ($rootScope, $repo, $confirm) ->
     # Display the status of Task and you can edit it.
     #
     # Example:
@@ -186,12 +186,13 @@
     # Requirements:
     #   - Task object (ng-model)
     #   - scope.statusById object
+    #   - $scope.project.my_permissions

     template = _.template("""
-

+    <div class="status-data <% if(editable){ %>clickable<% }%>">
         <span class="level" style="background-color:<%= status.color %>">
         <%= status.name %>
-
+        <% if(editable){ %><% }%>
         status

@@ -204,34 +205,47 @@
     """) #TODO: i18n

     link = ($scope, $el, $attrs, $model) ->
+        isEditable = ->
+            return $scope.project.my_permissions.indexOf("modify_task") != -1
+
         render = (task) =>
             status = $scope.statusById[task.status]

             html = template({
                 status: status
                 statuses: $scope.statusList
+                editable: isEditable()
             })
             $el.html(html)

         $el.on "click", ".status-data", (event) ->
             event.preventDefault()
             event.stopPropagation()
+            return if not isEditable()

             $el.find(".pop-status").popover().open()

         $el.on "click", ".status", (event) ->
             event.preventDefault()
             event.stopPropagation()
+            return if not isEditable()
+
             target = angular.element(event.currentTarget)

             $.fn.popover().closeAll()

             task = $model.$modelValue.clone()
             task.status = target.data("status-id")
-
             $model.$setViewValue(task)
-            $repo.save($model.$modelValue).then ->
+
+            onSuccess = ->
+                $confirm.notify("success")
                 $rootScope.$broadcast("history:reload")
+            onError = ->
+                $confirm.notify("error")
+                task.revert()
+                $model.$setViewValue(task)
+            $repo.save($model.$modelValue).then(onSuccess, onError)

         $scope.$watch $attrs.ngModel, (task) ->
             render(task) if task
@@ -245,7 +259,7 @@
         require: "ngModel"
     }

-module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", TaskStatusButtonDirective])
+module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", TaskStatusButtonDirective])

 TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm) ->


Don't make invisible highlight hoverable/clickable

--- app/js/jail_iframe/classes/fact.coffee
+++ app/js/jail_iframe/classes/fact.coffee
@@ -14,42 +14,23 @@

 class FactInteraction
   constructor: (elements, @id, @options) ->
-    @highlight_attention = new FactlinkJailRoot.AttentionSpan
-      onAttentionLost:   => @highlighter.dehighlight()
-      onAttentionGained: => @highlighter.highlight()
-      wait_for_attention:  delay_before_mouseover_detected
-      wait_for_neglection: delay_before_mouseout_detected
-
     @highlighter = new Highlighter $(elements), 'fl-active'

     @show_button = new FactlinkJailRoot.ShowButton
-      mouseenter: => @onHover()
-      mouseleave: => @onUnhover()
+      mouseenter: => @highlighter.highlight()
+      mouseleave: => @highlighter.dehighlight()
       click:      => @onClick()
     @show_button.placeNearElement elements[0]

-    $(elements).on
-      mouseenter: (e) => @onHover()
-      mouseleave: => @onUnhover()
-      click: => @onClick()
-
   onClick: (options={}) =>
-    @highlight_attention.gainAttentionNow()
     @show_button.startLoading() # must be called after show
     FactlinkJailRoot.on 'modalOpened', @_onModalOpened, @
     FactlinkJailRoot.openFactlinkModal @id

   _onModalOpened: ->
-    @highlight_attention.loseAttentionNow()
     FactlinkJailRoot.off 'modalOpened', @_onModalOpened, @

-  onHover: ->
-    @highlight_attention.gainAttention()
-  onUnhover:  ->
-    @highlight_attention.loseAttention()
-
   destroy: ->
-    @highlight_attention.loseAttentionNow()
     @show_button.destroy()

 class FactLoadPromotion


Publication page open access (#2653)

Publication page open access (#2653)

Rebased and going ahead and merging.

--- app/pages/about/publications-page.cjsx
+++ app/pages/about/publications-page.cjsx
@@ -152,7 +152,8 @@
       {citation: "Planet Hunters. VIII. Characterization of 41 Long-Period Exoplanet Candidates from Kepler Archival Data, Wang+ 2015."
       href: "http://arxiv.org/pdf/1512.02559v2.pdf"},
       {citation: "Planet Hunters. IX. KIC 8462852 – Where’s the flux?, Boyajian+ 2015."
-      href: "http://mnras.oxfordjournals.org/content/457/4/3988.full.pdf"},
+      href: "http://mnras.oxfordjournals.org/content/457/4/3988.full.pdf"
+      openAccess: "http://arxiv.org/pdf/1509.03622v1.pdf"},
       {citation: "GALEX J194419.33+491257.0: An unusually active SU UMa-type dwarf nova with a very short orbital period in the Kepler data, Kato & Osaki 2014."
       href: "http://adsabs.harvard.edu/doi/10.1093/pasj/psu025"},
       {citation: "Planet Hunters. VI. An Independent Characterization of KOI-351 and Several Long Period Planet Candidates from the Kepler Archival Data, Schmitt+ 2014."
@@ -367,7 +368,8 @@

                             {publication.citation}

-                            {if publication.href? then Available here.}
+                            {if publication.href? then View publication.}{' '}
+                            {if publication.openAccess? then View open access version.}

}


Load jQuery in no conflict mode when we load it up.

This prevents conflicts with the likes of mootools et al.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -94,7 +94,9 @@

 withJQueryLoaded = (callback) ->
   if ! jQuery?
-    loadScript("//ajax.googleapis.com/ajax/libs/jquery/" + jqVersion + "/jquery.min.js", callback)
+    loadScript "//ajax.googleapis.com/ajax/libs/jquery/" + jqVersion + "/jquery.min.js", () ->
+      jQuery.noConflict()
+      callback()
   else if ! isAcceptableJQuery()
     console?.error("AnchorTab is disabled because you are using an unsupported version of jQuery.")


update date time input config

update date time input config

--- app/assets/javascripts/folio/console/simple_form_inputs/_date_time_input.coffee
+++ app/assets/javascripts/folio/console/simple_form_inputs/_date_time_input.coffee
@@ -3,6 +3,9 @@
   sideBySide: true
   format: 'DD. MM. YYYY HH:mm'
   keepInvalid: false
+  widgetPositioning:
+    horizontal: 'auto'
+    vertical: 'bottom'
   icons:
     time: 'fa fa-clock',
     date: 'fa fa-calendar',


Updated animation|slide speed.

Updated animation|slide speed.

--- media/coffee/cup.of.coffee
+++ media/coffee/cup.of.coffee
@@ -16,13 +16,13 @@

 # Toggle divs
 slideToggle = (divs...) ->
-    $(div).slideToggle(150) for div in divs
+    $(div).slideToggle(100) for div in divs

 toggle = (divs...) ->
     $(div).toggle() for div in divs

 slide = (divs...) ->
-    $(div).animate({width:'toggle'}, 150) for div in divs
+    $(div).animate({width:'toggle'}, 100) for div in divs
     if $(div).css('display') == 'block'
         $(div).css('display', 'table')

@@ -87,8 +87,17 @@
         $.get('/lock')
         reload()

+# Lookup examples for kanji
+$ -> $('.lookup-button').click ->
+    # Get current kanji
+    kanji = $('.kanji').text().trim()
+    # Display progressbar
+    #$('.loader-left').fadeToggle(250)
+    # TODO: Lookup examples for kanji
+
+# TODO: do something when clicking on radikal!
+
 # Lookup examples in weblio on click
-# TODO: show compound decomposition in left tab?
 $ -> $('ruby').click ->
     # Get usage text
     term = $(this).find('rb').text().trim()
@@ -100,7 +109,7 @@
         success: (data, textStatus, jqXHR) ->
             # If nothing is found -> display notification
             if data.examples.length is 0
-                humane.log('Ooops, no examples found!',  { timeout: 2000})
+                humane.log('Ooops, no examples found!',  {timeout: 2000})
                 $('.loader-left').fadeToggle(100)
                 return


Remove unused class

Remove unused class

--- src/packages/collaboration/lib/buddy-view.coffee
+++ src/packages/collaboration/lib/buddy-view.coffee
@@ -4,7 +4,7 @@
 module.exports =
 class BuddyView extends View
   @content: ({user, state}) ->
-    @div class: 'two-lines', =>
+    @div =>
       @div "#{user.login} (#{user.name})"
       if state.repository
         [owner, name] = url.parse(state.repository.url).path.split('/')[-2..]


Additional transforms following compilation: made the last statement return a JS primitive value

--- src/languages/io.coffee
+++ src/languages/io.coffee
@@ -17,6 +17,32 @@

     @wrappedCodePrefix + rawCode + @wrappedCodeSuffix

+  unwrapTransform: (expr) ->
+    {
+      type: "CallExpression",
+      callee: {
+        type: "MemberExpression",
+        computed: false,
+        object: {
+          type: "Identifier",
+          name: "_io"
+        },
+        property: {
+          type: "Identifier",
+          name: "unwrapIoValue"
+        }
+      },
+      arguments: [expr]
+    }
+
+  makeLastStatementReturn: (ast) ->
+    program = ast
+    wrapperFunction = program.body[0]
+    blockStatement = wrapperFunction.body
+    lastExpressionStatement = blockStatement.body[blockStatement.body.length - 1]
+    blockStatement.body[blockStatement.body.length - 1] = {type: "ReturnStatement", argument: Io.prototype.unwrapTransform(lastExpressionStatement.expression)}
+    ast
+
   parse: (code, aether) ->

     wrappedCode = iota.compile(code, true);
@@ -26,4 +52,5 @@
       wrappedCode = wrappedCode.replace('execute', 'foo')

     ast = esprima.parse(wrappedCode, {range: true})
+    ast = Io.prototype.makeLastStatementReturn ast
     ast


Unpack ctags

Unpack ctags

--- build/tasks/generate-asar-task.coffee
+++ build/tasks/generate-asar-task.coffee
@@ -13,7 +13,7 @@
       grunt.log.error 'The app has to be built before generating asar archive.'
       return done(false)

-    asar.createPackageWithOptions appDir, path.resolve(appDir, '..', 'app.asar'), {unpack: '*.node'}, (err) ->
+    asar.createPackageWithOptions appDir, path.resolve(appDir, '..', 'app.asar'), {unpack: '{*.node,ctags-darwin,ctags-linux,ctags-win32.exe}'}, (err) ->
       return done(err) if err?

       rm appDir


ok with ids longer than 12 bytes                  [fixes #15981773]

--- controllers/teams.coffee
+++ controllers/teams.coffee
@@ -8,11 +8,15 @@
 # middleware
 loadTeam = (req, res, next) ->
   if id = req.param('id')
-    Team.findById id, (err, team) ->
-      return next err if err
-      return next 404 unless team
-      req.team = team
-      next()
+    try
+      Team.findById id, (err, team) ->
+        return next err if err
+        return next 404 unless team
+        req.team = team
+        next()
+    catch error
+      throw error unless error.message == 'Id cannot be longer than 12 bytes'
+      return next 404
   else
     next()


Clearing with innerHTML is slower than removing children

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -15,8 +15,9 @@

   render: ->
     selectedRange = @getSelectedRange()
-    @element.innerHTML = ""
-    @element.appendChild(element) for element in @createElementsForText()
+    elements = @createElementsForText()
+    @element.removeChild(@element.lastChild) while @element.lastChild
+    @element.appendChild(element) for element in elements
     @setSelectedRange(selectedRange)

   createElementsForText: ->


sending a message should close the emoji-container

* same behaviour as hangouts browser client

refs https://github.com/yakyak/yakyak/issues/198

--- src/ui/views/input.coffee
+++ src/ui/views/input.coffee
@@ -93,6 +93,7 @@
                     if e.keyCode == 13
                         e.preventDefault()
                         action 'sendmessage', e.target.value
+                        document.querySelector('#emoji-container').classList.remove('open');
                         historyPush e.target.value
                         e.target.value = ''
                         autosize.update e.target


removed console.logs

removed console.logs

--- src/views/defaults.coffee
+++ src/views/defaults.coffee
@@ -35,12 +35,9 @@

     @listenTo @collection, "sync", ->
-      console.log "page sync"
       if @collection.meta and @pagination and not @paginationViews
         @paginationViews = []
-        console.log "pagination"
         for el in @$(@listPaginatorSelector).get()
-          console.log el
           @paginationViews.push new @pagination(
             collection: @collection
             el: el


Rudimentary support for multi-parameter classes

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -468,7 +468,7 @@
     if typeMap then lookupInMap typeMap, constraint.className

   classParamsForType: (constraint) ->
-    lookupInMap @classParams, constraint.type.name
+    nestedLookupInMap @classParams, typeNamesOfNormalized constraint

 expressionCompile = (ctx, expression) ->
   throw new Error "invalid expressionCompile args" unless ctx instanceof Context and expression
@@ -864,14 +864,14 @@
   if isCall expression
     if isAtom op
       (labelOperator op)
-      new ClassContraint op.symbol, (typeCompile ctx, args[0]) # TODO: support multiparameter type classes
+      new ClassConstraint op.symbol, new Types (typesCompile ctx, args)
     else
       malformed expression, 'Class name required in a constraint'
   else
     malformed expression, 'Class constraint expected'

 typeConstraintsCompile = (ctx, expressions) ->
-  filter ((t) -> t instanceof ClassContraint),
+  filter ((t) -> t instanceof ClassConstraint),
     (typeConstraintCompile ctx, e for e in expressions)

 typeTupleCompile = (ctx, form) ->
@@ -1311,23 +1311,23 @@
       'malformed'

 findClassType = (className, paramNames, methods) ->
-  # TODO: support multi-param classes
-  [param] = paramNames
-  kind = undefined
-  constraint = undefined
+  kinds = mapMap (-> undefined), (arrayToSet paramNames)
   for name, {arity, type, def} of values methods
-    vars = findFree type.type
-    foundKind = lookupInMap vars, param
-    if not foundKind
-      # TODO: attach error to the type expression
-      malformed def, 'Method must include class parameter in its type'
-    if kind and not kindsEq foundKind, kind
-      # TODO: attach error to the type expression instead
-      # TODO: better error message
-      malformed def, 'All methods must use the class paramater of the same kind'
-    kind or= foundKind
-    constraint or= new ClassContraint className, (new TypeVariable param, kind)
-  constraint
+    for param in paramNames
+      vars = findFree type.type
+      kindSoFar = lookupInMap kinds, param
+      foundKind = lookupInMap vars, param
+      if not foundKind
+        # TODO: attach error to the type expression
+        malformed def, 'Method must include class parameter in its type'
+      if kindSoFar and not kindsEq foundKind, kindSoFar
+        # TODO: attach error to the type expression instead
+        # TODO: better error message
+        malformed def, 'All methods must use the class paramater of the same kind'
+      replaceInMap kinds, param, foundKind
+  classParam = (param) ->
+    new TypeVariable param, (lookupInMap kinds, param)
+  new ClassConstraint className, new Types (map classParam, paramNames)

 declareMethods = (ctx, classConstraint, methodDeclarations) ->
   for name, {arity, type} of values methodDeclarations
@@ -1356,13 +1356,13 @@
     classDefinition = ctx.classNamed className

     # TODO: defer if super class instances don't exist yet
-    superClassInstances = findSuperClassInstances ctx, instanceType.type, classDefinition
+    superClassInstances = findSuperClassInstances ctx, instanceType.types, classDefinition

     if hasName
       instanceName = ctx.definitionName()

       ctx.newScope()
-      freshConstrains = assignMethodTypes ctx, instanceName,
+      freshConstraints = assignMethodTypes ctx, instanceName,
         classDefinition, instanceType, constraints
       definitions = pairs wheres
       methodsDeclarations = definitionList ctx,
@@ -1384,7 +1384,7 @@

       # """var #{instanceName} = new #{className}(#{listOf methods});"""
       (jsVarDeclaration (validIdentifier instanceName),
-        (irDefinition (new Constrained freshConstrains, (tupleOfTypes methodTypes).type),
+        (irDefinition (new Constrained freshConstraints, (tupleOfTypes methodTypes).type),
           (jsNew className, (join superClassInstances, methods))))
     else
       'malformed'
@@ -1395,10 +1395,10 @@
   # First we must freshen the instance type, to avoid name clashes of type vars
   freshInstanceType = freshInstance ctx,
     (quantifyUnbound ctx,
-      (new Constrained instanceConstraints, instanceType.type))
+      (new Constrained instanceConstraints, instanceType.types))

   # log "mguing", classDeclaration.constraint.type, freshInstanceType.type
-  sub = mostGeneralUnifier classDeclaration.constraint.type, freshInstanceType.type
+  sub = mostGeneralUnifier classDeclaration.constraint.types, freshInstanceType.type
   # log sub

   ctx.bindTypeVariables setToArray (findFree freshInstanceType)
@@ -1421,9 +1421,9 @@
 instancePrefix = (instanceName, methodName) ->
   "#{instanceName}_#{methodName}"

-findSuperClassInstances = (ctx, instanceType, classDefinition) ->
+findSuperClassInstances = (ctx, instanceTypes, classDefinition) ->
   toConstraint = (superName) ->
-    new ClassContraint superName, instanceType
+    new ClassConstraint superName, instanceTypes
   superConstraints = map toConstraint, classDefinition.supers
   instanceDictFor ctx, constraint for constraint in superConstraints

@@ -1686,8 +1686,8 @@
 # Whether constraints entail constraint
 entail = (ctx, constraints, constraint) ->
   for c in constraints
-    for superClassContraint in constraintsFromSuperClasses ctx, c
-      if typeEq superClassContraint, constraint
+    for superClassConstraint in constraintsFromSuperClasses ctx, c
+      if typeEq superClassConstraint, constraint
         return yes
   instanceContraints = constraintsFromInstance ctx, constraint
   if instanceContraints
@@ -1696,14 +1696,14 @@
     no

 constraintsFromSuperClasses = (ctx, constraint) ->
-  {className, type} = constraint
+  {className, types} = constraint
   join [constraint], concat (for s in (ctx.classNamed className).supers
-    constraintsFromSuperClasses ctx, new ClassContraint s, type)
+    constraintsFromSuperClasses ctx, new ClassConstraint s, types)

 constraintsFromInstance = (ctx, constraint) ->
   {className, type} = constraint
   for instance in (ctx.classNamed className).instances
-    substitution = toMatchTypes instance.type.type.type, constraint.type
+    substitution = toMatchTypes instance.type.type.types, constraint.types
     if substitution
       return map ((c) -> substitute substitution, c), instance.type.constraints
   null
@@ -1987,10 +1987,11 @@
   counter = {}
   classParams = newMap()
   for constraint in reducedConstraints when not isAlreadyParametrized ctx, constraint
-    {className, type} = constraint
-    typeMap = lookupInMap classParams, type.name
+    {className} = constraint
+    names = typeNamesOfNormalized constraint
+    typeMap = nestedLookupInMap classParams, names
     if not typeMap
-      addToMap classParams, type.name, (typeMap = newMap())
+      nestedAddToMap classParams, names, (typeMap = newMap())
     addToMap typeMap, className,
       "_#{className}_#{counter[className] ?= 0; ++counter[className]}"
   ctx.addClassParams classParams
@@ -2065,6 +2066,9 @@
       return join [s], chain
   undefined

+typeNamesOfNormalized = (constraint) ->
+  map (({name}) -> name), constraint.types.types
+
 accessList = (what, list) ->
   [first, rest...] = list
   if first
@@ -2078,8 +2082,7 @@
 instanceDictFor = (ctx, constraint) ->
   for {name, type} in (ctx.classNamed constraint.className).instances
     # TODO: support lookup of composite types, by traversing left depth-first
-    if toMatchTypes type.type.type, constraint.type
-    # if type.type.type.name is constraint.type.name
+    if toMatchTypes type.type.types, constraint.types
       return validIdentifier name
   throw new Error "no instance for #{safePrintType constraint}"

@@ -2422,6 +2425,7 @@
   typecons: '#67B3DD'
   label: '#9C49B6'
   string: '#FEDF6B'
+  char: '#FEDF6B'
   paren: '#444'
   name: '#9EE062'
   recurse: '#67B3DD'
@@ -2971,9 +2975,13 @@

 addToMap = (set, key, value) ->
   return if set.values[key]
-  set.size += 1
+  set.size++
   set.values[key] = value
   set
+
+# Precondition: the key is in the Map
+replaceInMap = (map, key, value) ->
+  map.values[key] = value

 removeFromSet =
 removeFromMap = (set, key) ->
@@ -3096,6 +3104,20 @@
   for k, v of mapB.values when k of mapA.values
     addToMap intersection, k, v
   intersection
+
+nestedAddToMap = (map, keys, value) ->
+  [nestedKeys..., finalKey] = keys
+  for key in nestedKeys
+    map = lookupInMap map, key
+    if not map
+      map = addToMap map, key, newMap()
+  addToMap map, finalKey, value
+
+nestedLookupInMap = (map, keys) ->
+  for key in keys
+    return null if not map?.size?
+    map = lookupInMap map, key
+  map

 # end of Set

@@ -3119,6 +3141,13 @@
     s1 = mostGeneralUnifier t1.op, t2.op
     s2 = mostGeneralUnifier (substitute s1, t1.arg), (substitute s1, t2.arg)
     joinSubs s1, s2
+  else if t1 instanceof Types and t2 instanceof Types
+    if _notEmpty t1.types
+      s1 = mostGeneralUnifier t1.types[0], t2.types[0]
+      s2 = mostGeneralUnifier (new Types t1.types[1...]), (new Types t2.types[1...])
+      joinSubs s1, s2
+    else
+      emptySubstitution()
   else
     newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]

@@ -3154,6 +3183,15 @@
     s3 = mergeSubs s1, s2
     s3 or
       newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]
+  else if t1 instanceof Types and t2 instanceof Types
+    if _notEmpty t1.types
+      s1 = matchType t1.types[0], t2.types[0]
+      s2 = matchType (new Types t1.types[1...]), (new Types t2.types[1...])
+      s3 = mergeSubs s1, s2
+      s3 or
+        newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]
+    else
+      emptySubstitution()
   else
     newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]

@@ -3187,8 +3225,10 @@
   else if type instanceof Constrained
     new Constrained (substituteList substitution, type.constraints),
       (substitute substitution, type.type)
-  else if type instanceof ClassContraint
-    new ClassContraint type.className, substitute substitution, type.type
+  else if type instanceof ClassConstraint
+    new ClassConstraint type.className, substitute substitution, type.types
+  else if type instanceof Types
+    new Types substituteList substitution, type.types
   else
     type

@@ -3201,14 +3241,16 @@
   else if type instanceof TypeApp
     concatMaps (findFree type.op), (findFree type.arg)
   else if type instanceof Constrained
-    concatMaps (findFree (map findFree, type.constraints)), (findFree type.type)
-  else if type instanceof ClassContraint
-    findFree type.type
+    concatMaps (findFreeInList type.constraints), (findFree type.type)
+  else if type instanceof ClassConstraint
+    findFree type.types
+  else if type instanceof Types
+    findFreeInList type.types
   else
     newMap()

-findBound = (name, binding) ->
-  (lookupInMap binding, name) or 'unbound name #{name}'
+findFreeInList = (list) ->
+  concatMaps (map findFree, list)...

 freshInstance = (ctx, type) ->
   throw new Error "not a forall in freshInstance #{safePrintType type}" unless type instanceof ForAll
@@ -3223,13 +3265,16 @@
 #   that is either ordinary type variable or type variable standing for a constructor
 #   with arbitrary type arguments
 isNormalizedConstraint = (constraint) ->
-  {type} = constraint
+  all (map isNormalizedConstraintArgument, constraint.types.types)
+
+isNormalizedConstraintArgument = (type) ->
   if type instanceof TypeVariable
     yes
   else if type instanceof TypeConstr
     no
   else if type instanceof TypeApp
-    isNormalizedConstraint type.op
+    isNormalizedConstraintArgument type.op.type
+

 typeEq = (a, b) ->
   if a instanceof TypeVariable and b instanceof TypeVariable or
@@ -3244,8 +3289,8 @@
   else if a instanceof Constrained and b instanceof Constrained
     (all zipWith typeEq, a.constraints, b.constraints) and
       (typeEq a.type, b.type)
-  else if a instanceof ClassContraint and b instanceof ClassContraint
-    a.className is b.className and (typeEq a.type, b.type)
+  else if a instanceof ClassConstraint and b instanceof ClassConstraint
+    a.className is b.className and (all zipWith typeEq, a.types, b.types)
   else
     no

@@ -3345,11 +3390,13 @@
 class TempType
   constructor: (@type) ->

+class Types
+  constructor: (@types) ->
+
 class Constrained
   constructor: (@constraints, @type) ->
-class ClassContraint
-  constructor: (@className, @type) ->
-
+class ClassConstraint
+  constructor: (@className, @types) ->

 addConstraints = ({constraints, type}, addedConstraints) ->
   new Constrained (join constraints, addedConstraints), type
@@ -3397,8 +3444,8 @@
     flattenType collectArgs type
   else if type instanceof ForAll
     "(∀ #{printType type.type})"
-  else if type instanceof ClassContraint
-    "(#{type.className} #{printType type.type})"
+  else if type instanceof ClassConstraint
+    "(#{type.className} #{(map printType, type.types.types).join ' '})"
   else if type instanceof Constrained
     "(: #{(map printType, join [type.type], type.constraints).join ' '})"
   else if type instanceof TempType
@@ -4119,36 +4166,68 @@

   'collections'
   """
-  Collection (class [collection]
-    elem? (fn [what in]
-      (: (Fn item (collection item) Bool))
-      (# Whether in contains what .)))
-
-  Bag (class [bag]
-    {(Collection bag)}
-
-    fold (fn [with initial over]
-      (: (Fn (Fn item b b) b (bag item)))
-      (# Fold over with using initial .))
-
-    length (fn [bag]
-      (: (Fn (bag item) Num))
-      (# The number of items in the bag .))
-
-    empty? (fn [bag]
-      (: (Fn (bag item) Bool))
-      (# Whether the bag contains no elements.)))
-
-  list-elem? (macro [what in]
-    (: (Fn item (List item) Bool))
-    (Js.call (Js.access in "contains") {what}))
-
-  collection-list (instance (Collection List)
-    elem? (fn [what in]
-      (list-elem? what in)))
+    Collection (class [collection]
+      elem? (fn [what in]
+        (: (Fn item (collection item) Bool))
+        (# Whether in contains what .)))
+
+    Bag (class [bag]
+      {(Collection bag)}
+
+      fold (fn [with initial over]
+        (: (Fn (Fn item b b) b (bag item)))
+        (# Fold over with using initial .))
+
+      length (fn [bag]
+        (: (Fn (bag item) Num))
+        (# The number of items in the bag .))
+
+      empty? (fn [bag]
+        (: (Fn (bag item) Bool))
+        (# Whether the bag contains no elements.)))
+
+    list-elem? (macro [what in]
+      (: (Fn item (List item) Bool))
+      (Js.call (Js.access in "contains") {what}))
+
+    collection-list (instance (Collection List)
+      elem? (fn [what in]
+        (list-elem? what in)))
   """
   "(elem? 3 {1 2 3})", yes

+  'multiparam classes'
+  """
+    Collection (class [ce e]
+      first (fn [in]
+        (: (Fn (ce e) e))))
+
+    list-first (macro [in]
+      (: (Fn (List item) item))
+      (Js.call (Js.access in "first") {}))
+
+    list-collection (instance (Collection List a)
+      first (fn [in]
+        (list-first in)))
+  """
+  "(first {42 43 44})", 42
+
+  'functional deps'
+  """
+    Collection (class [ce e]
+      (# (| ce: e))
+      first (fn [in]
+        (: (Fn ce e))))
+
+    list-first (macro [in]
+      (: (Fn (List item) item))
+      (Js.call (Js.access in "first") {}))
+
+    list-collection (instance (Collection (List a) a)
+      first (fn [in]
+        (list-first in)))
+  """
+  "(first {42 43 44})", 42
   # The following doesn't work because the Collection type class specifies
   # that the constructor takes only one argument.
   #


Fixed issues around explicitly typed Js-including inferred definitions by pushing constraints through js calls and making sure the types are unified. This means that js calls must be typed explicitly twice, which is not ideal.

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1381,26 +1381,25 @@

   if ctx.isFinallyTyped name, scopeIndex
     # TODO: check class constraints
-    if not includesJsType currentType.type
-      # Check the declared type
-      declaredType = ctx.type name
-      explicitType = copyOrigin (freshInstance ctx, declaredType), declaredType.type
-      updatedDeclaredType = quantifyUnbound ctx, explicitType#(substitute ctx.substitution, explicitType)
-      unify ctx, currentType.type, explicitType.type
-      unifiedType = explicitType# substitute ctx.substitution, explicitType
-      inferredType = quantifyUnbound ctx, unifiedType
-      if not typeEq inferredType, updatedDeclaredType
-        declaredTypeTooGeneral ctx, inferredType, updatedDeclaredType, unifiedType, declaredType
-      # Context reduction
-      isDeclaredConstraint = (c) ->
-        entailed = entail ctx, unifiedType.constraints, c
-      notDeclared = filter (__ _not, isDeclaredConstraint), constraints #(substituteList ctx.substitution, constraints)
-      {success} = deferConstraints ctx, notDeclared, currentType, scopeIndex
-      if success
-        [deferredConstraints, retainedConstraints] = success
-        if _notEmpty retainedConstraints
-          for constraint in retainedConstraints
-            declaredTypeMissingConstraint ctx, name, constraint
+    declaredType = ctx.type name
+    explicitType = copyOrigin (freshInstance ctx, declaredType), declaredType.type
+    # Check the declared type
+    updatedDeclaredType = quantifyUnbound ctx, explicitType#(substitute ctx.substitution, explicitType)
+    unify ctx, currentType.type, explicitType.type
+    unifiedType = explicitType# substitute ctx.substitution, explicitType
+    inferredType = quantifyUnbound ctx, unifiedType
+    if not typeEq inferredType, updatedDeclaredType
+      declaredTypeTooGeneral ctx, inferredType, updatedDeclaredType, unifiedType, declaredType
+    # Context reduction
+    isDeclaredConstraint = (c) ->
+      entailed = entail ctx, unifiedType.constraints, c
+    notDeclared = filter (__ _not, isDeclaredConstraint), constraints #(substituteList ctx.substitution, constraints)
+    {success} = deferConstraints ctx, notDeclared, currentType, scopeIndex
+    if success
+      [deferredConstraints, retainedConstraints] = success
+      if _notEmpty retainedConstraints
+        for constraint in retainedConstraints
+          declaredTypeMissingConstraint ctx, name, constraint
   else
     if not ctx.isAtNonDeferrableDefinition()
       {success} = deferConstraints ctx,
@@ -1993,8 +1992,7 @@
 callJsMethodCompile = (ctx, call) ->
   [dotMethod, object, args...] = _validTerms call
   labelOperator dotMethod
-  call.tea = toConstrained markOrigin jsType, call
-  assignCompile ctx, call,
+  compiled =
     if object
       if /^.-/.test dotMethod.symbol
         (jsAccess (termCompile ctx, object), dotMethod.symbol[2...])
@@ -2003,6 +2001,13 @@
     else
       malformed ctx, call, 'Missing an object'
       jsNoop()
+  constraints =
+    if object
+      (concatMap (__ _constraints, _tea), (join [object], args))
+    else
+      []
+  call.tea = new Constrained constraints, markOrigin jsType, call
+  assignCompile ctx, call, compiled

 ms['set!'] = ms_doset = (ctx, call) ->
   # (set! (.innerHTML e) "Ahoj")
@@ -3266,6 +3271,8 @@
   finalType = type#substitute ctx.substitution, type
   # allConstraints = (addConstraintsFrom ctx,
   #   {type, name: reference?.name, scopeIndex: reference?.scopeIndex}, finalType).constraints
+
+  # this is done because of bare definitons and deferred types, which dont have all constraints when compiled
   reducedConstraints =
     if reference
       constraintsFromReference ctx, {type, name: reference.name, scopeIndex: reference.scopeIndex}
@@ -5431,6 +5438,9 @@
     "undefined"
   else
     throw new Error "Unrecognized type in printType"
+
+printTypes = (list) ->
+  map printType, list

 printTypeToHtml = (type) ->
   if type.TypeVariable
@@ -5823,7 +5833,7 @@
   mapKeys lookupDefinitions, moduleSet

 contextWithDependencies = (modules) ->
-  ctx: injectedContext modules, yes
+  ctx: (injectedContext modules, yes)
   modules: setToArray modules

 moduleDependencies = (moduleName) ->
@@ -6886,6 +6896,27 @@
   #     found-or-equals (fn [found item]
   #       (= what item))

+  'deferring constraints on explicitly typed Js-inferred values'
+  """
+  Eq (class [a]
+    = (fn [x y] (: (Fn a a Bool))))
+
+  num-eq (instance (Eq Num)
+    = (macro [x y]
+      (: (Fn Num Num Bool))
+      (Js.binary "===" x y)))
+
+  Set (class [set item]
+    remove (fn [what from]
+      (: (Fn item set item))))
+
+  array-set (instance (Set (Array a) a)
+    {(Eq a)}
+    remove (fn [what from]
+      (:: a (.indexOf from what))))
+  """
+  "(remove 2 {1 2 3})", 1
+
   'nested pattern matching'
   """
     f (fn [x]


Always unify JS types and report them if they are inferred for a definition

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1226,11 +1226,14 @@
     # Finalizing type again after possibly added substitution when defer constraints
     currentType = substitute ctx.substitution, type
     # log "assign type", name, (printType (addConstraints currentType, retainedConstraints)), (printType quantifyUnbound ctx, (addConstraints currentType, retainedConstraints))
-    ctx.assignType name,
-      if polymorphic
-        quantifyUnbound ctx, (addConstraints currentType, retainedConstraints)
-      else
-        toForAll currentType
+    if includesJsType currentType.type
+      ctx.extendSubstitution substituionFail "#{name}'s inferred type includes JS"
+    else
+      ctx.assignType name,
+        if polymorphic
+          quantifyUnbound ctx, (addConstraints currentType, retainedConstraints)
+        else
+          toForAll currentType
   if deferredConstraints
     ctx.addToScopeConstraints deferredConstraints

@@ -4034,6 +4037,8 @@

 unify = (ctx, t1, t2) ->
   throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
+  if (includesJsType t1) or (includesJsType t2)
+    return
   sub = ctx.substitution
   ctx.extendSubstitution mostGeneralUnifier (substitute sub, t1), (substitute sub, t2)


Display declared types of instance methods

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2204,7 +2204,9 @@
 prefixWithInstanceName = (ctx, definitionPairs, instanceName) ->
   for [lhs, rhs] in definitionPairs
     if (syntaxNewName ctx, 'Method name required', lhs) is true
-      [(token_ instancePrefix instanceName, lhs.symbol), rhs]
+      prefixedName = instancePrefix instanceName, lhs.symbol
+      lhs.tea = ctx.type prefixedName
+      [(token_ prefixedName), rhs]
     else
       [lhs, rhs]