Documentation of b-tree structure.

Documentation of b-tree structure.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -2,7 +2,237 @@
 #
 # ## Purpose
 #
-# Implements a file backed b-tree.
+# Implements a file backed [b-tree](http://en.wikipedia.org/wiki/B-tree).
+#
+# TK Points to cover as far as advocacy goes.
+#
+#  * Concurrency.
+#  * Flexiblity of ordering.
+#  * Atomicity.
+#  * A database primitive, for use as if it were a programming primitive like an
+#  array or an associative array.
+#  * Standard Node.js API.
+#
+# But this isn't important at the moment.
+#
+# ### Terminology
+#
+# We refer to the nodes in our b-tree as *pages*. The term node conjures an
+# image of a discrete component in a linked data structure that contains one,
+# maybe two or three, values. Nodes in a b-tree contain hundreds or thousands of
+# values. They are indexed. They are read from disk. They are allowed to fall
+# out of memory when they have not been recently referenced. These are behaviors
+# that conjure an image of a page of values.
+#
+# Otherwise, we use common teriminology for *order*, *depth*, *parent*, *child*,
+# *split* and *merge*. There is no hard and fast definition for all the terms. A
+# leaf is a fuzzy concept in b-tree literature, for example. We call a page that
+# contains records a *leaf page*. We call a non-leaf page a *branch page*.
+#
+# The term *b-tree* itself may not be correct. There are different names for
+# b-tree that reflect the variations of implementation, but those distinctions
+# have blurred over the years. Our implemenation may be considered a b+tree,
+# since pages are linked, and records are stored only in the leaves.
+#
+# Terms specific to our implementation will be introduced as they are
+# encountered in the document.
+#
+# ### What is a b-tree?
+#
+# This documentation assumes that you understand the theory behind the b-tree,
+# and know the variations of implementation. If you are interested in learning
+# about b-trees, this document could be a learning aid for you, but you should
+# start with the Wikipedia articles on
+# [B-trees](http://en.wikipedia.org/wiki/B-tree) and
+# [B+trees](http://en.wikipedia.org/wiki/B%2B_tree).
+#
+# ### What flavor of b-tree is this?
+#
+# Strata is a b-tree with leaf pages that contain records ordered by the
+# collation order of the tree, indexed for retrieval in constant time, so that
+# they can be found using binary search. Branch pages contain links to other
+# pages, and do not store records themselves. Leaf pages are linked in ascending
+# order to ease the implementation of cursors.
+#
+# There is a maximum size for a page, that limits the number of links, in the
+# case of branch pages, or records in the case of leaf pages. When the maximum
+# size is reached which point a node is split. When two sibling pages next to
+# each other contain keys or records that combined are the less than than
+# maximum size they are merged.
+#
+# The tree always has a root branch page. The order of the tree increases when
+# the root branch is split. It decreases when the root branch is merged. The
+# split of the root branch is a different operation from the split of a non-root
+# branch, because the root branch does not have siblings.
+#
+# ### Page Structure
+#
+# The b-tree has two types of pages. Leaf pages and branch pages.
+#
+# #### Leaf Pages
+#
+# Leaf pages contain records.
+#
+# In the abstract, a leaf page is an array data structure with zero based
+# integer index. The elements of the structure contain records. Given an
+# integer, the leaf page will return the record stored at element in the array.
+# This lookup is performed in constant time.
+#
+# The records in the record array are ordered according to the collation of the
+# b-tree. Because of this, and because a lookup takes constant time, we can search
+# for a record in a leaf page using binary search in logorithmic time.
+#
+# The first record of every leaf page must be unique in relation to the first
+# record of every other leaf page. The b-tree will accept records that are
+# dupcliates according to the collation. When we split a page, we cannot cannot
+# choose a partition that has the same value as the first record, since that
+# would violate the unique first record constraint.
+#
+# If a page reaches the maximum leaf page size, filled with records that have
+# the same value according to the collation, the leaf page is cannot split,
+# since no suitable partition exists. The page will be allowed to grow beyond
+# the maximum page size.
+#
+# #### Branch Pages
+#
+# Branch pages contain links to other pages. To find the a record in the b-tree,
+# we first use branch pages to find the leaf page that contains our record.
+#
+# All pages are identified by a unique page address. The page address is an
+# integer assigned to the page when the page is created. Branch pages link to
+# other pages by storing the address of the referenced page in an array of page
+# addresses. This array of page addresses is essentially an *array of children*.
+#
+# A branch page orders its children according to the b-tree collation using a
+# record obtained from a leaf page as a *key*. A branch page always has one more
+# children than the number of keys. Keys are unique. A key used in a branch page
+# will not equal another key used in any branch page according to the collation.
+#
+# There are two special types of branch pages, the root page, and penultimate
+# pages.
+#
+# ##### Root Page
+#
+# The root page is the first page we consult to find the desired leaf page. Our
+# b-tree always contains a root page. The b-tree is never so empty that the root
+# page disappears. The root page always has the same address.
+#
+# TK move. Until the root branch page is split, it is both the root branch page
+# and a penultimate branch page.
+#
+# ##### Penultimate Pages
+#
+# A penultimate branch page is a branch page whose children are leaf pages. If a
+# branch page is not penultimate, then its children are branch pages.
+#
+# In a penultimate branch page, the array of children is ordered by the b-tree
+# collation using a first record in the referenced leaf page for ordering. That
+# is, the first record of the leaf page is used as the key associated with a
+# page address in a penultimate branch page.
+#
+# The non-leaf nodes of a b-tree have the property that the number of node
+# children is one greater than the number of keys. We obtain this property by
+# treating the first child as the left child of the entire page, and excluding
+# its key from the search. We search the subsequent keys to find the first key
+# that is grater than or equal to the record sought. Essentially, when we
+# encouter a key that is greater than our sought record, we know that the record
+# is contained in the leaf page child associated with the key before it.
+# Although it sounds linear, we are able to perform this search using binary
+# search in logorithmic time.
+#
+# By ignoring the key of the first leaf page, the penultimate branch page has a
+# number of children that is one greater than the number of keys.
+#
+# Notice that, when we are inserting a record into a leaf page other than the
+# left leaf page, we add it to a leaf page whose key is equal to or greater than
+# the penultimate branch key, so that the first record does not change, and
+# therefore that penultimate branch key does not change. The exception is the
+# left leaf page, which accepts all the records less than the first key, and
+# therefore may accept a record less than its current least record.
+#
+# An insertion can only insert a into the left most leaf page of a penumltimate
+# branch page a record less than the least record of the leaf page.
+#
+# ##### Interior Branch Pages
+#
+# A branch page whose children are other branch pages is called an interior
+# branch page.
+#
+# Like the penultimate branch page, we treat the first child of an interior
+# branch page as the left child of the entire page. Like the penultimate branch
+# page the subsequent children have an associated key that is the first record
+# of a leaf page.
+#
+# The key is obtained by decending the sub-tree referenced by the child. We
+# first visit the branch page referneced by the child. We then visit left
+# children recursively, visiting the left child of the child, and the left child
+# of any subsquently visited children, until we reach a leaf page.  The first
+# record of that leaf page is the key to associate with the child address in the
+# address array of the interior branch page.
+#
+# It is the nature of the b-tree that keys move up to the higher levels of the
+# tree as pages split, while preserving the collation order of the keys. When a
+# branch page splits, a key from the middle of the page is chosen as a
+# partition. The partition is used as the key for the right half of the split
+# page in the parent page.
+#
+# Our implementation does not store the keys, as you may have noticed, but
+# decends down to the leaf page to fetch the record to use as a key.
+#
+# We start from a penultimate page as a root page. When a leaf page fills, we
+# split it, creating a new right leaf page. The penultimate page uses the first
+# record of the new right page as the key to associate with that page.
+#
+# When the root penultimate page is full we split it, so that the root page is
+# an interior page with two children, which are two penultimate pages. The tree
+# now contains a root interior branch page, with a left penultimate branch page
+# and a right penultimate branch page.
+#
+# The root interior branch page has one key. Prior to split, that key was
+# associated with the address of a child leaf page. After split, the key is
+# associated with the right penultimate branch page. The leaf page is now the
+# left child of the right penultimate branch page.
+#
+# When we visit the root interior page, to obtain the key to associate with the
+# right penultimate page, we visit the right penultimate page, then we visit its
+# left child, the leaf page whose first record is the key.
+#
+# #### Keys and First Records
+#
+# We said that it is only possible for an insertion to insert a into the left
+# most child leaf page of a penumltimate branch page a record less than the least
+# record. We can say about a tree rooted by an interor branch page, that an
+# insertion is only able to insert into the left most leaf page in the *entire
+# tree* a record less than the least record.
+#
+# Using our example tree with one root interior page, with two penultimate
+# branch page children, we cannot insert a record into the right penultimate
+# branch page that will displace the first record of its left most child branch,
+# because that first record is the key for the right penultimate branch page.
+# When we insert a record that is less than the key, the search for a leaf page
+# to store the record goes to the left of the key. It cannot descend into the
+# right penultimate branch page, so it is impossible for it be inserted into
+# left child of the right penultimate branch page, so the first record left
+# child of the penultimate branch page will never be displaced by an insertion.
+#
+# Only if we insert a record that is less than least key of the left penultimate
+# page do we face the possibility of displacing the first record of a leaf page,
+# and that leaf page is the left most leaf page in the entire tree.
+#
+# This maintains a property of the b-tree that for every leaf page except the
+# left most leaf page, there exists a unique branch page key derived from the
+# first record of the page.
+#
+# As above, you can find the first record used to derive a key by visting the
+# child and going left. You can find the leaf page to the left of the leaf page
+# used to derive a page branch key, by visiting the child to the left of the key
+# and going right.
+#
+# NOTE Literate programming has finally materialized with Docco and
+# CoffeeScript.
+#
+# When the root page splits, it becomes an interior branch page. Until it splits
+# it is both the root page and a penultimate page.

 # Requried node.js libraries.
 fs = require "fs"
@@ -18,6 +248,27 @@
   console.log.apply null, splat if splat.length
   process.exit 1
 say = (splat...) -> console.log.apply null, splat
+
+# ### Collation
+#
+# A b-tree has a client defined collation. The collation is determined by the
+# combination of an extractor and a comparator. The extractor is used to
+# extract or calculate from the stored record the fields values pertient to the
+# collation. The comparator is used to order records by comparing the extracted
+# fields.
+#
+# The extractor allows us to cache the fields pertinent to the collation. It may
+# be the case that records have large fields that are not pertinent to the
+# collation. If we are only using the record as a point of reference while
+# searchng the tree, we allow the record to fall out of the cache, and hold onto
+# only the pertient fields.
+#
+# This strategy wouldn't work if extraction and comparison were the same
+# function. By making them separate functions, we can cache the intermediate
+# extraction step.
+#
+# Additionally, the comparitor is pretty easily generalized, while the exractor
+# is invariably specialized.

 # Default comparator is good only for strings, use a - b for numbers.
 comparator = (a, b) ->
@@ -288,6 +539,20 @@
       true
     false

+# ### B-Tree Structure
+#
+# We call page of the b-tree, ah, maybe we stop using the word tier, start using
+# the word page, because we're not going to have to reduce the ambiguity with
+# some sort of subdivided binary file structure.
+#
+# NOTE Come back and look at this. It is succinct.
+#
+# There are two types of pages. Branch pages and leaf pages.
+#
+# Leaf pages store the records. They maintain the collation
+#
+# Leaf pages are all uniformly the same.
+#
 # ### Leaf Tier Files
 #
 # A leaf tier maintains an array of file positions called an address array. A
@@ -455,9 +720,39 @@
     fs.close(@fd, _) if @fd
     @position = @fd = null

-# ### Branch Tier Files
-#
-# A branch tier file contains an array of object addresses
+# ### Tier File Storage
+#
+# Tiers are identified by a number tier address. The tier address is a number
+# that is incremented as new tier are created. A branch tier file has a file
+# name that includes the tier address. The file name is derived from the tier
+# address. When we load a tier, we first derive the file name from the tier
+# address, then we load the file.
+#
+# The leaf tier file contains insert objects, delete objects and address array
+# objects, stored as JSON, one object per line, as described above.
+#
+# The branch tier file contains a single object that includes the array of
+# tier addresses. The tier addresses reference leaf tier files. The tier
+# addresses are sorted according to the value of the objects in leaf tiers they
+# reference. The addresses are sorted by the tree collation using the record
+# from the referenced leaf tier that has the least value according to the tree
+# collation.
+#
+# Put another way, each branch element in a branch tier represents a leaf tier.
+# The leaf tiers are sorted by the tree collation. The object value used to sort
+# the branch tier is first element in the sorted leaf tier.
+#
+# TODO Okay, what? I'm forgetting that I need the addresses to inner tiers as
+# well as to leaf tiers, both, when the tier is not penultimate. Descending is
+# not going to work, that is going to load the entire tree into memory quickly.
+#
+# Hmm... Maybe not. Tree four deep means loading only a particular path. It
+# ensures that we read lock our way down the tree to get our record. Although, I
+# believe I thought long and hard about this, and jumping down to the leaves
+# wasn't a problem.
+#
+# We only need to store the sorted array of the leaf tier addresses in our
+# branch tier, since we can obtain the object used to collate the address

 #
 class IO
@@ -527,55 +822,8 @@
     previous.next = next
     entry

-  # TODO Move down, bring read and write up.
-  lock: (exclusive, address, leaf, callback) ->
-    if not tier = @cache[address]
-      @cache[address] = tier =
-        @link({ leaf, address, cache: {}, addresses: [], locks: [[]] })
-    locks = tier.locks
-    if tier.loaded
-      lock = callback
-    else
-      lock = (error, tier) =>
-        if error
-          callback error
-        else if tier.loaded
-          callback null, tier
-        else if leaf
-          @readLeaves tier, callback
-        else
-          @readBranches tier, callback
-    if exclusive
-      throw new Error "already locked" unless locks.length % 2
-      locks.push [ lock ]
-      locks.push []
-      if locks[0].length is 0
-        locks.shift()
-        lock(null, tier)
-    else
-      locks[locks.length - 1].push lock
-      if locks.length is 1
-        lock(null, tier)
-
-  resume: (tier, continuations) ->
-    process.nextTick =>
-      for callback in continuations
-        callback(null, tier)
-
-  # Remove the lock callback from the callback list..
-  release: (tier) ->
-    locks = tier.locks
-    running = locks[0]
-    running.shift()
-    say { locks }
-    if running.length is 0 and locks.length isnt 1
-      locks.shift()
-      @resume tier, locks[0] if locks[0].length
-
-  upgrade: (tier, _) ->
-    @release tier
-    @lock true, tier.address, false, _
-
+  # Generate a file name for a tier file with an optional suffix for temporary
+  # rewrite files. Tier file names are generated from the tier address.
   filename: (address, suffix) ->
     suffix or= ""
     padding = "00000000".substring(0, 8 - String(address).length)
@@ -595,6 +843,87 @@
       cache: {}
       right: right
       io: new LeafIO @filename address
+
+  # Going forward, every will story keys, so that when we encounter a record, we
+  # don't have to run the extractor, plus we get some caching.
+  #
+  # Or maybe just three arrays, key, object and address? I'm talking about using
+  # each key as an object cache, by adding an object member to the key. But,
+  # they cache container is a hash table, which is a compactish representation,
+  # so why not make it yet anohter hash table? The logic gets a lot easier.
+  object: (tier, index, _) ->
+    address = tier.addresses[index]
+    if not object = tier.cache[address]
+      object = tier.cache[address] = tier.io.readObject address, _
+    object
+
+  key: (tierOrAddress, index, _) ->
+    if typeof tierOrAddress is "number"
+      leaf  = @lock false, tierOrAddress, true, _
+      key = @extractor @object leaf, index, _
+      @release leaf
+      key
+    else if tierOrAddress.leaf
+      @extractor @object tierOrAddress, index, _
+    else
+      address = tierOrAddress.addresses[index]
+      if not key = tierOrAddress.cache[address]
+        # Here's something to consdier. The only time we lock the leaf is during
+        # descent, to read in the key.
+        #
+        # If this leaf tier is locked exclusively, we are not the ones holding the
+        # lock, because that would mean that we are locking the inner tier, we
+        # would already have traversed the inner tier, the key would be in the
+        # cache.
+        #
+        # If this leaf tier has an exclusive lock pending, it is not a lock that
+        # we are waiting for, so we can append a shared lock and wait for multiple
+        # exclusive and shared locks to clear.
+        key = tierOrAddress.cache[address] = @key address, 0, _
+      key
+
+  # Write the branches to a file as a JSON string. We tuck the tier properties
+  # into an object before we serialize it, so that it is easy deserialize.
+  writeBranches: (tier, suffix, _) ->
+    filename = @filename tier.address, suffix
+    record = [ tier.penultimate, tier.next?.address, tier.addresses ]
+    json = JSON.stringify(record) + "\n"
+    fs.writeFile filename, json, "utf8", _
+
+  # Read branches from a brach tier file and assign the tier properties to the
+  # given tier object.
+  #
+  # TODO: Move link to head always, to maintain MRU.
+  readBranches: (tier, _) ->
+    filename = @filename tier.address
+    json = fs.readFile filename, "utf8", _
+    [ penultimate, next, addresses ] = JSON.parse json
+    extend tier, { penultimate, next, addresses }
+
+  # TODO Consider: can't I always just rewrite and link?
+  rewriteBranches: (tier, _) ->
+    @writeBranches(tier, ".new", _)
+
+  # TODO Ensure that you close the current tier I/O. Also, you must also be very
+  # much locked before you use this, but I'm sure you know that.
+
+  # Compact a leaf tier file by writing it to a temporary file that will become
+  # the permanent file. All of records referenced by the current address array a
+  # appended to a new leaf tier file using insert objects. An address array
+  # object is appended to the end of new leaf tier file. The new file will load
+  # quickly, because the address array object will be found immediately.
+  rewriteLeaves: (tier, _) ->
+    io = new LeafIO @filename(tier.address, ".new")
+    addresses = []
+    cache = {}
+    for i in [0...tier.addresses.length]
+      object = @object tier, i, _
+      address = io.writeInsert(object, i, _)
+      addresses.push address
+      cache[address] = object
+    io.writeAddresses(tier.right, addresses, _)
+    io.close(_)
+    extend tier, { addresses, cache }

   readLeaves: (tier, _) ->
     filename = @filename tier.address
@@ -649,87 +978,6 @@
     io = new LeafIO @filename tier.address
     tier = extend tier, { addresses, cache, loaded, io }

-  # Going forward, every will story keys, so that when we encounter a record, we
-  # don't have to run the extractor, plus we get some caching.
-  #
-  # Or maybe just three arrays, key, object and address? I'm talking about using
-  # each key as an object cache, by adding an object member to the key. But,
-  # they cache container is a hash table, which is a compactish representation,
-  # so why not make it yet anohter hash table? The logic gets a lot easier.
-  object: (tier, index, _) ->
-    address = tier.addresses[index]
-    if not object = tier.cache[address]
-      object = tier.cache[address] = tier.io.readObject address, _
-    object
-
-  key: (tierOrAddress, index, _) ->
-    if typeof tierOrAddress is "number"
-      leaf  = @lock false, tierOrAddress, true, _
-      key = @extractor @object leaf, index, _
-      @release leaf
-      key
-    else if tierOrAddress.leaf
-      @extractor @object tierOrAddress, index, _
-    else
-      address = tierOrAddress.addresses[index]
-      if not key = tierOrAddress.cache[address]
-        # Here's something to consdier. The only time we lock the leaf is during
-        # descent, to read in the key.
-        #
-        # If this leaf tier is locked exclusively, we are not the ones holding the
-        # lock, because that would mean that we are locking the inner tier, we
-        # would already have traversed the inner tier, the key would be in the
-        # cache.
-        #
-        # If this leaf tier has an exclusive lock pending, it is not a lock that
-        # we are waiting for, so we can append a shared lock and wait for multiple
-        # exclusive and shared locks to clear.
-        key = tierOrAddress.cache[address] = @key address, 0, _
-      key
-
-  # Write the branches to a file as a JSON string. We tuck the tier properties
-  # into an object before we serialize it, so that it is easy deserialize.
-  writeBranches: (tier, suffix, _) ->
-    filename = @filename tier.address, suffix
-    record = [ tier.penultimate, tier.next?.address, tier.addresses ]
-    json = JSON.stringify(record) + "\n"
-    fs.writeFile filename, json, "utf8", _
-
-  # Read branches from a brach tier file and assign the tier properties to the
-  # given tier object.
-  #
-  # TODO: Move link to head always, to maintain MRU.
-  readBranches: (tier, _) ->
-    filename = @filename tier.address
-    json = fs.readFile filename, "utf8", _
-    [ penultimate, next, addresses ] = JSON.parse json
-    extend tier, { penultimate, next, addresses }
-
-  # TODO Consider: can't I always just rewrite and link?
-  rewriteBranches: (tier, _) ->
-    @writeBranches(tier, ".new", _)
-
-  # TODO Ensure that you close the current tier I/O. Also, you must also be very
-  # much locked before you use this, but I'm sure you know that.
-
-  # Compact a leaf tier file by writing it to a temporary file that will become
-  # the permanent file. All of records referenced by the current address array a
-  # appended to a new leaf tier file using insert objects. An address array
-  # object is appended to the end of new leaf tier file. The new file will load
-  # quickly, because the address array object will be found immediately.
-  rewriteLeaves: (tier, _) ->
-    io = new LeafIO @filename(tier.address, ".new")
-    addresses = []
-    cache = {}
-    for i in [0...tier.addresses.length]
-      object = @object tier, i, _
-      address = io.writeInsert(object, i, _)
-      addresses.push address
-      cache[address] = object
-    io.writeAddresses(tier.right, addresses, _)
-    io.close(_)
-    extend tier, { addresses, cache }
-
   # Move a new branch tier file into place. Unlink the existing branch tier
   # file, then rename the new branch tier file to the permanent name of the
   # branch tier file.
@@ -744,6 +992,55 @@
     catch e
       throw e unless e.code is "ENOENT"
     fs.rename replacement, permanent, _
+
+  # TODO Move down, bring read and write up.
+  lock: (exclusive, address, leaf, callback) ->
+    if not tier = @cache[address]
+      @cache[address] = tier =
+        @link({ leaf, address, cache: {}, addresses: [], locks: [[]] })
+    locks = tier.locks
+    if tier.loaded
+      lock = callback
+    else
+      lock = (error, tier) =>
+        if error
+          callback error
+        else if tier.loaded
+          callback null, tier
+        else if leaf
+          @readLeaves tier, callback
+        else
+          @readBranches tier, callback
+    if exclusive
+      throw new Error "already locked" unless locks.length % 2
+      locks.push [ lock ]
+      locks.push []
+      if locks[0].length is 0
+        locks.shift()
+        lock(null, tier)
+    else
+      locks[locks.length - 1].push lock
+      if locks.length is 1
+        lock(null, tier)
+
+  resume: (tier, continuations) ->
+    process.nextTick =>
+      for callback in continuations
+        callback(null, tier)
+
+  # Remove the lock callback from the callback list..
+  release: (tier) ->
+    locks = tier.locks
+    running = locks[0]
+    running.shift()
+    say { locks }
+    if running.length is 0 and locks.length isnt 1
+      locks.shift()
+      @resume tier, locks[0] if locks[0].length
+
+  upgrade: (tier, _) ->
+    @release tier
+    @lock true, tier.address, false, _

 class exports.Strata
   # Construct the Strata from the options.


Note constant address of root and left most leaf.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -921,6 +921,11 @@
   #
   # An empty database has a single root penultimate branch page with only a left
   # child and no keys. The left child is a single leaf page that is empty.
+  #
+  # Note that the address of the root branch page is `0` and the address of the
+  # left most leaf page is `-1`. This will not change. Even as the b-tree is
+  # balanced with splits and mergers of leaf pages, the root branch page is
+  # always `0` and left most leaf page is always `-1`.

   #
   create: (_) ->


Color highlighting for Terminal messages

--- lib/atox.coffee
+++ lib/atox.coffee
@@ -93,7 +93,7 @@
       @chatpanel.addChat { cid: -2, img: 'none', event: @event }

       @event.on 'Terminal', (data) =>
-        @chatpanel.addMessage {cid: -2, msg: data, name: 'aTox', color: "rgba(255, 255, 255 ,1)"}
+        @chatpanel.addMessage {cid: -2, msg: "" + data + "", name: 'aTox', color: "rgba(255, 255, 255 ,1)"}

       @TOX.startup()
       @mainWin.showAT() if atom.config.get 'aTox.showDefault'


Forgot to not pass Game inst. to gen funcs

--- src/game.coffee
+++ src/game.coffee
@@ -47,10 +47,10 @@
 		@camera.target = @player
 		@timeManager.targets.push @player

-		@transitionToMap (MapGenerator.generateBigRoom @, 80, 25), 2, 2
+		@transitionToMap (MapGenerator.generateBigRoom 80, 25), 2, 2

 		@events.on 'key.n', =>
-			newMap = (MapGenerator.generateCellularAutomata @, 80, 21)
+			newMap = (MapGenerator.generateCellularAutomata 80, 21)
 			[startX, startY] = []

 			for row, y in newMap.data


facebook canvas test. Fix music/sound data loading.

--- src/gurk.coffee
+++ src/gurk.coffee
@@ -276,7 +276,7 @@

   playSound : (sound) =>
     return if not @getSoundSetting()
-    sound = "data/sounds/#{sound}"
+    sound = "/data/sounds/#{sound}"
     eburp.resources.get sound, (resource) =>
       resource.data.volume = 0.25
       resource.data.play()
@@ -284,7 +284,7 @@
   playMusic : (track,musicOn=@getMusicSetting()) =>
     @music = track
     return if not musicOn
-    track = "data/music/#{track}"
+    track = "/data/music/#{track}"
     reference = eburp.resources.get track, (resource) =>
       @stopMusic()
       @activeTrack = reference


complete workspace init

complete workspace init

--- lib/dobi.coffee
+++ lib/dobi.coffee
@@ -3,6 +3,7 @@
 fs = require 'fs'
 open = require 'open'
 optimist = require 'optimist'
+path = require 'path'
 readline = require 'readline'

 # usage
@@ -38,6 +39,16 @@
 exit = (msg) ->
   log msg if msg
   process.exit()
+
+getWorkspacePath = (current, next) ->
+  [current, next] = [CWD, current] if not next
+  fs.exists path.join(current, 'dobi.json'), (exists) ->
+    if exists
+      next current
+    else
+      parent = path.join current, '..'
+      return next null if parent is current
+      getWorkspacePath parent, next

 log = (msg) ->
   console.log "[dobi] #{msg}"
@@ -102,7 +113,15 @@

   # initialize a workspace
   when 'init'
-    exit 'not available yet'
+    getWorkspacePath (workspace) ->
+      exit "already in a workspace: #{workspace}" if workspace
+      fs.writeFile path.join(CWD, 'dobi.json'), JSON.stringify({
+        created: Date.now()
+      }), (err) ->
+        exit 'failed to create workspace config' if err
+        fs.mkdir path.join(CWD, 'pkg'), (err) ->
+          exit 'failed to create pkg directory' if err
+          exit "workspace successfully created at: #{CWD}"

   # create a site using your app
   when 'install'


:art: cleaner code for focusing to replace editor on use-selection-as-replace-pattern

--- lib/find.coffee
+++ lib/find.coffee
@@ -49,11 +49,7 @@

     @subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:use-selection-as-find-pattern', =>
       return if @projectFindPanel?.isVisible() or @findPanel?.isVisible()
-
-      @createViews()
-      @projectFindPanel.hide()
-      @findPanel.show()
-      @findView.focusFindEditor()
+      showPanel @findPanel, @projectFindPanel, => @findView.focusReplaceEditor()

     @subscriptions.add atom.commands.add 'atom-workspace', 'find-and-replace:use-selection-as-replace-pattern', =>
       return if @projectFindPanel?.isVisible() or @findPanel?.isVisible()


Use findOneAndUpdate instead of multiple steps.

--- src/models/task.coffee
+++ src/models/task.coffee
@@ -50,13 +50,12 @@
     'status': 'IDLE'
   }

-  res = yield @findOne(query).sort('priority').exec()
-
-  if res?
-    res.status = 'LOADING'
-    yield res.save()
-
-  res
+  yield @findOneAndUpdate(query, {
+    '$set':
+      'status': 'LOADING'
+  }, {
+    new: true, sort:'priority'
+  })

 TaskSchema.methods.runAsNextTask = (nw) -> co =>


:lipstick:

:lipstick:

This is a totally subjective style change. I just prefer keeping code
that is rarely changed tight and dense. Another option would be to
move the menu templates into a separate file.

--- src/main.coffee
+++ src/main.coffee
@@ -16,7 +16,7 @@

     @setupJavaScriptArguments()
     @parseCommandLine()
-    @buildApplicationmenu()
+    @buildApplicationMenu()
     @setupNodePaths()
     @handleEvents()

@@ -28,62 +28,33 @@
     args = optimist(modifiedArgv).argv
     @resourcePath = args['resource-path']

-  buildApplicationmenu: ->
-    template = [
+  buildApplicationMenu: ->
+    atomMenu =
       label: 'Atom'
-      submenu: [
-        label: 'About Atom'
-        selector: 'orderFrontStandardAboutPanel:'
-      ,
-        type: 'separator'
-      ,
-        label: 'Hide Atom Shell'
-        accelerator: 'Command+H'
-        selector: 'hide:'
-      ,
-        label: 'Hide Others'
-        accelerator: 'Command+Shift+H'
-        selector: 'hideOtherApplications:'
-      ,
-        label: 'Show All'
-        selector: 'unhideAllApplications:'
-      ,
-        type: 'separator'
-      ,
-        label: 'Quit'
-        accelerator: 'Command+Q'
-        click: -> app.quit()
-      ]
-    ,
+      submenu:
+        { label: 'About Atom', selector: 'orderFrontStandardAboutPanel:' }
+        { type: 'separator' }
+        { label: 'Hide Atom Shell', accelerator: 'Command+H', selector: 'hide:' }
+        { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
+        { label: 'Show All', selector: 'unhideAllApplications:' }
+        { type: 'separator' }
+        { label: 'Quit', accelerator: 'Command+Q', click: -> app.quit() }
+
+    viewMenu =
       label: 'View'
-      submenu: [
-        label: 'Reload'
-        accelerator: 'Command+R'
-        click: -> BrowserWindow.getFocusedWindow()?.reloadIgnoringCache()
-      ,
-        label: 'Toggle DevTools',
-        accelerator: 'Alt+Command+I',
-        click: -> BrowserWindow.getFocusedWindow()?.toggleDevTools()
-      ]
-    ,
+      submenu:
+        { label: 'Reload', accelerator: 'Command+R', click: -> BrowserWindow.getFocusedWindow()?.reloadIgnoringCache() }
+        { label: 'Toggle DevTools', accelerator: 'Alt+Command+I', click: -> BrowserWindow.getFocusedWindow()?.toggleDevTools() }
+
+    windowMenu =
       label: 'Window'
-      submenu: [
-        label: 'Minimize'
-        accelerator: 'Command+M'
-        selector: 'performMiniaturize:'
-      ,
-        label: 'Close'
-        accelerator: 'Command+W'
-        selector: 'performClose:'
-      ,
-        type: 'separator'
-      ,
-        label: 'Bring All to Front'
-        selector: 'arrangeInFront:'
-      ]
-    ]
+      submenu:
+        { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }
+        { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }
+        { type: 'separator' }
+        { label: 'Bring All to Front', selector: 'arrangeInFront:' }

-    @menu = Menu.buildFromTemplate template
+    @menu = Menu.buildFromTemplate [atomMenu, viewMenu, windowMenu]

   setupNodePaths: ->
     resourcePaths = [


store client cookies in apps user directory

--- src/main.coffee
+++ src/main.coffee
@@ -3,12 +3,18 @@
 login  = require './login'
 ipc    = require 'ipc'
 fs     = require 'fs'
+path   = require 'path'
 appmenu = require './appmenu'

 client = new Client()

 app = require 'app'
 BrowserWindow = require 'browser-window'
+
+client = new Client
+    rtokenpath:  path.normalize path.join app.getPath('userData'), '/refreshtoken.txt'
+    cookiespath: path.normalize path.join app.getPath('userData'), '/cookies.json'
+

 mainWindow = null


fix bug: pass env to custom-configs

fix bug: pass env to custom-configs

--- src/main.coffee
+++ src/main.coffee
@@ -51,7 +51,7 @@

         @env ?= process.env.NODE_ENV or 'development'

-        customConfigs = new CustomConfigs(configs)
+        customConfigs = new CustomConfigs(configs, @env)
         configObj = customConfigs.toObject()

         @configJSONGenerator = new ConfigJSONGenerator(configObj, @env)


concat cwd path to config path only when config path is relative

--- src/main.coffee
+++ src/main.coffee
@@ -33,7 +33,9 @@

     # dont load default config over opts and args
     unless opts.config is defaultConfigPath
-        config.merge(require(path.join(process.cwd(), opts.config)))
+        unless opts.config[0] is '/'
+            opts.config = require(path.join(process.cwd(), opts.config))
+        config.merge(opts.config)

     # Kludge:
     if opts.stdout


Make hide the window (close to tray) work on all platforms if "Close to tray" is checked

--- src/main.coffee
+++ src/main.coffee
@@ -371,8 +371,8 @@
             ipcsend n, e

     # Emitted when the window is about to close.
-    # For OSX only hides the window if we're not force closing.
-    mainWindow.on 'close', (ev) ->
+    # Hides the window if we're not force closing.
+    mainWindow.on 'unload', (ev) ->
         if process.platform == 'darwin'
             if mainWindow.isFullScreen()
                 mainWindow.setFullScreen false


Make StyleDocco silent by default, and add --verbose flag

--- src/main.coffee
+++ src/main.coffee
@@ -22,6 +22,7 @@
     .default('resources', path.resolve(__dirname, '../resources'))
   .describe('preprocessor', 'Custom preprocessor command')
   .describe('include', 'CSS to include on all pages')
+  .describe('verbose', 'Display status messages to stdout')
   .describe('version', 'Display StyleDocco version')
   .argv
 options.in = options._[0] or './'
@@ -40,6 +41,13 @@
   else
     ''

+log =
+  if options.verbose
+    (str) -> console.log str
+  else
+    () ->
+
+
 # Get sections of matching doc/code blocks.
 getSections = (filename) ->
   lang = langs.getLanguage filename
@@ -66,7 +74,7 @@
   render = (data) ->
     template = fs.readFileSync templateFile, 'utf-8'
     html = jade.compile(template, filename: templateFile, pretty: on)(data)
-    console.log "styledocco: #{source} -> #{path.join options.out, dest}"
+    log "styledocco: #{source} -> #{path.join options.out, dest}"
     writeFile dest, html
   if langs.isSupported(source) and options.preprocessor isnt 'none'
     # Run source through suitable CSS preprocessor.
@@ -122,7 +130,7 @@
 writeStaticFile = (fileName) ->
   outPath = path.join options.out, fileName
   fs.writeFileSync outPath, fs.readFileSync getResourcePath(fileName), 'utf-8'
-  console.log "styledocco: writing #{outPath}"
+  log "styledocco: writing #{outPath}"

 writeStaticFile 'docs.css'
 writeStaticFile 'docs.js'


remove trailing semicolons

remove trailing semicolons

--- lib/main.coffee
+++ lib/main.coffee
@@ -60,7 +60,7 @@
           customArguments.push(script)

         args = defaultArguments.concat(customArguments)
-        nslCmd = spawn('java', args);
+        nslCmd = spawn('java', args)

         nslCmd.stderr.on 'data', (data) ->
           atom.notifications.addError("Transpiling failed", detail: data, dismissable: true)
@@ -75,9 +75,9 @@

   getPath: (callback) ->
     if os.platform() is 'win32'
-      whichJava = spawn('where', ['java']);
+      whichJava = spawn('where', ['java'])
     else
-      whichJava = spawn('which', ['java']);
+      whichJava = spawn('which', ['java'])

     # Find Java
     whichJava.stderr.on 'data', (data) ->


main: Rename setStatusBarElement to setStatusBar

--- lib/main.coffee
+++ lib/main.coffee
@@ -64,7 +64,7 @@
         @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
             if item and item is atom.workspace.getActiveTextEditor()
                 @editor = item
-                @setStatusBarElement()
+                @setStatusBar()

     deactivate: ->
@@ -117,7 +117,7 @@
             @kernelManager.destroyRunningKernel kernel
             @clearResultBubbles()
             @kernelManager.startKernel kernelSpec, grammar, =>
-                @setStatusBarElement()
+                @setStatusBar()
                 kernel = @kernelManager.getRunningKernelFor language
                 @setWatchSidebar kernel

@@ -126,7 +126,7 @@
                 @kernelManager.destroyRunningKernel kernel
             @clearResultBubbles()
             @kernelManager.startKernel kernelSpec, grammar, =>
-                @setStatusBarElement()
+                @setStatusBar()
                 kernel = @kernelManager.getRunningKernelFor language
                 @setWatchSidebar kernel

@@ -147,7 +147,7 @@
             return

         @kernelManager.startKernelFor grammar, (kernel) =>
-            @setStatusBarElement()
+            @setStatusBar()
             @_createResultBubble kernel, code, row

@@ -287,9 +287,9 @@
         while @statusBarElement.hasChildNodes()
             @statusBarElement.removeChild @statusBarElement.lastChild

-    setStatusBarElement: ->
+    setStatusBar: ->
         unless @statusBarElement?
-            console.error 'setStatusBarElement: there is no status bar'
+            console.error 'setStatusBar: there is no status bar'
             return

         @removeStatusBarElement()


:bug: Prevent on lint parse errors on empty files

--- lib/main.coffee
+++ lib/main.coffee
@@ -154,14 +154,14 @@
               range: [[lineIdx, 0], [lineIdx, colEndIdx]]
             ]
           else
-            atom.notifications.addError """
-              **sass-lint had a problem**
-              Please consider filing an issue with [linter-sass-lint](https://github.com/AtomLinter/linter-sass-lint)
-              or [sass-lint](https://github.com/sasstools/sass-lint) including the text below and any other
-              information possible.
-
-              #{error.stack}
-            """, {dismissable: true}
+            # Leaving this here to allow people to report the errors
+            console.log(error.stack)
+            return [
+              type: 'Error'
+              text: 'Unexpected parse error in file'
+              filePath: filePath
+              range: [[lineIdx, 0], [lineIdx, colEndIdx]]
+            ]
           return []

         if result then return result.messages.map (msg) ->


Fix toggle when minimap was disabled

Fix toggle when minimap was disabled

--- lib/main.coffee
+++ lib/main.coffee
@@ -104,7 +104,7 @@

   # Activates the minimap package.
   activate: ->
-
+    @active = true
     @toggle() if atom.config.get 'minimap.autoToggle'

   # Deactivates the minimap package.
@@ -116,6 +116,7 @@
       @editorsMinimaps.delete(key)
     @editorsMinimaps = undefined
     @toggled = false
+    @active = false

   # Verifies that the passed-in version expression is satisfied by
   # the current minimap version.
@@ -131,6 +132,7 @@

   # Toggles the minimap display.
   toggle: ->
+    return unless @active
     if @toggled
       @toggled = false
       @editorsMinimaps?.forEach (value, key) =>


Rewrite window management logic

Rewrite window management logic

--- lib/main.coffee
+++ lib/main.coffee
@@ -6,43 +6,53 @@
 {CompositeDisposable} = require 'atom'
 CodewarsView = require './codewars-view'

+DATA_DIR = 'codewars-workspace'
+OPEN_FILE_KEY = DATA_DIR + '-loading'
+
 module.exports = Codewars =
-  dataDir: 'codewars-workspace'
+  subscriptions: null
   codewarsView: null
-  subscriptions: null
-  openNewWindow: false
-  windowWasOpened: false
-
-  checkWindowLock: (callback) ->
-    writeLockFile = (cb) =>
-      fs.writeFile @windowLockPath, atom.getCurrentWindow().id, cb
-
-    fs.readFile @windowLockPath, (err, data) =>
-      if err or atom.getCurrentWindow().id is 1
-        @clearWindowLock =>
-          writeLockFile (err) =>
-            throw err if err
-            @openNewWindow = true
-            callback false
-      else if (parseInt data.toString()) < atom.getCurrentWindow().id
-        writeLockFile (err) ->
-          throw err if err
-          callback true
-      else
-        callback false
-
-  clearWindowLock: (callback) ->
-    fs.unlink @windowLockPath, callback
+  path: null
+  pathStatesDir: null
+  pathWindowState: null
+  pathFocusState: null
+  firstWindowAtLaunch: false

   activate: (state) ->
-    @path = path.join atom.getConfigDirPath(), @dataDir
-    @windowLockPath = path.join @path, 'window.lock'
+    window.codewars = @
+    @state = state
+    @path = path.join atom.getConfigDirPath(), DATA_DIR
+    @pathStatesDir = path.join @path, '.states'
+    @pathWindowState = path.join @pathStatesDir, 'window'
+    @pathFocusState = path.join @pathStatesDir, 'focus'

-    mkdirp @path, (err) =>
+    if state.isCodewarsWindow
+      @_clearWindowState ->
+        atom.close()
+      return
+
+    if atom.getCurrentWindow().id is 1
+      @firstWindowAtLaunch = true
+      fs.access @pathWindowState, (err) =>
+        if err then @firstWindowAtLaunch = false
+
+    checkIfCodewarsWindow = (textEditorFile) =>
+      if (path.basename textEditorFile) is OPEN_FILE_KEY
+        @_writeWindowState()
+        # Actually create the view now
+        @createView state
+
+    mkdirp @pathStatesDir, (err) =>
       throw err if err
-      @checkWindowLock (activate) =>
-        return unless activate
-        @createView state
+      # Check if the window has the codewars initial file open
+      textEditorFile = atom.workspace.getActiveTextEditor()?.buffer.file?.path
+      if textEditorFile
+        checkIfCodewarsWindow textEditorFile
+      else
+        @_textEditorObserver =
+          atom.workspace.observeTextEditors (editor) =>
+            checkIfCodewarsWindow editor?.buffer.file?.path
+            @_textEditorObserver?.dispose()

     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
     @subscriptions = new CompositeDisposable
@@ -51,34 +61,56 @@
     @subscriptions.add atom.commands.add 'atom-workspace', 'codewars:toggle': => @toggle()

   createView: (state) ->
-    window.codewars = @
     @codewarsView = new CodewarsView @path, state.codewarsViewState
     @codewarsView.show()
+    @_watchStateFiles()

   deactivate: ->
-    @clearWindowLock()
+    @_pathStatesWatcher?.close()
+    @_clearWindowState()
     @subscriptions?.dispose()
     @codewarsView?.destroy()

   serialize: ->
-    codewarsViewState: @codewarsView.serialize()
+    isCodewarsWindow: !!@codewarsView
+    codewarsViewState: @codewarsView?.serialize()

   toggle: ->
-    if @windowWasOpened
-      @checkWindowLock (activate) =>
-        if @openNewWindow
-          @windowWasOpened = false
-          @toggle()
-        else atom.notifications.addInfo "Codewars is already open in another window."
-      return
+    @_checkWindowState (openNewWindow) =>
+      return unless openNewWindow
+      atom.open pathsToOpen: [path.join(@path, OPEN_FILE_KEY)], newWindow: true

-    if @openNewWindow
-      atom.open pathsToOpen: [path.join(@path, 'codewars')], newWindow: true
-      @windowWasOpened = true
-      @openNewWindow = false
-      return
+    if @codewarsView
+      if @codewarsView.isVisible() then @codewarsView.hide()
+      else @codewarsView.show()
+    else
+      @_touchFocusState()

-    if @codewarsView.isVisible()
-      @codewarsView.hide()
-    else
-      @codewarsView.show()
+  # == Private functions == #
+
+  _writeWindowState: (cb) ->
+    fs.writeFile @pathWindowState, atom.getCurrentWindow().id, cb
+
+  _checkWindowState: (callback) ->
+    fs.readFile @pathWindowState, (err, data) =>
+      if err or @firstWindowAtLaunch or not data?.toString()?.length
+        @firstWindowAtLaunch = false
+        @_clearWindowState -> callback true
+      else callback false
+
+  _clearWindowState: (callback) ->
+    fs.unlink @pathWindowState, callback
+
+  _touchFocusState: ->
+    fs.writeFile @pathFocusState
+
+  _watchStateFiles: ->
+    @_pathStatesWatcher = fs.watch @pathStatesDir
+    @_pathStatesWatcher.on 'change', (event, filename) =>
+      if filename is 'window'
+        fs.readFile @pathWindowState, (err, data) =>
+          if err or (parseInt data.toString()) isnt atom.getCurrentWindow().id
+            @_writeWindowState()
+      if filename is 'focus'
+        if not @codewarsView.isVisible() then @codewarsView.show()
+        atom.getCurrentWindow().focus()


== null

== null

--- lib/main.coffee
+++ lib/main.coffee
@@ -66,7 +66,7 @@
     @selectionCount = null

     @statusBarPanel?.destroy()
-    @statusBarPanel
+    @statusBarPanel = null

     @statusBar?.destroy()
     @statusBar = null


Ordering bug

Ordering bug

--- SingularityUI/app/views/tasks.coffee
+++ SingularityUI/app/views/tasks.coffee
@@ -7,12 +7,9 @@
     templateTasksCleaning: require './templates/tasksCleaning'

     render: (tasksFilter) =>
-        @collection = app.collections.tasksActive
-        template = @templateTasksActive
-        tasks = _.pluck(@collection.sort().models, 'attributes')
-
         if tasksFilter is 'active'
-            tasks = tasks.reverse()
+            @collection = app.collections.tasksActive
+            template = @templateTasksActive

         if tasksFilter is 'scheduled'
             @collection = app.collections.tasksScheduled
@@ -21,6 +18,11 @@
         if tasksFilter is 'cleaning'
             @collection = app.collections.tasksCleaning
             template = @templateTasksCleaning
+
+        tasks = _.pluck(@collection.sort().models, 'attributes')
+
+        if tasksFilter is 'active'
+            tasks = tasks.reverse()

         context =
             tasks: tasks


Remove dead code, simplify Signature class.

--- src/mock.coffee
+++ src/mock.coffee
@@ -2,11 +2,8 @@

   constructor: ->
     @args = []
+    @returns = undefined
     @called = false
-    @returns = undefined
-
-  toString: ->
-    @args ? ""

@@ -47,10 +44,7 @@
     for signature in @method_calls[ @last_method_name ]
       if signature.args? and ( signature.args.length == args.length ) and ( signature.args.every ( element, i ) -> element == args[ i ] )
         throw new Error(".expects('#{@last_method_name}').args(#{args}) is a duplicate expectation")
-    #if @method_calls[ @last_method_name ].length == 1 and not @method_calls[ @last_method_name ][ 0 ].args?
-    #  @method_calls[ @last_method_name ] = []
     @last_signature.args = args
-    #@method_calls[ @last_method_name ].push( @last_signature )
     @last_method_was = "args"
     @

@@ -66,7 +60,7 @@
     messages = ""
     for method_name, signatures of @method_calls
       for signature in signatures when signature.called == false
-        messages += "'#{method_name}(#{signature.toString()})' was never called\n"
+        messages += "'#{method_name}(#{signature.args})' was never called\n"
     throw new Error(messages) unless messages == ""
     @last_method_was = "check"
     @


Melhora checagem de permissões

Melhora checagem de permissões

--- Agencia/server/operador.coffee
+++ Agencia/server/operador.coffee
@@ -12,15 +12,20 @@
 		throw e

+permissoes =
+	interno: ['funcionario']
+	deposito: ['cliente']
+	saque: ['cliente']
+	extrato: ['cliente']
+	transferencia: ['cliente']
+
+
 @chamarOperadorAutenticado = (operador, nome, data) ->
 	user = Meteor.user()
 	if not user?
 		throw new Meteor.Error 'Autentiação requerida'

-	if operador is 'interno' and user.role isnt 'funcionario'
-		throw new Meteor.Error 'Usuário sem acesso'
-
-	if operador isnt 'interno' and user.role not in ['funcionario', 'cliente']
+	if user.role not in permissoes[operador]
 		throw new Meteor.Error 'Usuário sem acesso'

 	return chamarOperador operador, nome, data


Get cabins list async

Get cabins list async

--- coffee/controller/cabin.coffee
+++ coffee/controller/cabin.coffee
@@ -6,11 +6,12 @@
 fs = require 'fs'

 exports.getList = (request, reply) ->
-  reply.view 'list',
-    site: site
-    user: request.auth.credentials
-    cabins: cabin.getCabins()
-    title: 'List'
+  cabin.getCabins '52407f3c4ec4a1381500025d', (err, data) ->
+    reply.view 'list',
+      site: site
+      user: request.auth.credentials
+      cabins: data
+      title: 'List'

 exports.getCabin = (request, reply) ->
   cabin.getCabin request.params.id, (err, data) ->
@@ -24,10 +25,10 @@
 exports.fetchImage = (id, reply) ->
   cabin.getCabin id, (err, data) ->
     # @TODO(starefossen) code is undefined
-    return reply().code(404) if err or not data?.bilder?[0]
+    return reply() if err or not data?.bilder?[0]
     cabin.getImage data.bilder[0], (err, data) ->
       # @TODO(starefossen) code is undefined
-      return reply().code(404) if err or not data?.img?[1]
+      return reply() if err or not data?.img?[1]
       stream = request data.img[1].url
       stream.once 'response', reply
       stream.pipe fs.createWriteStream('static/images/cabin/' + id)


Add default value of null for heat map and statistics layers

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -90,6 +90,8 @@
             bicycle:
                 bicycle_parked: true
                 bicycle_with: false
+        heatmap_layer: null
+        statistics_layer: null

     migrateCityFromV1ToV2 = (source) ->
         city = source.city


should not use bind for map's callback.

should not use bind for map's callback.

map pass item index as 2nd arg, so bind is not appropriate for the
function which take 2nd arg.

--- src/pane.coffee
+++ src/pane.coffee
@@ -637,7 +637,7 @@
   # Public: Destroy all items.
   destroyItems: ->
     Promise.all(
-      @getItems().map(@destroyItem.bind(this))
+      @getItems().map((item) => @destroyItem(item))
     )

   # Public: Destroy all items except for the active item.
@@ -645,7 +645,7 @@
     Promise.all(
       @getItems()
         .filter((item) => item isnt @activeItem)
-        .map(@destroyItem.bind(this))
+        .map((item) => @destroyItem(item))
     )

   promptToSaveItem: (item, options={}) ->
@@ -950,7 +950,7 @@
   # Returns a {Promise} that resolves once the pane is either closed, or the
   # closing has been cancelled.
   close: ->
-    Promise.all(@getItems().map(@promptToSaveItem.bind(this))).then (results) =>
+    Promise.all(@getItems().map((item) => @promptToSaveItem(item))).then (results) =>
       @destroy() unless results.includes(false)

   handleSaveError: (error, item) ->
@@ -989,4 +989,4 @@
   try
     Promise.resolve(callback())
   catch error
-    Promise.reject(error)+    Promise.reject(error)


Adjust max width for image set preview

Adjust max width for image set preview

--- components/article/view.coffee
+++ components/article/view.coffee
@@ -193,7 +193,7 @@
   setupImageSets: ->
     # Slideshow Preview
     $('.article-section-container[data-section-type="image_set"]').each (i, value) ->
-      allowedPixels = 580.0 - 100 # min-width + margins
+      allowedPixels = 580.0 - 120 # min-width + margins
       totalPixels = 0.0
       $(value).find('img').each (i, value) ->
         _.defer ->


hub: improved logging and caching

hub: improved logging and caching

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -3351,6 +3351,7 @@
         #  - if no customer info yet with stripe, then NOT an error; instead,
         #    customer_id is undefined.
         dbg = @dbg("stripe_get_customer_id")
+        dbg()
         if not @account_id?
             err = "You must be signed in to use billing related functions."
             @error_to_client(id:id, error:err)
@@ -3366,18 +3367,25 @@
                 dbg("using cached @stripe_customer_id")
                 cb(undefined, @stripe_customer_id)
             else
+                if @_stripe_customer_id_cbs?
+                    @_stripe_customer_id_cbs.push({id:id, cb:cb})
+                    return
+                @_stripe_customer_id_cbs = [{id:id, cb:cb}]
                 dbg('getting stripe_customer_id from db...')
                 database.get_stripe_customer_id
                     account_id : @account_id
                     cb         : (err, customer_id) =>
-                        if err
-                            dbg("fail -- #{err}")
-                            @error_to_client(id:id, error:err)
-                            cb(err)
-                        else
-                            dbg("got result #{customer_id}")
-                            @stripe_customer_id = customer_id  # cache for later
-                            cb(undefined, customer_id)
+                        @stripe_customer_id = customer_id  # cache for later
+                        for x in @_stripe_customer_id_cbs
+                            {id, cb} = x
+                            if err
+                                dbg("fail -- #{err}")
+                                @error_to_client(id:id, error:err)
+                                cb(err)
+                            else
+                                dbg("got result #{customer_id}")
+                                cb(undefined, customer_id)
+                        delete @_stripe_customer_id_cbs

     # like stripe_get_customer_id, except sends an error to the
     # user if they aren't registered yet, instead of returning undefined.


Trying synchronous spritesheet building during level loading for speed.

--- app/lib/LevelLoader.coffee
+++ app/lib/LevelLoader.coffee
@@ -104,7 +104,7 @@

   onSupermodelLoadedOne: (e) ->
     @buildSpriteSheetsForThangType e.model if not @headless and e.model instanceof ThangType
-    @update()
+    @update() unless @destroyed

   # Things to do when either the Session or Supermodel load

@@ -160,7 +160,7 @@
   buildSpriteSheetsForThangType: (thangType) ->
     @grabThangTypeTeams() unless @thangTypeTeams
     for team in @thangTypeTeams[thangType.get('original')] ? [null]
-      spriteOptions = {resolutionFactor: 4, async: true}
+      spriteOptions = {resolutionFactor: 4, async: false}
       if thangType.get('kind') is 'Floor'
         spriteOptions.resolutionFactor = 2
       if team and color = @teamConfigs[team]?.color
@@ -174,10 +174,14 @@
     return unless building
     #console.log 'Building:', thangType.get('name'), options
     @spriteSheetsToBuild += 1
-    thangType.once 'build-complete', =>
+    onBuildComplete = =>
       return if @destroyed
       @spriteSheetsBuilt += 1
       @notifyProgress()
+    if options.async
+      thangType.once 'build-complete', onBuildComplete
+    else
+      onBuildComplete()

   # World init


Add TextBuffer::deleteRow

Add TextBuffer::deleteRow

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -196,6 +196,12 @@
   # Returns an empty {Range} starting at the start of deleted range.
   deleteText: (range) ->
     @setTextInRange(range, '')
+
+  # Public: Deletes the line associated with the specified row.
+  #
+  # Returns the {Range} of the deleted text.
+  deleteRow: (row) ->
+    @deleteRows(row, row)

   # Public: Deletes the specified lines associated with the specified row range.
   # If the row range is out of bounds, it will be clipped. If the startRow is


Conditionally include deprecations in TextEditor

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2,7 +2,7 @@
 path = require 'path'
 Serializable = require 'serializable'
 Delegator = require 'delegato'
-{deprecate} = require 'grim'
+{includeDeprecations, deprecate} = require 'grim'
 {Model} = require 'theorist'
 EmitterMixin = require('emissary').Emitter
 {CompositeDisposable, Emitter} = require 'event-kit'
@@ -174,9 +174,6 @@
     subscriptions.add atom.config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, => @updateInvisibles()
     subscriptions.add atom.config.onDidChange 'editor.invisibles', scope: scopeDescriptor, => @updateInvisibles()

-  getViewClass: ->
-    require './text-editor-view'
-
   destroyed: ->
     @unsubscribe()
     @scopedConfigSubscriptions.dispose()
@@ -461,75 +458,11 @@
   onDidChangeIcon: (callback) ->
     @emitter.on 'did-change-icon', callback

-  on: (eventName) ->
-    switch eventName
-      when 'title-changed'
-        deprecate("Use TextEditor::onDidChangeTitle instead")
-      when 'path-changed'
-        deprecate("Use TextEditor::onDidChangePath instead")
-      when 'modified-status-changed'
-        deprecate("Use TextEditor::onDidChangeModified instead")
-      when 'soft-wrap-changed'
-        deprecate("Use TextEditor::onDidChangeSoftWrapped instead")
-      when 'grammar-changed'
-        deprecate("Use TextEditor::onDidChangeGrammar instead")
-      when 'character-widths-changed'
-        deprecate("Use TextEditor::onDidChangeCharacterWidths instead")
-      when 'contents-modified'
-        deprecate("Use TextEditor::onDidStopChanging instead")
-      when 'contents-conflicted'
-        deprecate("Use TextEditor::onDidConflict instead")
-
-      when 'will-insert-text'
-        deprecate("Use TextEditor::onWillInsertText instead")
-      when 'did-insert-text'
-        deprecate("Use TextEditor::onDidInsertText instead")
-
-      when 'cursor-added'
-        deprecate("Use TextEditor::onDidAddCursor instead")
-      when 'cursor-removed'
-        deprecate("Use TextEditor::onDidRemoveCursor instead")
-      when 'cursor-moved'
-        deprecate("Use TextEditor::onDidChangeCursorPosition instead")
-
-      when 'selection-added'
-        deprecate("Use TextEditor::onDidAddSelection instead")
-      when 'selection-removed'
-        deprecate("Use TextEditor::onDidRemoveSelection instead")
-      when 'selection-screen-range-changed'
-        deprecate("Use TextEditor::onDidChangeSelectionRange instead")
-
-      when 'decoration-added'
-        deprecate("Use TextEditor::onDidAddDecoration instead")
-      when 'decoration-removed'
-        deprecate("Use TextEditor::onDidRemoveDecoration instead")
-      when 'decoration-updated'
-        deprecate("Use Decoration::onDidChangeProperties instead. You will get the decoration back from `TextEditor::decorateMarker()`")
-      when 'decoration-changed'
-        deprecate("Use Marker::onDidChange instead. e.g. `editor::decorateMarker(...).getMarker().onDidChange()`")
-
-      when 'screen-lines-changed'
-        deprecate("Use TextEditor::onDidChange instead")
-
-      when 'scroll-top-changed'
-        deprecate("Use TextEditor::onDidChangeScrollTop instead")
-      when 'scroll-left-changed'
-        deprecate("Use TextEditor::onDidChangeScrollLeft instead")
-
-      else
-        deprecate("TextEditor::on is deprecated. Use documented event subscription methods instead.")
-
-    EmitterMixin::on.apply(this, arguments)
-
   # Retrieves the current {TextBuffer}.
   getBuffer: -> @buffer

   # Retrieves the current buffer's URI.
   getURI: -> @buffer.getUri()
-
-  getUri: ->
-    deprecate("Use `::getURI` instead")
-    @getURI()

   # Create an {TextEditor} with its initial state based on this object
   copy: ->
@@ -698,9 +631,6 @@
   #
   # * `bufferRow` A {Number} representing a zero-indexed buffer row.
   lineTextForBufferRow: (bufferRow) -> @buffer.lineForRow(bufferRow)
-  lineForBufferRow: (bufferRow) ->
-    deprecate 'Use TextEditor::lineTextForBufferRow(bufferRow) instead'
-    @lineTextForBufferRow(bufferRow)

   # Essential: Returns a {String} representing the contents of the line at the
   # given screen row.
@@ -714,23 +644,9 @@
   #
   # Returns {TokenizedLine}
   tokenizedLineForScreenRow: (screenRow) -> @displayBuffer.tokenizedLineForScreenRow(screenRow)
-  lineForScreenRow: (screenRow) ->
-    deprecate "TextEditor::tokenizedLineForScreenRow(bufferRow) is the new name. But it's private. Try to use TextEditor::lineTextForScreenRow instead"
-    @tokenizedLineForScreenRow(screenRow)

   # {Delegates to: DisplayBuffer.tokenizedLinesForScreenRows}
   tokenizedLinesForScreenRows: (start, end) -> @displayBuffer.tokenizedLinesForScreenRows(start, end)
-  linesForScreenRows: (start, end) ->
-    deprecate "Use TextEditor::tokenizedLinesForScreenRows instead"
-    @tokenizedLinesForScreenRows(start, end)
-
-  # Returns a {Number} representing the line length for the given
-  # buffer row, exclusive of its line-ending character(s).
-  #
-  # * `row` A {Number} indicating the buffer row.
-  lineLengthForBufferRow: (row) ->
-    deprecate "Use editor.lineTextForBufferRow(row).length instead"
-    @lineTextForBufferRow(row).length

   bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)

@@ -980,11 +896,6 @@
         for [foldStartRow, foldEndRow] in foldedRowRanges
           @createFold(foldStartRow + delta, foldEndRow + delta)
       return
-
-  # Deprecated: Use {::duplicateLines} instead.
-  duplicateLine: ->
-    deprecate("Use TextEditor::duplicateLines() instead")
-    @duplicateLines()

   replaceSelectedText: (options={}, fn) ->
     {selectWordIfEmpty} = options
@@ -1115,16 +1026,6 @@
   # Extended: Delete all lines intersecting selections.
   deleteLine: ->
     @mutateSelectedText (selection) -> selection.deleteLine()
-
-  # Deprecated: Use {::deleteToBeginningOfWord} instead.
-  backspaceToBeginningOfWord: ->
-    deprecate("Use TextEditor::deleteToBeginningOfWord() instead")
-    @deleteToBeginningOfWord()
-
-  # Deprecated: Use {::deleteToBeginningOfLine} instead.
-  backspaceToBeginningOfLine: ->
-    deprecate("Use TextEditor::deleteToBeginningOfLine() instead")
-    @deleteToBeginningOfLine()

   ###
   Section: History
@@ -1351,7 +1252,7 @@
   #
   # Returns a {Decoration} object
   decorateMarker: (marker, decorationParams) ->
-    if decorationParams.type is 'gutter'
+    if includeDeprecations and decorationParams.type is 'gutter'
       deprecate("Decorations of `type: 'gutter'` have been renamed to `type: 'line-number'`.")
       decorationParams.type = 'line-number'
     @displayBuffer.decorateMarker(marker, decorationParams)
@@ -1386,11 +1287,6 @@
   # Returns an {Array} of {Decoration}s.
   getLineDecorations: (propertyFilter) ->
     @displayBuffer.getLineDecorations(propertyFilter)
-
-  # Soft-deprecated (forgot to deprecated this pre 1.0)
-  getGutterDecorations: (propertyFilter) ->
-    deprecate("Use ::getLineNumberDecorations instead")
-    @getLineNumberDecorations(propertyFilter)

   # Extended: Get all decorations of type 'line-number'.
   #
@@ -1585,13 +1481,6 @@
   getCursorScreenPositions: ->
     cursor.getScreenPosition() for cursor in @getCursors()

-  # Get the row of the most recently added cursor in screen coordinates.
-  #
-  # Returns the screen row {Number}.
-  getCursorScreenRow: ->
-    deprecate('Use `editor.getCursorScreenPosition().row` instead')
-    @getCursorScreenPosition().row
-
   # Essential: Move the cursor to the given position in screen coordinates.
   #
   # If there are multiple cursors, they will be consolidated to a single cursor.
@@ -1632,85 +1521,52 @@
   # * `lineCount` (optional) {Number} number of lines to move
   moveUp: (lineCount) ->
     @moveCursors (cursor) -> cursor.moveUp(lineCount, moveToEndOfSelection: true)
-  moveCursorUp: (lineCount) ->
-    deprecate("Use TextEditor::moveUp() instead")
-    @moveUp(lineCount)

   # Essential: Move every cursor down one row in screen coordinates.
   #
   # * `lineCount` (optional) {Number} number of lines to move
   moveDown: (lineCount) ->
     @moveCursors (cursor) -> cursor.moveDown(lineCount, moveToEndOfSelection: true)
-  moveCursorDown: (lineCount) ->
-    deprecate("Use TextEditor::moveDown() instead")
-    @moveDown(lineCount)

   # Essential: Move every cursor left one column.
   #
   # * `columnCount` (optional) {Number} number of columns to move (default: 1)
   moveLeft: (columnCount) ->
     @moveCursors (cursor) -> cursor.moveLeft(columnCount, moveToEndOfSelection: true)
-  moveCursorLeft: ->
-    deprecate("Use TextEditor::moveLeft() instead")
-    @moveLeft()

   # Essential: Move every cursor right one column.
   #
   # * `columnCount` (optional) {Number} number of columns to move (default: 1)
   moveRight: (columnCount) ->
     @moveCursors (cursor) -> cursor.moveRight(columnCount, moveToEndOfSelection: true)
-  moveCursorRight: ->
-    deprecate("Use TextEditor::moveRight() instead")
-    @moveRight()

   # Essential: Move every cursor to the beginning of its line in buffer coordinates.
   moveToBeginningOfLine: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfLine()
-  moveCursorToBeginningOfLine: ->
-    deprecate("Use TextEditor::moveToBeginningOfLine() instead")
-    @moveToBeginningOfLine()

   # Essential: Move every cursor to the beginning of its line in screen coordinates.
   moveToBeginningOfScreenLine: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfScreenLine()
-  moveCursorToBeginningOfScreenLine: ->
-    deprecate("Use TextEditor::moveToBeginningOfScreenLine() instead")
-    @moveToBeginningOfScreenLine()

   # Essential: Move every cursor to the first non-whitespace character of its line.
   moveToFirstCharacterOfLine: ->
     @moveCursors (cursor) -> cursor.moveToFirstCharacterOfLine()
-  moveCursorToFirstCharacterOfLine: ->
-    deprecate("Use TextEditor::moveToFirstCharacterOfLine() instead")
-    @moveToFirstCharacterOfLine()

   # Essential: Move every cursor to the end of its line in buffer coordinates.
   moveToEndOfLine: ->
     @moveCursors (cursor) -> cursor.moveToEndOfLine()
-  moveCursorToEndOfLine: ->
-    deprecate("Use TextEditor::moveToEndOfLine() instead")
-    @moveToEndOfLine()

   # Essential: Move every cursor to the end of its line in screen coordinates.
   moveToEndOfScreenLine: ->
     @moveCursors (cursor) -> cursor.moveToEndOfScreenLine()
-  moveCursorToEndOfScreenLine: ->
-    deprecate("Use TextEditor::moveToEndOfScreenLine() instead")
-    @moveToEndOfScreenLine()

   # Essential: Move every cursor to the beginning of its surrounding word.
   moveToBeginningOfWord: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfWord()
-  moveCursorToBeginningOfWord: ->
-    deprecate("Use TextEditor::moveToBeginningOfWord() instead")
-    @moveToBeginningOfWord()

   # Essential: Move every cursor to the end of its surrounding word.
   moveToEndOfWord: ->
     @moveCursors (cursor) -> cursor.moveToEndOfWord()
-  moveCursorToEndOfWord: ->
-    deprecate("Use TextEditor::moveToEndOfWord() instead")
-    @moveToEndOfWord()

   # Cursor Extended

@@ -1719,62 +1575,36 @@
   # If there are multiple cursors, they will be merged into a single cursor.
   moveToTop: ->
     @moveCursors (cursor) -> cursor.moveToTop()
-  moveCursorToTop: ->
-    deprecate("Use TextEditor::moveToTop() instead")
-    @moveToTop()

   # Extended: Move every cursor to the bottom of the buffer.
   #
   # If there are multiple cursors, they will be merged into a single cursor.
   moveToBottom: ->
     @moveCursors (cursor) -> cursor.moveToBottom()
-  moveCursorToBottom: ->
-    deprecate("Use TextEditor::moveToBottom() instead")
-    @moveToBottom()

   # Extended: Move every cursor to the beginning of the next word.
   moveToBeginningOfNextWord: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfNextWord()
-  moveCursorToBeginningOfNextWord: ->
-    deprecate("Use TextEditor::moveToBeginningOfNextWord() instead")
-    @moveToBeginningOfNextWord()

   # Extended: Move every cursor to the previous word boundary.
   moveToPreviousWordBoundary: ->
     @moveCursors (cursor) -> cursor.moveToPreviousWordBoundary()
-  moveCursorToPreviousWordBoundary: ->
-    deprecate("Use TextEditor::moveToPreviousWordBoundary() instead")
-    @moveToPreviousWordBoundary()

   # Extended: Move every cursor to the next word boundary.
   moveToNextWordBoundary: ->
     @moveCursors (cursor) -> cursor.moveToNextWordBoundary()
-  moveCursorToNextWordBoundary: ->
-    deprecate("Use TextEditor::moveToNextWordBoundary() instead")
-    @moveToNextWordBoundary()

   # Extended: Move every cursor to the beginning of the next paragraph.
   moveToBeginningOfNextParagraph: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfNextParagraph()
-  moveCursorToBeginningOfNextParagraph: ->
-    deprecate("Use TextEditor::moveToBeginningOfNextParagraph() instead")
-    @moveToBeginningOfNextParagraph()

   # Extended: Move every cursor to the beginning of the previous paragraph.
   moveToBeginningOfPreviousParagraph: ->
     @moveCursors (cursor) -> cursor.moveToBeginningOfPreviousParagraph()
-  moveCursorToBeginningOfPreviousParagraph: ->
-    deprecate("Use TextEditor::moveToBeginningOfPreviousParagraph() instead")
-    @moveToBeginningOfPreviousParagraph()

   # Extended: Returns the most recently added {Cursor}
   getLastCursor: ->
     _.last(@cursors)
-
-  # Deprecated:
-  getCursor: ->
-    deprecate("Use TextEditor::getLastCursor() instead")
-    @getLastCursor()

   # Extended: Returns the word surrounding the most recently added cursor.
   #
@@ -2087,16 +1917,10 @@
   # This method merges selections on successive lines.
   selectLinesContainingCursors: ->
     @expandSelectionsForward (selection) -> selection.selectLine()
-  selectLine: ->
-    deprecate('Use TextEditor::selectLinesContainingCursors instead')
-    @selectLinesContainingCursors()

   # Essential: Select the word surrounding each cursor.
   selectWordsContainingCursors: ->
     @expandSelectionsForward (selection) -> selection.selectWord()
-  selectWord: ->
-    deprecate('Use TextEditor::selectWordsContainingCursors instead')
-    @selectWordsContainingCursors()

   # Selection Extended

@@ -2151,15 +1975,6 @@
   # Returns a {Selection}.
   getLastSelection: ->
     _.last(@selections)
-
-  # Deprecated:
-  getSelection: (index) ->
-    if index?
-      deprecate("Use TextEditor::getSelections()[index] instead when getting a specific selection")
-      @getSelections()[index]
-    else
-      deprecate("Use TextEditor::getLastSelection() instead")
-      @getLastSelection()

   # Extended: Get current {Selection}s.
   #
@@ -2428,9 +2243,6 @@
   #
   # Returns a {Boolean}.
   isSoftWrapped: (softWrapped) -> @displayBuffer.isSoftWrapped()
-  getSoftWrapped: ->
-    deprecate("Use TextEditor::isSoftWrapped instead")
-    @displayBuffer.isSoftWrapped()

   # Essential: Enable or disable soft wrapping for this editor.
   #
@@ -2438,17 +2250,11 @@
   #
   # Returns a {Boolean}.
   setSoftWrapped: (softWrapped) -> @displayBuffer.setSoftWrapped(softWrapped)
-  setSoftWrap: (softWrapped) ->
-    deprecate("Use TextEditor::setSoftWrapped instead")
-    @setSoftWrapped(softWrapped)

   # Essential: Toggle soft wrapping for this editor
   #
   # Returns a {Boolean}.
   toggleSoftWrapped: -> @setSoftWrapped(not @isSoftWrapped())
-  toggleSoftWrap: ->
-    deprecate("Use TextEditor::toggleSoftWrapped instead")
-    @toggleSoftWrapped()

   # Public: Gets the column at which column will soft wrap
   getSoftWrapColumn: -> @displayBuffer.getSoftWrapColumn()
@@ -2573,9 +2379,6 @@
   # Returns a {ScopeDescriptor}.
   scopeDescriptorForBufferPosition: (bufferPosition) ->
     @displayBuffer.scopeDescriptorForBufferPosition(bufferPosition)
-  scopesForBufferPosition: (bufferPosition) ->
-    deprecate 'Use ::scopeDescriptorForBufferPosition instead. The return value has changed! It now returns a `ScopeDescriptor`'
-    @scopeDescriptorForBufferPosition(bufferPosition).getScopesArray()

   # Extended: Get the range in buffer coordinates of all tokens surrounding the
   # cursor that match the given scope selector.
@@ -2606,13 +2409,6 @@

   # {Delegates to: DisplayBuffer.tokenForBufferPosition}
   tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
-
-  scopesAtCursor: ->
-    deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
-    @getLastCursor().getScopeDescriptor().getScopesArray()
-  getCursorScopes: ->
-    deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
-    @scopesAtCursor()

   ###
   Section: Clipboard Operations
@@ -3030,16 +2826,235 @@

   pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)

-  # Deprecated: Call {::joinLines} instead.
-  joinLine: ->
+  ###
+  Section: Utility
+  ###
+
+  inspect: ->
+    "<TextEditor #{@id}>"
+
+  logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
+
+if includeDeprecations
+  TextEditor::getViewClass = ->
+    require './text-editor-view'
+
+  TextEditor::joinLine = ->
     deprecate("Use TextEditor::joinLines() instead")
     @joinLines()

-  ###
-  Section: Utility
-  ###
-
-  inspect: ->
-    "<TextEditor #{@id}>"
-
-  logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
+  TextEditor::scopesAtCursor = ->
+    deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
+    @getLastCursor().getScopeDescriptor().getScopesArray()
+
+  TextEditor::getCursorScopes = ->
+    deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
+    @scopesAtCursor()
+
+  TextEditor::getUri = ->
+    deprecate("Use `::getURI` instead")
+    @getURI()
+
+  TextEditor::lineForBufferRow = (bufferRow) ->
+    deprecate 'Use TextEditor::lineTextForBufferRow(bufferRow) instead'
+    @lineTextForBufferRow(bufferRow)
+
+  TextEditor::lineForScreenRow = (screenRow) ->
+    deprecate "TextEditor::tokenizedLineForScreenRow(bufferRow) is the new name. But it's private. Try to use TextEditor::lineTextForScreenRow instead"
+    @tokenizedLineForScreenRow(screenRow)
+
+  TextEditor::linesForScreenRows = (start, end) ->
+    deprecate "Use TextEditor::tokenizedLinesForScreenRows instead"
+    @tokenizedLinesForScreenRows(start, end)
+
+  TextEditor::lineLengthForBufferRow = (row) ->
+    deprecate "Use editor.lineTextForBufferRow(row).length instead"
+    @lineTextForBufferRow(row).length
+
+  TextEditor::duplicateLine = ->
+    deprecate("Use TextEditor::duplicateLines() instead")
+    @duplicateLines()
+
+  TextEditor::scopesForBufferPosition = (bufferPosition) ->
+    deprecate 'Use ::scopeDescriptorForBufferPosition instead. The return value has changed! It now returns a `ScopeDescriptor`'
+    @scopeDescriptorForBufferPosition(bufferPosition).getScopesArray()
+
+  TextEditor::toggleSoftWrap = ->
+    deprecate("Use TextEditor::toggleSoftWrapped instead")
+    @toggleSoftWrapped()
+
+  TextEditor::setSoftWrap = (softWrapped) ->
+    deprecate("Use TextEditor::setSoftWrapped instead")
+    @setSoftWrapped(softWrapped)
+
+  TextEditor::backspaceToBeginningOfWord = ->
+    deprecate("Use TextEditor::deleteToBeginningOfWord() instead")
+    @deleteToBeginningOfWord()
+
+  TextEditor::backspaceToBeginningOfLine = ->
+    deprecate("Use TextEditor::deleteToBeginningOfLine() instead")
+    @deleteToBeginningOfLine()
+
+  TextEditor::getGutterDecorations = (propertyFilter) ->
+    deprecate("Use ::getLineNumberDecorations instead")
+    @getLineNumberDecorations(propertyFilter)
+
+  TextEditor::getCursorScreenRow = ->
+    deprecate('Use `editor.getCursorScreenPosition().row` instead')
+    @getCursorScreenPosition().row
+
+  TextEditor::moveCursorUp = (lineCount) ->
+    deprecate("Use TextEditor::moveUp() instead")
+    @moveUp(lineCount)
+
+  TextEditor::moveCursorDown = (lineCount) ->
+    deprecate("Use TextEditor::moveDown() instead")
+    @moveDown(lineCount)
+
+  TextEditor::moveCursorLeft = ->
+    deprecate("Use TextEditor::moveLeft() instead")
+    @moveLeft()
+
+  TextEditor::moveCursorRight = ->
+    deprecate("Use TextEditor::moveRight() instead")
+    @moveRight()
+
+  TextEditor::moveCursorToBeginningOfLine = ->
+    deprecate("Use TextEditor::moveToBeginningOfLine() instead")
+    @moveToBeginningOfLine()
+
+  TextEditor::moveCursorToBeginningOfScreenLine = ->
+    deprecate("Use TextEditor::moveToBeginningOfScreenLine() instead")
+    @moveToBeginningOfScreenLine()
+
+  TextEditor::moveCursorToFirstCharacterOfLine = ->
+    deprecate("Use TextEditor::moveToFirstCharacterOfLine() instead")
+    @moveToFirstCharacterOfLine()
+
+  TextEditor::moveCursorToEndOfLine = ->
+    deprecate("Use TextEditor::moveToEndOfLine() instead")
+    @moveToEndOfLine()
+
+  TextEditor::moveCursorToEndOfScreenLine = ->
+    deprecate("Use TextEditor::moveToEndOfScreenLine() instead")
+    @moveToEndOfScreenLine()
+
+  TextEditor::moveCursorToBeginningOfWord = ->
+    deprecate("Use TextEditor::moveToBeginningOfWord() instead")
+    @moveToBeginningOfWord()
+
+  TextEditor::moveCursorToEndOfWord = ->
+    deprecate("Use TextEditor::moveToEndOfWord() instead")
+    @moveToEndOfWord()
+
+  TextEditor::moveCursorToTop = ->
+    deprecate("Use TextEditor::moveToTop() instead")
+    @moveToTop()
+
+  TextEditor::moveCursorToBottom = ->
+    deprecate("Use TextEditor::moveToBottom() instead")
+    @moveToBottom()
+
+  TextEditor::moveCursorToBeginningOfNextWord = ->
+    deprecate("Use TextEditor::moveToBeginningOfNextWord() instead")
+    @moveToBeginningOfNextWord()
+
+  TextEditor::moveCursorToPreviousWordBoundary = ->
+    deprecate("Use TextEditor::moveToPreviousWordBoundary() instead")
+    @moveToPreviousWordBoundary()
+
+  TextEditor::moveCursorToNextWordBoundary = ->
+    deprecate("Use TextEditor::moveToNextWordBoundary() instead")
+    @moveToNextWordBoundary()
+
+  TextEditor::moveCursorToBeginningOfNextParagraph = ->
+    deprecate("Use TextEditor::moveToBeginningOfNextParagraph() instead")
+    @moveToBeginningOfNextParagraph()
+
+  TextEditor::moveCursorToBeginningOfPreviousParagraph = ->
+    deprecate("Use TextEditor::moveToBeginningOfPreviousParagraph() instead")
+    @moveToBeginningOfPreviousParagraph()
+
+  TextEditor::getCursor = ->
+    deprecate("Use TextEditor::getLastCursor() instead")
+    @getLastCursor()
+
+  TextEditor::selectLine = ->
+    deprecate('Use TextEditor::selectLinesContainingCursors instead')
+    @selectLinesContainingCursors()
+
+  TextEditor::selectWord = ->
+    deprecate('Use TextEditor::selectWordsContainingCursors instead')
+    @selectWordsContainingCursors()
+
+  TextEditor::getSelection = (index) ->
+    if index?
+      deprecate("Use TextEditor::getSelections()[index] instead when getting a specific selection")
+      @getSelections()[index]
+    else
+      deprecate("Use TextEditor::getLastSelection() instead")
+      @getLastSelection()
+
+  TextEditor::getSoftWrapped = ->
+    deprecate("Use TextEditor::isSoftWrapped instead")
+    @displayBuffer.isSoftWrapped()
+
+  TextEditor::on = (eventName) ->
+    switch eventName
+      when 'title-changed'
+        deprecate("Use TextEditor::onDidChangeTitle instead")
+      when 'path-changed'
+        deprecate("Use TextEditor::onDidChangePath instead")
+      when 'modified-status-changed'
+        deprecate("Use TextEditor::onDidChangeModified instead")
+      when 'soft-wrap-changed'
+        deprecate("Use TextEditor::onDidChangeSoftWrapped instead")
+      when 'grammar-changed'
+        deprecate("Use TextEditor::onDidChangeGrammar instead")
+      when 'character-widths-changed'
+        deprecate("Use TextEditor::onDidChangeCharacterWidths instead")
+      when 'contents-modified'
+        deprecate("Use TextEditor::onDidStopChanging instead")
+      when 'contents-conflicted'
+        deprecate("Use TextEditor::onDidConflict instead")
+
+      when 'will-insert-text'
+        deprecate("Use TextEditor::onWillInsertText instead")
+      when 'did-insert-text'
+        deprecate("Use TextEditor::onDidInsertText instead")
+
+      when 'cursor-added'
+        deprecate("Use TextEditor::onDidAddCursor instead")
+      when 'cursor-removed'
+        deprecate("Use TextEditor::onDidRemoveCursor instead")
+      when 'cursor-moved'
+        deprecate("Use TextEditor::onDidChangeCursorPosition instead")
+
+      when 'selection-added'
+        deprecate("Use TextEditor::onDidAddSelection instead")
+      when 'selection-removed'
+        deprecate("Use TextEditor::onDidRemoveSelection instead")
+      when 'selection-screen-range-changed'
+        deprecate("Use TextEditor::onDidChangeSelectionRange instead")
+
+      when 'decoration-added'
+        deprecate("Use TextEditor::onDidAddDecoration instead")
+      when 'decoration-removed'
+        deprecate("Use TextEditor::onDidRemoveDecoration instead")
+      when 'decoration-updated'
+        deprecate("Use Decoration::onDidChangeProperties instead. You will get the decoration back from `TextEditor::decorateMarker()`")
+      when 'decoration-changed'
+        deprecate("Use Marker::onDidChange instead. e.g. `editor::decorateMarker(...).getMarker().onDidChange()`")
+
+      when 'screen-lines-changed'
+        deprecate("Use TextEditor::onDidChange instead")
+
+      when 'scroll-top-changed'
+        deprecate("Use TextEditor::onDidChangeScrollTop instead")
+      when 'scroll-left-changed'
+        deprecate("Use TextEditor::onDidChangeScrollLeft instead")
+
+      else
+        deprecate("TextEditor::on is deprecated. Use documented event subscription methods instead.")
+
+    EmitterMixin::on.apply(this, arguments)


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -6,7 +6,7 @@
   self = GameState(I).extend
     drawTitleText: (canvas, titleText, subtitleText) ->
       canvas.fillColor
-      canvas.font
+      canvas.font I.font

       canvas.centerText
         y: App.height / 2 - 20


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -4,7 +4,7 @@
     fontColor: 'white'

   self = GameState(I).extend
-    centerText: (canvas, titleText, subtitleText) ->
+    centerText: (canvas, te, subtitleText) ->
       canvas.font I.font

       canvas.centerText


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -7,7 +7,7 @@
   self = GameState(I).extend
     centerText: (canvas, options={}) ->
       font =
-      canvas.font "#{size}px "
+      canvas.font "#{size}px #{}"

       canvas.centerText
         y: App.height / 2 - 20


Refactored Force

Refactored Force

--- js/particle.coffee
+++ js/particle.coffee
@@ -31,8 +31,10 @@
           gForce = @GRAVITATIONAL_CONSTANT * particle.mass / delta.squareMagnitude()
           x = gForce * Math.cos(delta.angle())
           y = gForce * Math.sin(delta.angle())
-          @force = @force.add new Vector(x, y)
+          @applyForce new Vector(x, y)

+    applyForce: (force) ->
+      @force = @force.add force

     render: (context, scale) ->
       context.fillStyle = '#FFFFFF'


CustomScroll: refactoring regards to #15ea1fb871bd0aef7009c1c0660646e57ed55c6d

--- src/components/scrollview/customscrollviewinner.coffee
+++ src/components/scrollview/customscrollviewinner.coffee
@@ -6,23 +6,21 @@

 module.exports = class KDCustomScrollViewWrapper extends KDScrollView

-  scroll:(event)->
+  scroll: (event) ->

     if @verticalThumb.beingDragged or @horizontalThumb.beingDragged
       return KD.utils.stopDOMEvent event

-  mouseWheel:(event)->
+  mouseWheel: (event) ->

     super

-    {_delta, deltaFactor} = event
+    {deltaX, deltaY, deltaFactor} = event.originalEvent

-    return  unless _delta
-
-    speed = deltaFactor or @getOptions().mouseWheelSpeed
-    x     = _delta.deltaX
-    y     = _delta.deltaY
+    speed = deltaFactor or @getOptions().mouseWheelSpeed or 1
+    x     = deltaX
+    y     = deltaY

     resX  = if x isnt 0 and @getScrollWidth() > @getWidth()
     then  @_scrollHorizontally {speed, velocity : x}
@@ -44,7 +42,7 @@

       stepInPixels = velocity * speed
       actPosition  = @getScrollTop()
-      newPosition  = actPosition - stepInPixels
+      newPosition  = actPosition + stepInPixels
       shouldStop   = if velocity > 0
       then lastPosition > newPosition
       else lastPosition < newPosition


recurly: add missing fields, DRY

recurly: add missing fields, DRY

--- node_modules_koding/koding-payment/lib/main.coffee
+++ node_modules_koding/koding-payment/lib/main.coffee
@@ -490,6 +490,9 @@
     state             : ifString b.state[0]
     zip               : b.zip[0]
     country           : b.country[0]
+    company           : b.company[0]
+    vatNumber         : b.vat_number[0]
+    phone             : b.phone[0]

   @getBilling: (accountCode, callback)->
     get "accounts/#{accountCode}/billing_info", (err, res)->
@@ -500,31 +503,37 @@
         then createBillingObject billingInfo
         else {}

+  makeTag = (data, field, tagName = field) ->
+    if data[field] then "<#{tagName}>#{data[field]}</#{tagName}>" else ''
+
   @setBilling: (accountCode, data, callback)->
     # Remove credit card number if it's not updated
     if data.cardNumber?.indexOf('*') > -1 or data.cardCV?.cardCV is '***'
       delete data.cardNumber
       delete data.cardCV

+    tag = makeTag.bind null, data
+
     # See https://docs.recurly.com/api/account for XML format
     xml =
       """
       <?xml version="1.0" encoding="UTF-8"?>
       <billing_info>
-        <number>#{data.cardNumber}</number>
-        <verification_value>#{data.cardCV}</verification_value>
-        <month>#{data.cardMonth}</month>
-        <year>#{data.cardYear}</year>
-
-        <first_name>#{data.cardFirstName}</first_name>
-        <last_name>#{data.cardLastName}</last_name>
-        <address1>#{data.address}</address1>
-        #{if data.city then "<city>#{data.city}</city>" else ''}
-        #{if data.state then "<state>#{data.state}</state>" else ''}
-        <zip>#{data.zip}</zip>
-        <country>#{data.country}</country>
-
-        <ip_address>#{data.ipAddress}</ip_address>
+        #{tag 'cardNumber', 'number'}
+        #{tag 'cardCV', 'verification_value'}
+        #{tag 'cardMonth', 'month'}
+        #{tag 'cardYear', 'year'}
+        #{tag 'company'}
+        #{tag 'cardFirstName', 'first_name'}
+        #{tag 'cardLastName', 'last_name'}
+        #{tag 'address1'}
+        #{tag 'city'}
+        #{tag 'state'}
+        #{tag 'zip'}
+        #{tag 'country'}
+        #{tag 'phone'}
+        #{tag 'vatNumber', 'vat_number'}
+        #{tag 'ipAddress', 'ip_address'}
       </billing_info>
       """


Fix #24

Fix #24

--- lib/util.coffee
+++ lib/util.coffee
@@ -185,12 +185,13 @@

 # shim atom.serialize in <= 1.6
 atomSerialize = () ->
-  return atom.serialize() if atom.serialize?
+  options = {isUnloading: true}
+  return atom.serialize(options) if atom.serialize?

   # Atom <= 1.6
   {
     version: atom.constructor.version
-    project: atom.project.serialize()
+    project: atom.project.serialize(options)
     workspace: atom.workspace.serialize()
     packageStates: packageStatesSerialize()
     grammars: {grammarOverridesByPath: atom.grammars.grammarOverridesByPath}


adding debug

adding debug

--- src/poll.coffee
+++ src/poll.coffee
@@ -88,15 +88,13 @@
 	robot.respond /view results/i, (msg) ->
 		@robot.logger.info "View results"
 		topic = @robot.brain.data.poll.topic
-		@robot.logger.info "#{@robot.brain.data.poll.options}"
 		options = @robot.brain.data.poll.options
-		@robot.logger.info "#{options}"
 		optionString = ""
 		for index, option of options
 			text = option.text
 			numVotes = if option.users == undefined then 0 else option.users.length
 			users = option.users
-			@robot.logger.info "option: #{option} :: text: #{text} :: count: #{numVotes}"
+			@robot.logger.info "option: #{option} :: text: #{text} :: count: #{numVotes} :: users: #{users.join(", ")}"
 			optionString += "\t#{index}: #{text} Total: #{numVotes} Users:(#{users.join(", ")})\n"
 		msg.send """
 		#{topic}


.

Former-commit-id: a77926fbaaf89801715810a6e601b44c1768c74d [formerly 779f6f94f47c8a37360b7fd95ef92f1dc0b3d903] [formerly c6c9ca3b4bd829e6427b24f32131546ded84cf65 [formerly 5761c8e84f7d6eecf8f8c2b9b83448d6045c672d]] [formerly b3f05fd7f840e18be30282151702d631d4cda1d7 [formerly 09e8aaef2647a25ab1e3794e11eef4a7bdf29020] [formerly a34a59c8b1804b9434cc6da402a8ef97d3cedc20 [formerly a34a59c8b1804b9434cc6da402a8ef97d3cedc20 [formerly 21f3149881ae1c0e14775fe852220add9c41ed52]]]]
Former-commit-id: 0134c39b39546d2b52ac0885427d15a697947568 [formerly 867b410513135a620e5978c2a844aa7f8c27898c] [formerly c9974cb72314d97a21821889cc5f84e63f17aa05 [formerly 30fb07c49f8a698b657d2c649102b120db0481b7]]
Former-commit-id: c7f96306ff53177b1fa932eda49fa0b4452c7e01 [formerly 4d601bdbeca479dc1d1531764af3404f62301564]
Former-commit-id: 803d3a382da5b4e6c22d41458400603079970516
Former-commit-id: 193aaab3f5d4c31121cb613eb58c60431943e7d9

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -152,7 +152,7 @@

   cluster_options :
-    'Classroom Discussion':
+    'Classroom Discussions':
       homie_histo_title: "Students' opinions"
     'Civics':
       homie_histo_title: "Citizens' opinions"


carcd demo

carcd demo

Former-commit-id: d3223fdde5db260aedb101bd762c8bf27546ba1a [formerly 6a36decfe45156daabc85df6d6fe76c50ff069c4] [formerly cc132c304ecb49925a7e6207ad648ffd48c79d3e [formerly 705b124f1b1dcd32d740328e2efa3ff1d462400e]] [formerly a2e24cbe875a6d16da4ca026f4392a761a928877 [formerly 06faaccfd24581826694b9229c8c3dbc9dd0964c] [formerly aa3136fcf7d9bca524edcedff3746f94dd50db5c [formerly aa3136fcf7d9bca524edcedff3746f94dd50db5c [formerly 4a5e469d68d6a3c89c3ba8a69dc3f2d2bf1e47a6]]]]
Former-commit-id: 685f206a09d79a540f3ea282ca4ed053b14338be [formerly abb564fc77e92a642e8fed4c460acec40066123f] [formerly eada16efd112d4ebc2937404e33329562ee12b02 [formerly c128a5650dd18ab972760fb7fc64ce8ee36057b9]]
Former-commit-id: 5b3187fdfcbb9b91e5bfab3266599bdd417653ff [formerly 3ca50d9ca6da10fd57e1d373b18a232833e8fd22]
Former-commit-id: 0e24788a894f5e75553a828753229a4e007d6dd2
Former-commit-id: 32c211c56011124bba581c963ba9ad80008a2dda

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -527,7 +527,7 @@
         ProfileMenu()

-customizations['carcd'] =
+customizations['carcd'] = customizations['carcd-demo'] =
   show_proposer_icon: true

   "cluster/Questions" :


Teams: Invitation: Add new variables to email template.

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -247,13 +247,27 @@

   # sendInvitationEmail sends email according to given JInvitation
   @sendInvitationEmail: (client, invitation, callback) ->
-    inviter      = getName client.connection.delegate
+
+    { delegate }  = client.connection
+    { profile }   = delegate
+
+    inviter       = getName delegate
+    groupLink     = "#{protocol}//#{invitation.groupName}.#{hostname}/"
+    inviterLink   = "#{protocol}//#{hostname}/" + profile.nickname
+
+    imgURL   = "//gravatar.com/avatar/#{profile.hash}?size=65&d=https://koding-cdn.s3.amazonaws.com/images/default.avatar.140.png&r=g"
+
+    if profile.avatar
+      imgURL = "/-/image/cache?endpoint=crop&grow=false&width=65&height=65&url=#{encodeURIComponent profile.avatar}"

     properties =
-      groupName: invitation.groupName
-      inviter  : inviter
-      invitee  : inviter # TODO: this field is deprecated, remove after updating template
-      link     : "#{protocol}//#{invitation.groupName}.#{hostname}/Invitation/#{encodeURIComponent invitation.code}"
+      groupName    : invitation.groupName
+      groupLink    : groupLink
+      inviter      : inviter
+      inviterLink  : inviterLink
+      inviterImage : imgURL
+      invitee      : inviter # TODO: this field is deprecated, remove after updating template
+      link         : groupLink + "Invitation/#{encodeURIComponent invitation.code}"

     Tracker.identifyAndTrack invitation.email, { subject : Tracker.types.INVITED_GROUP }, properties

@@ -269,5 +283,3 @@
     name = "#{name} #{lastName}"  if firstName and lastName

     return name
-
-


a remote_name may now be specified as the first argument of a call to sync(); it defaults to 'origin' if not provided - the previous API has been preserved

--- src/repo.coffee
+++ src/repo.coffee
@@ -244,16 +244,17 @@

   # Public: Sync the current branch with the remote.
-  #
-  # callback - Receives `(err)`.
-  #
-  sync: (branch, callback) ->
-    [callback, branch] = [branch, []] if !callback
+  #
+  # callback - Receives `(err)`.
+  #
+  sync: (remote_name, branch, callback) ->
+    [remote_name, callback, branch] = ['origin', branch, remote_name] if !callback
+    [remote_name, callback, branch] = ['origin', remote_name, []]     if !branch
     @git "stash", {}, ["save"], (err) =>
       return callback err if err
-      @git "pull", {}, branch, (err) =>
+      @git "pull", {}, [remote_name, branch], (err) =>
         return callback err if err
-        @git "push", (err) =>
+        @git "push", {}, [remote_name, branch], (err) =>
           return callback err if err
           @git "stash", {}, "pop", (err) =>
             return callback err if err


handle case of newline with line attribute

--- src/line.coffee
+++ src/line.coffee
@@ -49,7 +49,11 @@
     this.wrapText(root)
     this.mergeAdjacent(root)
     this.wrapText(root)
-    root.appendChild(root.ownerDocument.createElement('br')) if root.firstChild == null
+    if root.firstChild == null
+      if root.tagName == 'OL' || root.tagName == 'UL'
+        root.appendChild(root.ownerDocument.createElement('li'))
+        root = root.firstChild
+      root.appendChild(root.ownerDocument.createElement('br'))

   @removeRedundant: (root) ->
     tandemKey = '_tandemAttributes' + Math.floor(Math.random() * 100000000)


remove unused import

remove unused import


Fix JSON-RPC.

Fix JSON-RPC.

--- src/main.coffee
+++ src/main.coffee
@@ -146,19 +146,8 @@
     connection = null

     # Create a JSON-RPC interface for this connection.
-    jsonRpc = $jsonRpc.create(
-      # onReceive
-      (message) ->
-        return unless message.type is 'request'
-
-        return api.exec connection, message
-
-      # onSend
-      (data) ->
-        if socket.readyState is socket.OPEN
-          socket.send JSON.stringify data
-        return
-    )
+    jsonRpc = $jsonRpc.createServer (message) ->
+      return api.exec connection, message if message.type is 'request'

     # Create a XO user connection.
     connection = xo.createUserConnection {
@@ -172,8 +161,14 @@
       $bind connection.close, connection
       return

-    # Handles each request in a separate fiber.
-    socket.on 'message', $bind jsonRpc.exec, jsonRpc
+    # Connect the WebSocket to the JSON-RPC server
+    socket.on 'message', (message) ->
+      jsonRpc.write message
+      return
+    jsonRpc.on 'data', (data) ->
+      if socket.readyState is socket.OPEN
+        socket.send JSON.stringify data
+      return

     socket.on 'error', (error) ->
       console.error '[WARN] WebSocket connection', error


restore wrongfully deleted line

restore wrongfully deleted line

--- src/main.coffee
+++ src/main.coffee
@@ -401,4 +401,5 @@
                 mainWindow.hide()
                 return

+        mainWindow = null
         quit()


AtomApplication creates the initial AtomWindow

--- src/main.coffee
+++ src/main.coffee
@@ -34,10 +34,6 @@
   setupNodePaths()
   atomApplication = new AtomApplication

-  new AtomWindow
-    bootstrapScript: 'window-bootstrap',
-    resourcePath: resourcePath
-
 delegate.browserMainParts.preMainMessageLoopRun = bootstrapApplication

 app = require 'app'
@@ -56,6 +52,7 @@
     @setupJavaScriptArguments()
     @buildApplicationMenu()
     @handleEvents()
+    @createAtomWindow()

   setupJavaScriptArguments: ->
     app.commandLine.appendSwitch 'js-flags', '--harmony_collections'
@@ -107,12 +104,14 @@
       window.removeAllListeners 'close'
       window.close()

-    ipc.on 'open-folder', ->
+    ipc.on 'open-folder', =>
       currentWindow = BrowserWindow.getFocusedWindow()
-      dialog.openFolder currentWindow, {}, (result, paths...) =>
-        new AtomWindow
-          bootstrapScript: 'window-bootstrap',
-          resourcePath: resourcePath
+      dialog.openFolder currentWindow, {}, (result, paths...) => @createAtomWindow()
+
+  createAtomWindow: ->
+    new AtomWindow
+      bootstrapScript: 'window-bootstrap',
+      resourcePath: resourcePath

 class AtomWindow
   @windows = []


Minor history API usage fixup against stack bloat.

--- src/site.coffee
+++ src/site.coffee
@@ -12,7 +12,7 @@
   # Fiddle:

   if history
-    window.history.pushState { fiddle: off }, document.title
+    window.history.replaceState { fiddle: off }, document.title

     fiddle = document.querySelector '#fiddle'
     fiddle.addEventListener 'click', ->