Added svg paths for all maneuver types.

Added svg paths for all maneuver types.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -1330,24 +1330,77 @@
         outTable += """</defs>"""
         outTable += """</svg>"""
         outTable += "

"
+
+        viewWidth = 200
+        viewHeight = 200
+
         for speed in [@maneuvers.length - 1 .. 0]
+
             outTable += "
" + (speed + 1) + "	"
-            for turn in [0 .. @maneuvers[speed].length]
+            for turn in [0 ... @maneuvers[speed].length]
+
                 outTable += """"""
                 if @maneuvers[speed][turn] > 0
-                    iconName = switch turn
-                        when 0 then "turnleft"
-                        when 1 then "bankleft"
-                        when 2 then "straight"
-                        when 3 then "bankright"
-                        when 4 then "turnright"
-                        when 5 then "uturn"
+
+                    # default values are for a straight line
+                    startx = 100
+                    starty = 180
+                    smoothx = 100
+                    smoothy = 100
+                    endx = 100
+                    endy = 80
+                    turnType = "S" # S = smooth, L = line, C = cubic bezier (for k-turn)
+                    extraPoint = "" # extra inflection point for cubic bezier
+
+
+                    switch turn
+                        when 0
+                            # turn left
+                            startx = 160
+                            smoothx = 160
+                            smoothy = 70
+                            endx = 80
+                            endy = 70
+                            turnType = "L"
+                        when 1
+                            # bank left
+                            startx = 150
+                            smoothx = 150
+                            smoothy = 120
+                            endx = 80
+                            endy = 60
+                        # when 2 then # straight - this is the default value, don't need to do anything
+                        when 3
+                            # bank right
+                            startx = 50
+                            smoothx = 50
+                            smoothy = 120
+                            endx = 120
+                            endy = 60
+                        when 4
+                            # turn right
+                            startx = 40
+                            smoothx = 40
+                            smoothy = 70
+                            endx = 120
+                            endy = 70
+                            turnType = "L"
+                        when 5
+                            # k-turn/u-turn
+                            startx = 150
+                            smoothx = 150
+                            smoothy = -10
+                            endx = 60
+                            endy = 120
+                            turnType = "C"
+                            extraPoint = "60,-10 "
+
                     color = switch @maneuvers[speed][turn]
                         when 1 then "white"
                         when 2 then "green"
                         when 3 then "red"
-                    outTable += """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">"""
-                    outTable += """<path marker-end='url(#""" + color + """arrowhead)' stroke-width='15' fill='none' stroke='""" + color + """' d='M150,180 S150,120 80,60' />"""
+                    outTable += """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{viewWidth} #{viewHeight}">"""
+                    outTable += """<path marker-end='url(##{color}arrowhead)' stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />"""
                     outTable += """</svg>"""
                 outTable += """	"""
             outTable += "
"


Update src/coffee/Deft/mvc/Application.coffee

Fixed typo

--- src/coffee/Deft/mvc/Application.coffee
+++ src/coffee/Deft/mvc/Application.coffee
@@ -12,7 +12,7 @@
 	###*
 	* Indicates whether this Application instance has been initialized.
 	###
-	initialized = false
+	initialized: false

 	###*
 	* @param {Object} [config] Configuration object.


Tabview: minor code styling

Tabview: minor code styling

--- src/components/tabs/tabpaneview.coffee
+++ src/components/tabs/tabpaneview.coffee
@@ -13,14 +13,12 @@

     @name = options.name
     @lastScrollTops =
-      window        : 0
       parent        : 0
       self          : 0
-      body          : 0

-    @on "KDTabPaneActive",        @bound "setMainView"
-    @on "KDTabPaneLazyViewAdded", @bound "fireLazyCallback"
+    @on 'KDTabPaneActive',        @bound 'setMainView'
+    @on 'KDTabPaneLazyViewAdded', @bound 'fireLazyCallback'

   show:->


Remove debug code

Remove debug code

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -35,7 +35,6 @@
     }]

     initialize: (options = {}) ->
-      window.x = @
       @set('loaded', false)
       @fetch
         reset: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -47,9 +47,10 @@
       )

     ###*
-    Draw this sprite on the given canvas at the given position.
+    Draw this sprite on the given canvas tiled to the x, y, width, and height
+.

-    @name draw
+    @name fill
     @methodOf Sprite#
     @param {PowerCanvas} canvas Reference to the canvas to draw the sprite on
     @param {Number} x Position on the x axis to draw the sprite


fixes #479

fixes #479

--- src/beautifiers/perltidy.coffee
+++ src/beautifiers/perltidy.coffee
@@ -23,7 +23,7 @@
       '--standard-output'
       '--standard-error-output'
       '--quiet'
-      "--profile=#{options.perltidy_profile}" if options.perltidy_profile?
+      "--profile=#{options.perltidy_profile}" if options.perltidy_profile?.length
       @tempFile("input", text)
       ], help: {
       link: "http://perltidy.sourceforge.net/"


/info and /posts API call done

/info and /posts API call done

--- src/tumblr.coffee
+++ src/tumblr.coffee
@@ -1,4 +1,62 @@
-/**
- * Tumblr API
- */
+##
+# Tumblr API
+##

+xhr = require 'request'
+qs  = require 'querystring'
+
+Tumblr = exports.Tumblr = (host, key) ->
+  @host = host
+  @key  = key
+
+(->
+
+  @info = (fn) ->
+    url = urlFor 'info', @
+
+    request url, fn
+
+  @posts = (options, fn) ->
+    [fn, options] = [options, null] if typeof options is 'function'
+
+    url = urlFor 'posts', @, options
+
+    request url, fn
+
+  urlFor = (action, self, options = null) ->
+    url = [
+      'http://api.tumblr.com/v2/blog/'         # Tumblr API URL
+      self.host + '/' + action                 # blog host and action
+      '/' + options.type if options?.type?     # optional type of post to return
+      '?api_key=' + self.key                   # API key
+    ]
+
+    delete options.type if options?.type?
+
+    params = qs.stringify options
+    url.push '&' + params if params isnt ''    # optional params
+
+    url.join ''
+
+  request = (url, fn = ->) ->
+    xhr
+      method: 'GET'
+      uri:    url
+    ,
+    (error, request, body) ->
+      body = JSON.parse body
+      throw new Error body.meta.msg if body.meta.status isnt 200
+      fn.call body, body.response
+
+  @
+
+).call(Tumblr.prototype)
+
+
+##
+# Example
+##
+tumblr = new Tumblr 'simonenko.tumblr.com', 'key'
+
+tumblr.posts limit: 2, (response) ->
+  console.log response


Delay drag start.

Delay drag start.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -551,6 +551,7 @@
       containment: '.feeds'
       helper: 'clone'
       appendTo: '[data-behavior~=feeds_target]'
+      delay: 300
       start: (event, ui) ->
         $('.feeds').addClass('dragging')
         feedbin.dragOwner = $(@).parents('[data-behavior~=droppable]').first()


Temporal fix to file upload files

Temporal fix to file upload files

--- app/assets/javascripts/forms.js.coffee
+++ app/assets/javascripts/forms.js.coffee
@@ -28,13 +28,15 @@
     i = 0
     while i < element.length
       element[i].addEventListener 'change', ->
-        $(element).parent().find('.file-name').text(@files[i].name)
+        console.log @files[0].name
+        $(element).parent().find('.file-name').text(@files[0].name)
         return
       i++

   initialize: ->
+    console.log 'initialize forms'
     App.Forms.disableEnter()
     App.Forms.submitOnChange()
     App.Forms.toggleLink()
-    App.Forms.uploadButton()
+    # App.Forms.uploadButton()
     false


Text for #222

Text for #222

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -28,10 +28,12 @@
 # Advanced players seeking to optimize their score will need to make
 # similar tradeoffs to what we faced in writing this app!
 #
+# TODO: Use Markdown instead of embedded HTML

 exports.game = {
   name: 'The Hour of NODE'
-  description: 'Learn the basic controls while solving simple puzzles.'
+  description: 'Welcome to Hour of NODE, part of the
+  CS Education Week efforts to teach programming to kids.'
   assume: 'baseline'
   comment: "The original Hour of NODE tutorial"
   author: {name: 'David Huffman', url: 'https://github.com/huffmande'}


removendo smooth click de menu

removendo smooth click de menu

--- app/assets/javascripts/index.js.coffee
+++ app/assets/javascripts/index.js.coffee
@@ -1,12 +1,6 @@
 scrollSpeed = 'fast'
 fadeSpeed = 'fast'
 minimumWindowScrollHeight = 200
-
-$('#top-bar .top-bar-section a').click (e) ->
-  faqId = $(@).attr('href');
-  $('html, body').animate
-    scrollTop : $(faqId).offset().top
-  , scrollSpeed

 # go top button based on a tutplus tutorial
 # link: http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/quick-tip-implement-a-sticky-back-to-top-button/


Retire unused @state attribute from Mock.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -164,7 +164,6 @@
     _check_expects_usage(method_name)
     _start_new_expectation(@, method_name)
     _add_method_to_mock(@, method_name)
-    _set_state(@, "expects")
     _current_expectation(@)

@@ -175,12 +174,6 @@

 _expectations = (mock) ->
   mock.expectations ?= []
-
-_set_state = (mock, state) ->
-  mock.state = state
-
-_is_state_in = (mock, states...) ->
-  mock.state in states

 _is_reserved_word = (word) ->
   word in [ "expects", "args", "returns", "check" ]


Correct some comments due to recent changes.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -1,14 +1,14 @@
 #
-# Expectation is an internal data structure that represents a
-# mocked method that we expect to be called.  Stores the method
-# name, the arguments that we expect to be called with, the value
-# to return or throw, and whether it has actually been called.
-# For example, the following:
-#
-#   my_mock = (new Mock()).expects("my_method").args(1,2,3).returns(42)
+# Expectation represents a mocked method that we expect to be called.
+# It stores the method name, the arguments that we expect to be called
+# with, a value to return or throw, and whether it has actually been
+# called.  For example, the following:
+#
+#   my_mock = new Mock()
+#   expectation = my_mock.expects("my_method").args(1,2,3).returns(42)
 #
 # would result in a Expectation with @method_name = "my_method",
-# @_args = [1,2,3], @returns = 42, @throws = undefined, and @called =
+# @_args = [1,2,3], @_returns = 42, @_throws = undefined, and @called =
 # false.  Doing:
 #
 #   my_mock.my_method(1,2,3)
@@ -30,6 +30,7 @@
   # my_mock = new Mock()
   # expectation = my_mock.expects("my_method")
   # expectation.args(1,2,3)
+  # ...
   #
   # Or more usually:
   #
@@ -46,6 +47,7 @@
   # my_mock = new Mock()
   # expectation = my_mock.expects("my_method")
   # expectation.returns(42)
+  # ...
   #
   # Or more usually:
   #
@@ -62,6 +64,7 @@
   # my_mock = new Mock()
   # expectation = my_mock.expects("my_method")
   # expectation.throws(new Error("an error"))
+  # ...
   #
   # Or more usually:
   #
@@ -126,10 +129,9 @@
 #
 #   my_mock.returns(456).args(123).expects("my_method")
 #
-# is not.  @state is used to remember the last function called so that
-# we can enforce this order.  Note that args, returns and throws are
-# optional.  Also note that either returns or throws can be used, but
-# not both on the same expectation.
+# is not.  Note that args, returns and throws are optional.  Also
+# note that either returns or throws can be used, but not both on
+# the same expectation.
 #
 # @expectations is used as a stack only in so far as the method expectation
 # at the front of the list is the most recently defined expectation and is
@@ -146,15 +148,16 @@
 # @expectations is just an array, not a hash.  It will not grow very large
 # so a linear search for a particular expectation is fine.
 #
-# @expectations and @state are added to the mock object by the various mock
-# methods when needed (i.e. lazily); if we mock an existing class then we
-# only need to worry about adding the mock methods to that class, not
-# monkeying with its contructor to also add @expectations and @state.
+# @expectations is added to the mock object by the various mock methods when
+# needed (i.e. lazily); if we mock an existing class then we only need to
+# worry about adding the mock methods to that class, not monkeying with its
+# contructor to also add @expectations.
 #
 class Mock

   #
-  # my_mock = (new Mock).expects("my_method")
+  # my_mock = new Mock()
+  # my_mock.expects("my_method")
   # my_mock.my_method()
   #
   expects: (method_name) ->
@@ -167,7 +170,7 @@

 #
-# Private Mock 'methods'; making them functions keeps the Mock object uncluttered.
+# private
 #

 _expectations = (mock) ->


change returned Promises to resolve only on completion of successful share

--- js/src/service/SharingClient.coffee
+++ js/src/service/SharingClient.coffee
@@ -75,8 +75,7 @@
       return Promise.resolve(objectKeyPromise)
         .then (latestVersionedObjectKey) ->
           if latestVersionedObjectKey?
-            share(latestVersionedObjectKey, uuids, isSearchable)
-          return
+            share(latestVersionedObjectKey, uuids)

     #
     # parameters to a private function are assumed valid since it is expected for the calling function to validate
@@ -129,7 +128,6 @@
             })

           sharingApi.shareObject(sharingRequest)
-          return
       )

     revokeObject: (objectId, uuids) ->


PROV keyword used to set scales

PROV keyword used to set scales

--- app/controllers/quick_dashboard.coffee
+++ app/controllers/quick_dashboard.coffee
@@ -52,6 +52,7 @@
     super

     @calibrations = {}
+    @provenances = {}
     @dfs =
       webfits: new $.Deferred()
       fitsjs: new $.Deferred()
@@ -134,6 +135,7 @@
           width = cache[band].width
           height = cache[band].height
           @calibrations[band] = cache[band].calibration
+          @provenances[band] = cache[band].provenance

           @wfits.loadImage(band, arr, width, height)
           @dfs[band].resolve()
@@ -160,8 +162,10 @@
             width = dataunit.width
             height = dataunit.height
             calibration = @getCalibration(header)
+            provenance = @getProvenance(header)

             @calibrations[band] = calibration
+            @provenances[band] = provenance
             @wfits.loadImage(band, arr, width, height)

             # Cache some data
@@ -171,7 +175,8 @@
             @cache['prefix'][band].width = width
             @cache['prefix'][band].height = height
             @cache['prefix'][band].calibration = calibration
-
+            @cache['prefix'][band].provenance = provenance
+
             @dfs[band].resolve()
           )

@@ -186,6 +191,9 @@
   getCalibration: (header) ->
     zeroPoint = header.get('MZP_AB')
     return Math.pow(10, 0.4*(30.0 - zeroPoint))
+
+  getProvenance: (header) ->
+    return header.get('PROV')

   # Call when all channels are received (i.e. each deferred is resolved)
   allChannelsReceived: =>
@@ -234,11 +242,16 @@
       @trigger 'close'
       return

+    # PJM: Adjust preset if provenance of IR data is CFHT:
+    if @provenances['Ks'] is 'C'
+      preset = parseInt(preset,10) + 3
+
     # Pass preset to classifier so it can be stored on annotation
     @classifier.preset = preset

     # Get a preset and apply to color composite
     parameters = @parameters[preset]
+    # console.log "preset, @parameters = ",preset,@parameters

     # PJM: not sure this is the best way to pass down the calibrations array but it works:
     # @wfits.setCalibrations(1, 1, 1)


synchro BJA

synchro BJA

--- app/views/CNeditor/CNeditor.coffee
+++ app/views/CNeditor/CNeditor.coffee
@@ -882,6 +882,12 @@
                         else
                             break
                     l = l.linePrev
+
+            when 'Lu'
+                @markerList(line)
+
+            when 'Lh'
+                @titleList(line)

             else
                 return false


Added triggerClearEvent to clear function

--- src/core/LiterallyCanvas.coffee
+++ src/core/LiterallyCanvas.coffee
@@ -354,12 +354,13 @@
     for ctx in contexts
       ctx.restore()

-  clear: ->
+  clear: (triggerClearEvent=true) ->
     oldShapes = @shapes
     newShapes = []
     @execute(new actions.ClearAction(this, oldShapes, newShapes))
     @repaintLayer('main')
-    @trigger('clear', null)
+    if triggerClearEvent
+      @trigger('clear', null)
     @trigger('drawingChange', {})

   execute: (action) ->


Use resource path from atom.getLoadSettings()

--- src/window.coffee
+++ src/window.coffee
@@ -64,11 +64,13 @@
   windowEventHandler?.unsubscribe()

 installAtomCommand = (callback) ->
-  commandPath = path.join(window.resourcePath, 'atom.sh')
+  {resourcePath} = atom.getLoadSettings()
+  commandPath = path.join(resourcePath, 'atom.sh')
   require('./command-installer').install(commandPath, callback)

 installApmCommand = (callback) ->
-  commandPath = path.join(window.resourcePath, 'node_modules', '.bin', 'apm')
+  {resourcePath} = atom.getLoadSettings()
+  commandPath = path.join(resourcePath, 'node_modules', '.bin', 'apm')
   require('./command-installer').install(commandPath, callback)

 window.deserializeEditorWindow = ->


Adds comments to the sign: tasks

Adds comments to the sign: tasks

--- tasks/sign.coffee
+++ tasks/sign.coffee
@@ -5,9 +5,10 @@
 manifest = require '../src/package.json'
 secrets = require '../secrets.json'

+# Sign the darwin64 app
 gulp.task 'sign:darwin64', ['build:darwin64'], (done) ->
   if process.platform isnt 'darwin'
-    console.warn 'Skipping darwin app signing; This only works on darwin due to the `codesign` command.'
+    console.warn 'Skipping darwin64 app signing; This only works on darwin due to the `codesign` command.'
     return done()

   async.series [
@@ -28,3 +29,8 @@
       './build/darwin64/' + manifest.productName + '.app'
     ].join(' ')
   ], done
+
+# Sign everything
+gulp.task 'sign', [
+  'sign:darwin64'
+]


Adds back sd

Adds back sd

--- apps/notifications/client/index.coffee
+++ apps/notifications/client/index.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
-{ FOLLOWING, API_URL } = require('sharify').data
+{ FOLLOWING, API_URL } = sd = require('sharify').data
 scrollFrame = require 'scroll-frame'
 qs = require 'querystring'
 Notifications = require '../../../collections/notifications.coffee'


sped up smallestGap even further

sped up smallestGap even further

--- src/network/smallestGap.coffee
+++ src/network/smallestGap.coffee
@@ -1,7 +1,8 @@
 # Returns distances of all objects in array
 module.exports = (arr, accessor) ->

-  distance = false
+  distances = []
+
   quad = d3.geom.quadtree()
     .x (d) -> if accessor then accessor(d)[0] else d.x
     .y (d) -> if accessor then accessor(d)[1] else d.y
@@ -16,8 +17,7 @@
               node2 = n2.point
               xx = Math.abs node1.x - node2.x
               yy = Math.abs node1.y - node2.y
-              d = Math.sqrt((xx * xx) + (yy * yy))
-              distance = d if distance is false or d < distance
+              distances.push Math.sqrt((xx * xx) + (yy * yy))
     false

-  distance
+  d3.min distances


OHM-207 Issue #577: Added null check

OHM-207 Issue #577: Added null check

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -20,9 +20,12 @@
   path = "/render?target=transformNull(summarize(stats.counters.#{domain}.channels.count,'1hour'))&from=-1days&format=json"
   raw = yield exports.fetchData path

-  this.body = raw.data.map (item) ->
-    load: item[0]
-    timestamp: moment.unix item[1]
+  if raw.data?
+    this.body = raw.data.map (item) ->
+      load: item[0]
+      timestamp: moment.unix item[1]
+  else
+    this.body = []

 # Retrives Global Status Metrics from the StatsD API


Document and match TextEditorMarker API

Document and match TextEditorMarker API

--- src/display-marker.coffee
+++ src/display-marker.coffee
@@ -1,15 +1,60 @@
 {Emitter, CompositeDisposable} = require 'event-kit'

+# Essential: Represents a buffer annotation that remains logically stationary
+# even as the buffer changes. This is used to represent cursors, folds, snippet
+# targets, misspelled words, and anything else that needs to track a logical
+# location in the buffer over time.
+#
+# ### DisplayMarker Creation
+#
+# Use {DisplayMarkerLayer::markBufferRange} or {DisplayMarkerLayer::markScreenRange}
+# rather than creating Markers directly.
+#
+# ### Head and Tail
+#
+# Markers always have a *head* and sometimes have a *tail*. If you think of a
+# marker as an editor selection, the tail is the part that's stationary and the
+# head is the part that moves when the mouse is moved. A marker without a tail
+# always reports an empty range at the head position. A marker with a head position
+# greater than the tail is in a "normal" orientation. If the head precedes the
+# tail the marker is in a "reversed" orientation.
+#
+# ### Validity
+#
+# Markers are considered *valid* when they are first created. Depending on the
+# invalidation strategy you choose, certain changes to the buffer can cause a
+# marker to become invalid, for example if the text surrounding the marker is
+# deleted. The strategies, in order of descending fragility:
+#
+# * __never__: The marker is never marked as invalid. This is a good choice for
+#   markers representing selections in an editor.
+# * __surround__: The marker is invalidated by changes that completely surround it.
+# * __overlap__: The marker is invalidated by changes that surround the
+#   start or end of the marker. This is the default.
+# * __inside__: The marker is invalidated by changes that extend into the
+#   inside of the marker. Changes that end at the marker's start or
+#   start at the marker's end do not invalidate the marker.
+# * __touch__: The marker is invalidated by a change that touches the marked
+#   region in any way, including changes that end at the marker's
+#   start or start at the marker's end. This is the most fragile strategy.
+#
+# See {TextBuffer::markRange} for usage.
 module.exports =
 class DisplayMarker
+  ###
+  Section: Construction and Destruction
+  ###
+
   constructor: (@layer, @bufferMarker) ->
     {@id} = @bufferMarker
     @hasChangeObservers = false
     @emitter = new Emitter
-    @disposables = null
+    @disposables = new CompositeDisposable
     @destroyed = false
-    @bufferMarker.onDidDestroy(@destroy.bind(this))
-
+    @disposables.add @bufferMarker.onDidDestroy(@destroy.bind(this))
+
+  # Essential: Destroys the marker, causing it to emit the 'destroyed' event. Once
+  # destroyed, a marker cannot be restored by undo/redo operations.
   destroy: ->
     return if @destroyed

@@ -17,87 +62,54 @@
     @bufferMarker.destroy()
     @emitter.emit('did-destroy')
     @layer.didDestroyMarker(this)
-
+    @emitter.dispose()
+    @disposables.dispose()
+
+  # Essential: Creates and returns a new {DisplayMarker} with the same properties as
+  # this marker.
+  #
+  # {Selection} markers (markers with a custom property `type: "selection"`)
+  # should be copied with a different `type` value, for example with
+  # `marker.copy({type: null})`. Otherwise, the new marker's selection will
+  # be merged with this marker's selection, and a `null` value will be
+  # returned.
+  #
+  # * `properties` (optional) {Object} properties to associate with the new
+  # marker. The new marker's properties are computed by extending this marker's
+  # properties with `properties`.
+  #
+  # Returns a {DisplayMarker}.
   copy: (params) ->
     @layer.getMarker(@bufferMarker.copy(params).id)

-  compare: (otherMarker) ->
-    @bufferMarker.compare(otherMarker.bufferMarker)
-
-  onDidDestroy: (callback) ->
-    @emitter.on('did-destroy', callback)
-
-  isDestroyed: -> @destroyed
-
-  getBufferRange: ->
-    @bufferMarker.getRange()
-
-  getScreenRange: ->
-    @layer.translateBufferRange(@getBufferRange())
-
-  setBufferRange: (bufferRange, options) ->
-    @bufferMarker.setRange(bufferRange, options)
-
-  setScreenRange: (screenRange, options) ->
-    @setBufferRange(@layer.translateScreenRange(screenRange, options), options)
-
-  getHeadBufferPosition: ->
-    @bufferMarker.getHeadPosition()
-
-  setHeadBufferPosition: (bufferPosition, properties) ->
-    @bufferMarker.setHeadPosition(bufferPosition, properties)
-
-  getHeadScreenPosition: ->
-    @layer.translateBufferPosition(@bufferMarker.getHeadPosition())
-
-  setHeadScreenPosition: (screenPosition, options) ->
-    bufferPosition = @layer.translateScreenPosition(screenPosition, options)
-    @bufferMarker.setHeadPosition(bufferPosition, options)
-
-  getTailBufferPosition: ->
-    @bufferMarker.getTailPosition()
-
-  setTailBufferPosition: (bufferPosition, options) ->
-    @bufferMarker.setTailPosition(bufferPosition, options)
-
-  getTailScreenPosition: ->
-    @layer.translateBufferPosition(@bufferMarker.getTailPosition())
-
-  setTailScreenPosition: (screenPosition, options) ->
-    bufferPosition = @layer.translateScreenPosition(screenPosition, options)
-    @bufferMarker.setTailPosition(bufferPosition, options)
-
-  getStartBufferPosition: ->
-    @bufferMarker.getStartPosition()
-
-  getEndBufferPosition: ->
-    @bufferMarker.getEndPosition()
-
-  isValid: ->
-    @bufferMarker.isValid()
-
-  isReversed: ->
-    @bufferMarker.isReversed()
-
-  getProperties: ->
-    @bufferMarker.getProperties()
-
-  setProperties: (properties) ->
-    @bufferMarker.setProperties(properties)
-
-  hasTail: ->
-    @bufferMarker.hasTail()
-
-  plantTail: ->
-    @bufferMarker.plantTail()
-
-  clearTail: ->
-    @bufferMarker.clearTail()
-
+  ###
+  Section: Event Subscription
+  ###
+
+  # Essential: Invoke the given callback when the state of the marker changes.
+  #
+  # * `callback` {Function} to be called when the marker changes.
+  #   * `event` {Object} with the following keys:
+  #     * `oldHeadBufferPosition` {Point} representing the former head buffer position
+  #     * `newHeadBufferPosition` {Point} representing the new head buffer position
+  #     * `oldTailBufferPosition` {Point} representing the former tail buffer position
+  #     * `newTailBufferPosition` {Point} representing the new tail buffer position
+  #     * `oldHeadScreenPosition` {Point} representing the former head screen position
+  #     * `newHeadScreenPosition` {Point} representing the new head screen position
+  #     * `oldTailScreenPosition` {Point} representing the former tail screen position
+  #     * `newTailScreenPosition` {Point} representing the new tail screen position
+  #     * `wasValid` {Boolean} indicating whether the marker was valid before the change
+  #     * `isValid` {Boolean} indicating whether the marker is now valid
+  #     * `hadTail` {Boolean} indicating whether the marker had a tail before the change
+  #     * `hasTail` {Boolean} indicating whether the marker now has a tail
+  #     * `oldProperties` {Object} containing the marker's custom properties before the change.
+  #     * `newProperties` {Object} containing the marker's custom properties after the change.
+  #     * `textChanged` {Boolean} indicating whether this change was caused by a textual change
+  #       to the buffer or whether the marker was manipulated directly via its public API.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidChange: (callback) ->
     unless @hasChangeObservers
-      @emitter = new Emitter
-      @disposables = new CompositeDisposable
       @oldHeadBufferPosition = @getHeadBufferPosition()
       @oldHeadScreenPosition = @getHeadScreenPosition()
       @oldTailBufferPosition = @getTailBufferPosition()
@@ -106,6 +118,214 @@
       @disposables.add @bufferMarker.onDidChange (event) => @notifyObservers(event.textChanged)
       @hasChangeObservers = true
     @emitter.on 'did-change', callback
+
+  # Essential: Invoke the given callback when the marker is destroyed.
+  #
+  # * `callback` {Function} to be called when the marker is destroyed.
+  #
+  # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
+  onDidDestroy: (callback) ->
+    @emitter.on('did-destroy', callback)
+
+  ###
+  Section: TextEditorMarker Details
+  ###
+
+  # Essential: Returns a {Boolean} indicating whether the marker is valid.
+  # Markers can be invalidated when a region surrounding them in the buffer is
+  # changed.
+  isValid: ->
+    @bufferMarker.isValid()
+
+  # Essential: Returns a {Boolean} indicating whether the marker has been
+  # destroyed. A marker can be invalid without being destroyed, in which case
+  # undoing the invalidating operation would restore the marker. Once a marker
+  # is destroyed by calling {DisplayMarker::destroy}, no undo/redo operation
+  # can ever bring it back.
+  isDestroyed: -> @destroyed
+
+  # Essential: Returns a {Boolean} indicating whether the head precedes the tail.
+  isReversed: ->
+    @bufferMarker.isReversed()
+
+  # Essential: Get the invalidation strategy for this marker.
+  #
+  # Valid values include: `never`, `surround`, `overlap`, `inside`, and `touch`.
+  #
+  # Returns a {String}.
+  getInvalidationStrategy: ->
+    @bufferMarker.getInvalidationStrategy()
+
+  # Essential: Returns an {Object} containing any custom properties associated with
+  # the marker.
+  getProperties: ->
+    @bufferMarker.getProperties()
+
+  # Essential: Merges an {Object} containing new properties into the marker's
+  # existing properties.
+  #
+  # * `properties` {Object}
+  setProperties: (properties) ->
+    @bufferMarker.setProperties(properties)
+
+  # Essential: Returns whether this marker matches the given parameters. The
+  # parameters are the same as {DisplayMarkerLayer::findMarkers}.
+  matchesProperties: (attributes) ->
+    attributes = @layer.translateToBufferMarkerParams(attributes)
+    @bufferMarker.matchesParams(attributes)
+
+  ###
+  Section: Comparing to other markers
+  ###
+
+  # Essential: Compares this marker to another based on their ranges.
+  #
+  # * `other` {DisplayMarker}
+  #
+  # Returns a {Number}
+  compare: (otherMarker) ->
+    @bufferMarker.compare(otherMarker.bufferMarker)
+
+  # Essential: Returns a {Boolean} indicating whether this marker is equivalent to
+  # another marker, meaning they have the same range and options.
+  #
+  # * `other` {DisplayMarker} other marker
+  isEqual: (other) ->
+    @bufferMarker.isEqual(other.bufferMarker)
+
+  ###
+  Section: Managing the marker's range
+  ###
+
+  # Essential: Gets the buffer range of this marker.
+  #
+  # Returns a {Range}.
+  getBufferRange: ->
+    @bufferMarker.getRange()
+
+  # Essential: Gets the screen range of this marker.
+  #
+  # Returns a {Range}.
+  getScreenRange: ->
+    @layer.translateBufferRange(@getBufferRange())
+
+  # Essential: Modifies the buffer range of this marker.
+  #
+  # * `bufferRange` The new {Range} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  #   * `reversed` {Boolean} If true, the marker will to be in a reversed orientation.
+  setBufferRange: (bufferRange, properties) ->
+    @bufferMarker.setRange(bufferRange, properties)
+
+  # Essential: Modifies the screen range of this marker.
+  #
+  # * `screenRange` The new {Range} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  #   * `reversed` {Boolean} If true, the marker will to be in a reversed orientation.
+  setScreenRange: (screenRange, properties, options) ->
+    @setBufferRange(@layer.translateScreenRange(screenRange, options), properties)
+
+  # Extended: Retrieves the buffer position of the marker's head.
+  #
+  # Returns a {Point}.
+  getHeadBufferPosition: ->
+    @bufferMarker.getHeadPosition()
+
+  # Extended: Sets the buffer position of the marker's head.
+  #
+  # * `bufferPosition` The new {Point} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  setHeadBufferPosition: (bufferPosition, properties) ->
+    @bufferMarker.setHeadPosition(bufferPosition, properties)
+
+  # Extended: Retrieves the screen position of the marker's head.
+  #
+  # Returns a {Point}.
+  getHeadScreenPosition: (options) ->
+    @layer.translateBufferPosition(@bufferMarker.getHeadPosition(), options)
+
+  # Extended: Sets the screen position of the marker's head.
+  #
+  # * `screenPosition` The new {Point} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  setHeadScreenPosition: (screenPosition, properties, options) ->
+    bufferPosition = @layer.translateScreenPosition(screenPosition, options)
+    @setHeadBufferPosition(bufferPosition, properties)
+
+  # Extended: Retrieves the buffer position of the marker's tail.
+  #
+  # Returns a {Point}.
+  getTailBufferPosition: ->
+    @bufferMarker.getTailPosition()
+
+  # Extended: Sets the buffer position of the marker's tail.
+  #
+  # * `bufferPosition` The new {Point} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  setTailBufferPosition: (bufferPosition, properties) ->
+    @bufferMarker.setTailPosition(bufferPosition, properties)
+
+  # Extended: Retrieves the screen position of the marker's tail.
+  #
+  # Returns a {Point}.
+  getTailScreenPosition: ->
+    @layer.translateBufferPosition(@bufferMarker.getTailPosition())
+
+  # Extended: Sets the screen position of the marker's tail.
+  #
+  # * `screenPosition` The new {Point} to use
+  # * `properties` (optional) {Object} properties to associate with the marker.
+  setTailScreenPosition: (screenPosition, properties, options) ->
+    bufferPosition = @layer.translateScreenPosition(screenPosition, options)
+    @bufferMarker.setTailPosition(bufferPosition, properties)
+
+  # Extended: Retrieves the buffer position of the marker's start. This will always be
+  # less than or equal to the result of {DisplayMarker::getEndBufferPosition}.
+  #
+  # Returns a {Point}.
+  getStartBufferPosition: ->
+    @bufferMarker.getStartPosition()
+
+  # Essential: Retrieves the screen position of the marker's start. This will always be
+  # less than or equal to the result of {DisplayMarker::getEndScreenPosition}.
+  #
+  # Returns a {Point}.
+  getStartScreenPosition: (options) ->
+    @layer.translateBufferPosition(@getStartBufferPosition(), options)
+
+  # Extended: Retrieves the buffer position of the marker's end. This will always be
+  # greater than or equal to the result of {DisplayMarker::getStartBufferPosition}.
+  #
+  # Returns a {Point}.
+  getEndBufferPosition: ->
+    @bufferMarker.getEndPosition()
+
+  # Essential: Retrieves the screen position of the marker's end. This will always be
+  # greater than or equal to the result of {DisplayMarker::getStartScreenPosition}.
+  #
+  # Returns a {Point}.
+  getEndScreenPosition: (options) ->
+    @layer.translateBufferPosition(@getEndBufferPosition(), options)
+
+  # Extended: Returns a {Boolean} indicating whether the marker has a tail.
+  hasTail: ->
+    @bufferMarker.hasTail()
+
+  # Extended: Plants the marker's tail at the current head position. After calling
+  # the marker's tail position will be its head position at the time of the
+  # call, regardless of where the marker's head is moved.
+  plantTail: ->
+    @bufferMarker.plantTail()
+
+  # Extended: Removes the marker's tail. After calling the marker's head position
+  # will be reported as its current tail position until the tail is planted
+  # again.
+  clearTail: ->
+    @bufferMarker.clearTail()
+
+  ###
+  Section: Private
+  ###

   notifyObservers: (textChanged) ->
     textChanged ?= false


changes in Gamma and Beta func impls, #21

--- src/vocabulary/exclamation.coffee
+++ src/vocabulary/exclamation.coffee
@@ -1,5 +1,5 @@
 {pervasive, real, withIdentity} = require './vhelpers'
-{isInt} = require '../helpers'
+{isInt, prod} = require '../helpers'
 {APLArray} = require '../array'
 {DomainError} = require '../errors'
 Γ = require 'gamma'
@@ -21,16 +21,10 @@
     # !¯1   !!! DOMAIN ERROR
     # !¯200 !!! DOMAIN ERROR
     monad: real (x) ->
-      if isInt x, 0, 25
-        r = 1; i = 2; (while i <= x then r *= i++); r
-      else if x > 150
-        1 / 0
-      else if x < 0 and x is Math.floor x
-        throw DomainError()
-      else if x < -150
-        0
-      else
-        Γ(x + 1)
+      if not isInt x then Γ(x + 1)
+      else if x < 0 then throw DomainError()
+      else if x < smallFactorials.length then smallFactorials[x]
+      else Math.round Γ(x + 1)

     # Binomial (`!`)
     #
@@ -44,27 +38,30 @@
     # (2!1000)=499500 <=> 1
     # (998!1000)=499500 <=> 1
     #
-    #                       Sign bit        Expected
+    #                   Negative integer?   Expected
     #                      ⍺   ⍵  ⍵-⍺        Result
     #                     -----------      ----------
     # 3!5   <=> 10  #      0   0   0       (!⍵)÷(!⍺)×!⍵-⍺
     # 5!3   <=> 0   #      0   0   1       0
-    #               #      0   1   0       Domain Error
+    # see below     #      0   1   0       Domain Error
     # 3!¯5  <=> ¯35 #      0   1   1       (¯1*⍺)×⍺!⍺-⍵+1
     # ¯3!5  <=> 0   #      1   0   0       0
     #               #      1   0   1       Cannot arise
     # ¯5!¯3 <=> 6   #      1   1   0       (¯1*⍵-⍺)×(|⍵+1)!(|⍺+1)
     # ¯3!¯5 <=> 0   #      1   1   1       0
+    #
+    # 0.5!¯1 !!! DOMAIN ERROR
     dyad: Beta = real (n, k) ->
-      r =
-        if 0 <= n < k or k < 0 <= n or n < k < 0
-          0
-        else if n < 0 <= k
-          if not isInt k then throw DomainError()
-          Math.pow(-1, k) * Beta k - n - 1, k
-        else if k <= n < 0
-          if not isInt n - k then throw DomainError()
-          Math.pow(-1, n - k) * Beta Math.abs(k + 1), Math.abs(n + 1)
-        else
-          Math.exp lnΓ(n + 1) - lnΓ(k + 1) - lnΓ(n - k + 1)
+      r = switch 4*negInt(k) + 2*negInt(n) + negInt(n - k)
+        when 0b000 then Math.exp lnΓ(n + 1) - lnΓ(k + 1) - lnΓ(n - k + 1)
+        when 0b001 then 0
+        when 0b010 then throw DomainError()
+        when 0b011 then Math.pow(-1, k) * Beta k - n - 1, k
+        when 0b100 then 0
+        when 0b101 then ;
+        when 0b110 then Math.pow(-1, n - k) * Beta Math.abs(k + 1), Math.abs(n + 1)
+        when 0b111 then 0
       if isInt(n) and isInt(k) then Math.round r else r
+
+negInt = (x) -> isInt(x) and x < 0
+smallFactorials = do -> [x = 1].concat(for i in [1..25] then x *= i)


Preserve escaped quotes within argument's string literals

Also:
 - check for response status code while executing an action

--- stormbot/scripts/stacktion.coffee
+++ stormbot/scripts/stacktion.coffee
@@ -8,20 +8,19 @@
 _ = require 'lodash'

 CONN_ERRORS =
-  'ECONNREFUSED': (err) -> "Connection has been refused. Check if other components are running as well."
-  'default': (err) -> "Something gone terribly wrong. #{err}"
-
-errorHandler = (robot) ->
-  (err, res) ->
-    if err
-      robot.logger.error (CONN_ERRORS[err.code] || CONN_ERRORS['default']) err
+  'ECONNREFUSED': (err) ->
+    "Connection has been refused. Check if other components are running as well. [#{err.code}]"
+  'ECONNRESET': (err) ->
+    "Remote server abruptly closed its end of the connection. Check if other components " +
+      "throw an error too. [#{err.code}]"
+  'default': (err) -> "Something gone terribly wrong. [#{err.code}]"

 parseArgs = (scheme=[], argstr="") ->
-  # split string by space while preserving quoted literals
-  args = argstr.match(/('.*?'|".*?"|\S+)/g) or []
+  # split string by space while preserving quoted literals and escaped quotes
+  args = argstr.match(/(["'])(?:(?!\1)[^\\]|\\.)*\1|(\S)+/g) or []

-  # trim quotes
-  (args[i] = arg[1...-1]) for arg, i in args when arg[0] in ['\'', '\"']
+  # trim quotes and unescape strip slashes
+  (args[i] = arg[1...-1].replace(/\\(.)/mg, "$1")) for arg, i in args when arg[0] in ['\'', '\"']

   # build an object
   _.zipObject scheme, args
@@ -32,6 +31,10 @@
   robot.error (err, msg) ->
     robot.logger.error 'Uncaught exception:', err

+  errorHandler = (err, res) ->
+    if err
+      robot.logger.error (CONN_ERRORS[err.code] || CONN_ERRORS['default']) err
+
   # define basic clients
   httpclients =
     actiontypes: robot.http('http://localhost:9501').path('/actiontypes')
@@ -40,12 +43,12 @@

   # init for actions
   httpclients.actions
-  .get(errorHandler robot) (err, res, body) ->
+  .get(errorHandler) (err, res, body) ->
     actions = JSON.parse body
     robot.brain.set 'actions', _.zipObject _.map(actions, 'name'), actions

   httpclients.actiontypes
-    .get(errorHandler robot) (err, res, body) ->
+    .get(errorHandler) (err, res, body) ->
       actiontypes = JSON.parse body
       robot.brain.set 'actiontypes', _.zipObject _.map(actiontypes, 'name'), actiontypes

@@ -69,8 +72,11 @@

     httpclients.actionexecutions
       .header('Content-Type', 'application/json')
-      .post(JSON.stringify(payload), errorHandler robot) (err, res, body) ->
+      .post(JSON.stringify(payload), errorHandler) (err, res, body) ->
         staction_execution = JSON.parse(body)
+
+        unless res.statusCode is 201
+          msg.send "Action has failed to run"

         unless staction_execution.status is 'complete'
           msg.send "Action has failed to execute"


Fixed issue where cookie was being set every page load

--- src/ahoy.coffee
+++ src/ahoy.coffee
@@ -10,7 +10,7 @@

     source = this.getSourceFromCookie cookieName

-    unless source? and false
+    unless source?
       source = this.parseQuery window.location.href
       source ?= this.parseReferrer document.referrer
       this.Cookie.set cookieName, this.JSON.stringify(source)


fixed endpoint

fixed endpoint

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -83,11 +83,11 @@
     }
     ;

-    var failed = 'data' in results.Failed ? results.Failed.data[0][0] + results.Failed.data[1][0] : 0,
-      processing = 'data'  in results.Processing ? results.Processing.data[0][0] + results.Processing.data[1][0] : 0,
-      completed = 'data' in results.Completed ? results.Completed.data[0][0] + results.Completed.data[1][0] : 0,
-      successful = 'data' in  results.Successful ? results.Successful.data[0][0] + results.Successful.data[1][0] : 0,
-      completedWErrors = 'data' in results['Completed with error(s)'] ? results['Completed with error(s)'].data[0][0] + results['Completed with error(s)'].data[1][0] : 0;
+    var failed = 'Failed' in results ? ('data' in results.Failed ? results.Failed.data[0][0] + results.Failed.data[1][0] : 0) : 0,
+      processing = 'Processing' in results ? ( 'data'  in results.Processing ? results.Processing.data[0][0] + results.Processing.data[1][0] : 0) : 0,
+      completed = 'Completed' in results ? ( 'data' in results.Completed ? results.Completed.data[0][0] + results.Completed.data[1][0] : 0) : 0,
+      successful ='Successful' in results ? ( 'data' in  results.Successful ? results.Successful.data[0][0] + results.Successful.data[1][0] : 0) : 0,
+      completedWErrors = 'Completed with error(s)' in results ? ( 'data' in results['Completed with error(s)'] ? results['Completed with error(s)'].data[0][0] + results['Completed with error(s)'].data[1][0] : 0) : 0;

     data.push({


:lipstick:

:lipstick:

--- src/app/config.coffee
+++ src/app/config.coffee
@@ -13,12 +13,15 @@
   configDirPath: configDirPath

   load: ->
+    @loadUserConfig()
+    @assignDefaults()
+    @registerNewExtensions()
+    @requireUserInitScript()
+
+  loadUserConfig: ->
     if fs.exists(configJsonPath)
       userConfig = JSON.parse(fs.read(configJsonPath))
       _.extend(this, userConfig)
-    @assignDefaults()
-    @registerNewExtensions()
-    @requireUserInitScript()

   assignDefaults: ->
     @core ?= {}


Replace Editor.activeEditSessionIndex with activeEditSessionIndex method

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -45,6 +45,7 @@
   isFocused: false
   softTabs: true
   tabText: '  '
+  activeEditSession: null
   editSessions: null
   attached: false
   lineOverdraw: 100
@@ -84,8 +85,11 @@

   serialize: ->
     @saveActiveEditSession()
-    editSessions = @editSessions.map (session) -> session.serialize()
-    { viewClass: "Editor", editSessions, @activeEditSessionIndex, @isFocused }
+
+    viewClass: "Editor"
+    editSessions: @editSessions.map (session) -> session.serialize()
+    activeEditSessionIndex: @getActiveEditSessionIndex()
+    isFocused: @isFocused

   copy: ->
     Editor.deserialize(@serialize(), @rootView())
@@ -382,13 +386,16 @@
       _.remove(@editSessions, editSession)

   loadNextEditSession: ->
-    nextIndex = (@activeEditSessionIndex + 1) % @editSessions.length
+    nextIndex = (@getActiveEditSessionIndex() + 1) % @editSessions.length
     @setActiveEditSessionIndex(nextIndex)

   loadPreviousEditSession: ->
-    previousIndex = @activeEditSessionIndex - 1
+    previousIndex = @getActiveEditSessionIndex() - 1
     previousIndex = @editSessions.length - 1 if previousIndex < 0
     @setActiveEditSessionIndex(previousIndex)
+
+  getActiveEditSessionIndex: ->
+    return index for session, index in @editSessions when session == @activeEditSession

   setActiveEditSessionIndex: (index) ->
     throw new Error("Edit session not found") unless @editSessions[index]
@@ -398,7 +405,6 @@
       @activeEditSession.off()

     @activeEditSession = @editSessions[index]
-    @activeEditSessionIndex = index

     @unsubscribeFromBuffer() if @buffer
     @buffer = @activeEditSession.buffer


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -841,7 +841,7 @@
       @destroyFold($(e.currentTarget).attr('fold-id'))
       false

-    onMouseDown = (e) =>
+    @renderedLines.on 'mousedown', (e) =>
       clickCount = e.originalEvent.detail

       screenPosition = @screenPositionFromMouseEvent(e)
@@ -858,8 +858,6 @@
         @activeEditSession.selectLine() unless e.shiftKey

       @selectOnMousemoveUntilMouseup() unless e.ctrlKey or e.originalEvent.which > 1
-
-    @renderedLines.on 'mousedown', onMouseDown

     @on "textInput", (e) =>
       @insertText(e.originalEvent.data)


Add outDegree method

Add outDegree method

--- app/scripts/alchemy/models/node.coffee
+++ app/scripts/alchemy/models/node.coffee
@@ -2,6 +2,7 @@
     constructor: (node) ->
         _.merge(@, node)
         @_d3 = {}
+
         # Merge undefined nodeStyle keys from conf.
         # Works with undefined @nodeStyle
         conf = alchemy.conf
@@ -10,8 +11,9 @@
     # Stores edge.id for easy edge lookup
     edges: []
     addEdge: (edge)-> @edges.push(edge)
+    outDegree: ()-> @edges.length

-    # Neighbors
+    # Find neighbors
     neighbors: ()->
         regex = new RegExp("[(#{@id}#{'\\'}-)(#{'\\'}-#{@id})]","g")
-        _.map @edges, (edgeID)->  edgeID.replace(regex, "")+        _.map @edges, (edgeID)->  edgeID.replace(regex, "")


auto indentation rules

auto indentation rules

--- src/pry/commands/xecute.coffee
+++ src/pry/commands/xecute.coffee
@@ -24,36 +24,57 @@
     @execute_code input.join(' ')
     chain.next()

+  colorizeLastLine: (prompt, code, lang = @compiler.mode()) ->
+    done = false
+    pygmentize {lang, format: "terminal"}, code, (_, res) =>
+      done = true
+      process.stdout.moveCursor(0, -1)
+      console.log prompt + res.toString().trim()
+    deasync.runLoopOnce() until done
+
   eval_code: (code, language) ->
     @output.send @compiler.execute(@code + @indent + code, language)
     @code = @indent = ''

-  colorizeLastLine: (code) ->
-    done = false
-    pygmentize {lang: @compiler.mode(), format: "terminal"}, code, (_, res) =>
-      done = true
-      process.stdout.moveCursor(0, -1)
-      console.log @prompt.cli._prompt + res.toString().slice(0, -1)
-    deasync.runLoopOnce() until done
-
   execute_code: (code, language = null) ->
     try
+      shouldDecreaseIndent = code.match(/^\s*(\}|\]|else|catch|finally|else\s+if\s+\S.*)$/)?
+
+      prompt = @prompt.cli._prompt
+      if code and shouldDecreaseIndent
+        @indent = @indent.slice(0, -2)
+        prompt = prompt.slice(0, -2)
+        code += '  '
+
       # process.stdout.moveCursor(0, -1)
-      # console.log @prompt.cli._prompt + code
-      @colorizeLastLine(code)
+      # console.log prompt + code
+      @colorizeLastLine(prompt, code) if code

-      if code.trim().slice(-1) is '\\'
-        @code += @indent + code.trim().slice(0, -1) + "\n"
+      shouldIncreaseIndent = code.match(///^\s*(.*class
+        |[a-zA-Z\$_](\w|\$|:|\.)*\s*(?=\:(\s*\(.*\))?\s*((=|-)>\s*$)) # function that is not one line
+        |[a-zA-Z\$_](\w|\$|\.)*\s*(:|=)\s*((if|while)(?!.*?then)|for|$) # assignment using multiline if/while/for
+        |(if|while|unless)\b(?!.*?then)|(for|switch|when|loop)\b
+        |(else|try|finally|catch\s+\S.*|else\s+if\s+\S.*)\s*$
+        |.*[-=]>$
+        |.*[\{\[]$)
+        |\-\>\s*$///)?
+
+      hasTrailingBackslash = code.trim().slice(-1) is '\\'
+      if shouldIncreaseIndent or hasTrailingBackslash
+        @code += @indent + (if hasTrailingBackslash then code.slice(0, -1) else code) + "\n"
         @indent += '  '
       else
+
         if @indent
           if code
-            @code += @indent + code.trim() + "\n"
+            @code += @indent + code + "\n"
           else
-            process.stdout.moveCursor(0, -1)
             @prompt.count--
             @indent = @indent.slice(0, -2)
-            @eval_code(code) unless @indent
+            if @indent
+              process.stdout.moveCursor(0, -1)
+            else
+              @eval_code(code)
         else
           @eval_code(code)


Better logging and names for script injection

--- bokehjs/src/coffee/server/embed.coffee
+++ bokehjs/src/coffee/server/embed.coffee
@@ -4,11 +4,12 @@
     "common/load_models",
     "server/serverutils",
 ],  ($, base, load_models, serverutils) ->
+
   inject_css = (url) ->
       link = $("<link href='#{url}' rel='stylesheet' type='text/css'>")
       $('body').append(link)

-  add_plot = (element, info, all_models) ->
+  add_plot_static = (element, info, all_models) ->
     modelid = info['bokehModelid'];
     load_models(all_models);
     modeltype = info['bokehModeltype'];
@@ -24,20 +25,31 @@
       _.delay(-> $(element).replaceWith(view.$el))
     )
   inject_plot = (element_id, all_models) ->
+    console.log "Injecting plot for script tag with id: #" + element_id
     script = $("#" + element_id)
+    if script.length == 0
+      throw "Error injecting plot: could not find script tag with id: " + element_id
+    if script.length > 1
+      throw "Error injecting plot: found too many script tags with id: " + element_id
+    if not document.body.contains(script[0])
+      throw "Error injecting plot: autoload script tag may only be under <body>"
     info = script.data()
     base.Config.ws_conn_string = info['bokehConnString']
     base.Config.prefix = info['bokehRootUrl']
     container = $('<div>', {class: 'bokeh-container'})
     container.insertBefore(script)
     if info.bokehData == "static"
-      add_plot(container, info, all_models)
+      console.log " - using static data"
+      add_plot_static(container, info, all_models)
     else if info.bokehData == "server"
+      console.log " - using server data"
       add_plot_server(container, info)
-  exports = {
-    inject_plot : inject_plot,
-    add_plot_server : add_plot_server,
-    add_plot : add_plot,
-    inject_css : inject_css
+    else
+      throw "Unknown bokehData value for inject_plot: " + info.bokehData
+
+  return {
+    "inject_plot": inject_plot,
+    "add_plot_server": add_plot_server,
+    "add_plot_static": add_plot_static,
+    "inject_css": inject_css
   }
-  return exports


factored out ssh cmds into function; sqlite tests refactored to use ssh_cmd

--- integration_test/cobalt.coffee
+++ integration_test/cobalt.coffee
@@ -15,6 +15,11 @@
 sshkey_pub_path =  "../swops-secret/cotest-rsa.pub"
 sshkey_prv_path =  "../swops-secret/cotest-rsa"
 fs.chmodSync sshkey_prv_path, 0o600
+
+ssh_cmd = (cmd, callback) ->
+  ssh = "ssh #{boxname}@#{host} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey -o LogLevel=ERROR -i #{sshkey_prv_path} '#{cmd}'"
+  exec ssh, (err, stdout, stderr) ->
+    callback err, stdout, stderr

 describe 'Integration testing', ->
   describe 'When I use the http API', ->
@@ -44,20 +49,18 @@
     it 'the CORS headers are present'
   describe 'When I login to my box', ->
     it 'SSH works', (done) ->
-      cmd = "ssh #{boxname}@#{host} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey -o LogLevel=ERROR -i #{sshkey_prv_path} 'exit 99'"
-      exec cmd, (err, stdout_, stderr_) ->
-          err.code.should.equal 99
-          done()
+      ssh_cmd 'exit 99', (err) ->
+        err.code.should.equal 99
+        done()

     it 'I can see my README.md'
     it 'I can see my git repo'
     it 'I cannot see any other box'
+
   describe 'When I publish some files', ->
     before (done) ->
-      cmd = "echo -n Testing > http/index.html"
-      ssh = "ssh #{boxname}@#{host} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey -o LogLevel=ERROR -i #{sshkey_prv_path} '#{cmd}'"
-      exec ssh, (err, stdout_, stderr_) ->
-          done()
+      ssh_cmd "echo -n Testing > http/index.html", ->
+        ssh_cmd '''echo -n "{\\"database\\": \\"scraperwiki.sqlite\\"}" > scraperwiki.json''', done

     it 'I can see a root index page', (done) ->
       response = request.get "#{baseurl}/#{boxname}/http/", (err, resp, body) ->
@@ -96,26 +99,16 @@
   describe 'When I use the SQL web API', ->
     resp = null
     before (done) ->
-      j =
-        database: 'scraperwiki.sqlite'
-      options =
-        uri: "http://#{host}/#{boxname}/settings"
-        form:
-          data: JSON.stringify j
-          apikey: cobalt_api_key
-      request.post options, (err, response, body) ->
-        cmd = '''echo "create table swdata (num int); insert into swdata values (7);" |
-          sqlite3 scraperwiki.sqlite'''
-        ssh = "ssh #{boxname}@#{host} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey -o LogLevel=ERROR -i #{sshkey_prv_path} '#{cmd}'"
-        exec ssh, (err, stdout_, stderr_) ->
-          options =
-            uri: "http://#{host}/#{boxname}/sqlite"
-            qs:
-              q: "select num*num from swdata"
-          request.get options, (err, response, body) ->
-            should.not.exist err
-            resp = response
-            done()
+       ssh_cmd '''echo "create table swdata (num int); insert into swdata values (7);" |
+          sqlite3 scraperwiki.sqlite''', ->
+         options =
+           uri: "http://#{host}/#{boxname}/sqlite"
+           qs:
+             q: "select num*num from swdata"
+         request.get options, (err, response, body) ->
+           should.not.exist err
+           resp = response
+           done()

     it 'has status 200', ->
       resp.should.have.status 200


Check legacy redirects go to the right place

--- integration_test/cobalt.coffee
+++ integration_test/cobalt.coffee
@@ -236,6 +236,16 @@
               followRedirect:false
               , (err, resp, body) ->
                 resp.should.have.status 301
+                done()
+
+        it 'I am redirected to the right place for legacy URLs with / in box name', (done) ->
+            legacy_boxname = boxname.replace('.', '/')
+            request.get
+              uri: "#{baseurl}/#{legacy_boxname}/0123456789/http/index.html"
+              followRedirect:true
+              , (err, resp, body) ->
+                resp.should.have.status 200
+                body.should.equal 'Testing'
                 done()

       describe 'without a publishing token set in scraperwiki.json', ->
@@ -406,6 +416,19 @@
               resp.should.have.status 301
               done()

+      it 'redirected to the right place for legacy URLs with / in box name', (done) ->
+          legacy_boxname = boxname.replace('.', '/')
+          request.get
+            uri: "#{baseurl}/#{legacy_boxname}/0123456789/sqlite/"
+            qs: options.qs
+            followRedirect:true
+            , (err, resp, body) ->
+              resp.should.have.status 200
+              should.exist (JSON.parse resp.body)
+              (JSON.parse resp.body)[0]['num*num'].should.equal 49
+              done()
+
+

     describe 'without a publishing token set in scraperwiki.json', ->
       before (done) ->


Fix Get More Apps button issue with router

--- client/landing/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/landing/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -255,8 +255,7 @@
   click : (event)->

     return if $(event.target).closest('.icon-container').length > 0
-    @showLoader()
-    KD.getSingleton("appManager").open 'Apps', => @hideLoader()
+    KD.getSingleton('router').handleRoute "/Apps"
     KD.track "Apps", "GetMoreAppsClicked"


Read in LocalVariableTable attribute.

Read in LocalVariableTable attribute.

--- src/attributes.coffee
+++ src/attributes.coffee
@@ -99,11 +99,26 @@
     tag = bytes_array.shift()
     bytes_array.splice(0, 2) if tag == 7

+class LocalVariableTable
+  parse: (bytes_array, constant_pool) ->
+    @num_entries = util.read_uint bytes_array.splice 0, 2
+    @entries = (@parse_entries bytes_array for i in [0...@num_entries])
+    return bytes_array
+
+  parse_entries: (bytes_array) ->
+    {
+      start_pc: util.read_uint bytes_array.splice 0, 2
+      length: util.read_uint bytes_array.splice 0, 2
+      name_ref: util.read_uint bytes_array.splice 0, 2
+      descriptor_ref: util.read_uint bytes_array.splice 0, 2
+      ref: util.read_uint bytes_array.splice 0, 2
+    }
+
 root.make_attributes = (bytes_array,constant_pool) ->
   #TODO: add classes for additional attr types
   attr_types = {
     'Code': Code, 'LineNumberTable': LineNumberTable, 'SourceFile': SourceFile,
-    'StackMapTable': StackMapTable
+    'StackMapTable': StackMapTable, 'LocalVariableTable': LocalVariableTable
   }
   num_attrs = util.read_uint(bytes_array.splice(0,2))
   attrs = []


Rollback IDs

Rollback IDs

--- models/search_result.coffee
+++ models/search_result.coffee
@@ -8,7 +8,7 @@
 module.exports = class SearchResult extends Backbone.Model
   _.extend @prototype, Image(sd.SECURE_IMAGES_URL)

-  idAttribute: '_id'
+  # idAttribute: '_id'

   initialize: (options) ->
     # Make the id unique to support Backbone collection's uniqueness requirement


替换删除文件方法

替换删除文件方法

--- lib/emulator/cordova-emulate.coffee
+++ lib/emulator/cordova-emulate.coffee
@@ -1,4 +1,5 @@
 fs = require 'fs'
+fse = require 'fs-extra'
 path = require 'path'
 request = require 'request'
 Decompress = require 'decompress'
@@ -66,22 +67,13 @@

   # as same as rm -rf /folder
   router.get '/fs/rmdir', (req, res, next) ->
-    deleteFolderRecursive = (path) ->
-      console.log path
-      files = []
-      if fs.existsSync path
-        files = fs.readdirSync path

-        files.forEach (file, index) ->
-          curPath = "#{path}/#{file}"
-          if fs.statSync(curPath).isDirectory() # recurse
-            deleteFolderRecursive curPath
-          else # delete file
-            fs.unlinkSync curPath
-
-        fs.rmdirSync path
-
-    deleteFolderRecursive req.rpath
-    res.status(200).json
-      err: null
-      result: true
+    fse.remove req.rpath, (err) ->
+      if err
+        res.status(500).json
+          err: err
+          result: false
+      else
+        res.status(200).json
+          err: null
+          result: true


Updated app retrieval webservice (for server-side/client-side management)

--- src/presentationLayer/api/index.coffee
+++ src/presentationLayer/api/index.coffee
@@ -38,7 +38,12 @@
 				(cb) => env.data.apps.getBackend req.params.key, cb
 			], (e, r) =>
 				return next(e) if e
-				res.send name:r[0].name, key:r[0].key, secret:r[0].secret, owner:r[0].owner, date:r[0].date, domains:r[1], keysets:r[2], backend:r[3], server_side: server_side_only
+				app = r[0]
+				delete app.id
+				app.domains = r[1]
+				app.keysets = r[2]
+				app.backend = r[3]
+				res.send app
 				next()

 		# update infos of an app


Match prod config in staging to avoid alerts

--- config/main.staging.coffee
+++ config/main.staging.coffee
@@ -124,7 +124,7 @@
     numberOfWorkers: 2
   social        :
     login       : 'prod-social'
-    numberOfWorkers: 4
+    numberOfWorkers: 7
     watch       : yes
     queueName   : socialQueueName
     verbose     : no
@@ -203,11 +203,11 @@
     logLevel    : "info"
   broker        :
     ip          : ""
-    port        : 80
-    certFile    : ""
-    keyFile     : ""
-    webProtocol : 'http:'
-    webHostname : "stage-broker-#{version}.sj.koding.com"
+    port        : 443
+    certFile    : "/opt/ssl_certs/wildcard.koding.com.cert"
+    keyFile     : "/opt/ssl_certs/wildcard.koding.com.key"
+    webProtocol : 'https:'
+    webHostname : "broker-#{version}.sj.koding.com"
     webPort     : null
     authExchange: authExchange
     authAllExchange: authAllExchange


Add listener on screen-lines-changed instead of contents-modified

--- lib/minimap-git-diff-binding.coffee
+++ lib/minimap-git-diff-binding.coffee
@@ -14,7 +14,7 @@

   activate: ->
     @subscribe @editorView, 'editor:path-changed', @subscribeToBuffer
-    @subscribe @editorView, 'editor:contents-modified', @renderDiffs
+    @subscribe @editorView, 'editor:screen-lines-changed', @renderDiffs
     @subscribe atom.project.getRepo(), 'statuses-changed', =>
       @scheduleUpdate()
     @subscribe atom.project.getRepo(), 'status-changed', (path) =>


Jake fixed the schema

Jake fixed the schema

--- src/scripts/services/webService.coffee
+++ src/scripts/services/webService.coffee
@@ -2,13 +2,6 @@

 module.exports = (m) ->
 	m.factory 'webService', ($http) ->
-
-		fixLeasterSchema = (hand) ->
-			return hand unless hand.handType is 'leaster'
-			hand = _.extend {}, hand
-			hand.score = _.extend {}, hand.score
-			hand.score.secondaryPlayerIndex ?= hand.score.primaryPlayerIndex
-			return hand

 		getNames: (value) ->
 			$http.get remote('sheepshead.cgi'), params: { action: 'name-lookup', query: value }
@@ -18,5 +11,5 @@
 				action: 'submit-hands'
 			data =
 				playerNames: players.map (p) -> p.name
-				hands: (fixLeasterSchema hand for hand in hands) #stupid server schema design
+				hands: hands
 			$http.post remote('sheepshead.cgi'), data, { params }


当应用创建位置栏为空时…添加默认目录

当应用创建位置栏为空时…添加默认目录

--- lib/project/new-project-info.coffee
+++ lib/project/new-project-info.coffee
@@ -57,10 +57,13 @@
     @element

   getProjectInfo: ->
+    path = @appPath.getText().trim()
+    dir = new Directory(path)
+    path = pathM.join desc.newProjectDefaultPath,dir.getBaseName() if dir.getParent().isRoot() is yes
     projectInfo =
-      appId : @appId.getText();
-      appName : @appName.getText();
-      appPath : @appPath.getText();
+      appId : @appId.getText()
+      appName : @appName.getText()
+      appPath : path

     console.log projectInfo
     projectInfo
@@ -95,12 +98,15 @@
       dir = new Directory(path);
       dir.exists()
         .then (isExists) =>
-          # console.log isExists,@,dir.getRealPathSync()
+          console.log isExists,dir.getRealPathSync()
           unless isExists
             @errorMsg.addClass('hide')
+
           else
             @errorMsg.removeClass('hide')
           @checkInput()
+    else
+      console.log 'empty'

   nextStep:(box) ->
     # console.log box


tokentransaciton bps

tokentransaciton bps

--- src/blueprints.coffee
+++ src/blueprints.coffee
@@ -115,6 +115,11 @@
   url:    (x) -> "/user/#{x.id ? x}/transactions"
   expects:  statusOk

+blueprints.user.tokentransactions =
+  method: 'GET'
+  url:    (x) -> "/user/#{x.id ? x}/tokentransactions"
+  expects:  statusOk
+
 blueprints.user.referrals =
   method: 'GET'
   url:    (x) -> "/user/#{x.id ? x}/referrals"


Quick fix to floating block bug

Quick fix to floating block bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -410,7 +410,7 @@
 Editor::resizeNubby = ->
   @setTopNubbyStyle @nubbyHeight, @nubbyColor

-Editor::drawFloatingBlock = (record, startWidth, endWidth) ->
+Editor::drawFloatingBlock = (record, startWidth, endWidth, rect, opts) ->
   blockView = @view.getViewNodeFor record.block
   blockView.layout record.position.x, record.position.y

@@ -525,7 +525,8 @@
     # Draw floating blocks
     startWidth = @mainCtx.measureText(@mode.startComment).width
     endWidth = @mainCtx.measureText(@mode.endComment).width
-    @drawFloatingBlock(record, startWidth, endWidth) for record in @floatingBlocks
+    for record in @floatingBlocks
+      @drawFloatingBlock(record, startWidth, endWidth, rect, opts)

     if opts.boundingRectangle?
       @mainCtx.restore()


Fix palette highlight position and empty header.

When the palette is resized (after changing the palette header),
the palette highlight canvas should also be resized, so the
highlight outlines are positioned correctly.

Also, if the palette header has only a single group and it
has no name, then the palette header is formatted so that
the palette header is not visible.

--- src/controller.coffee
+++ src/controller.coffee
@@ -1461,6 +1461,9 @@
         paletteHeaderRow = document.createElement 'div'
         paletteHeaderRow.className = 'droplet-palette-header-row'
         @paletteHeader.appendChild paletteHeaderRow
+        # hide the header if there is only one group, and it has no name.
+        if @paletteGroups.length is 1 and paletteGroup.name is ''
+          paletteHeaderRow.style.height = 0

       # Create the element itself
       paletteGroupHeader = document.createElement 'div'
@@ -1520,6 +1523,7 @@
         do updatePalette

     @resizePalette()
+    @resizePaletteHighlight()

   # The next thing we need to do with the palette
   # is let people pick things up from it.


Fix palette layout for even number of entries.

--- src/controller.coffee
+++ src/controller.coffee
@@ -33,7 +33,7 @@
       obj[key] = value

     return obj
-
+
   deepCopy = (a) ->
     if a instanceof Object
       newObject = {}
@@ -42,7 +42,7 @@
         newObject[key] = deepCopy val

       return newObject
-
+
     else
       return a

@@ -51,7 +51,7 @@
       for own key, val of a
         unless deepEquals b[key], val
           return false
-
+
       for own key, val of b when not key of a
         unless deepEquals a[key], val
           return false
@@ -120,7 +120,7 @@

       # Append that div.
       @wrapperElement.appendChild @iceElement
-
+
       @wrapperElement.style.backgroundColor = '#FFF'

       # ### Tracker
@@ -226,7 +226,7 @@
           mousedown: [@iceElement, @paletteElement, @dragCover]
           mouseup: [window]
           mousemove: [window] } then do (eventName, elements) =>
-
+
         for element in elements
           element.addEventListener eventName, (event) =>
             trackPoint = @getPointRelativeToTracker event
@@ -238,7 +238,7 @@
             # Call all the handlers.
             for handler in editorBindings[eventName]
               handler.call this, trackPoint, event, state
-
+
             # Stop event propagation so that
             # we don't get bad selections
             event.stopPropagation?()
@@ -364,7 +364,7 @@
       @clearMain(opts)

       @topNubbyPath.draw @mainCtx
-
+
       if opts.boundingRectangle?
         @mainCtx.save()
         opts.boundingRectangle.clip @mainCtx
@@ -401,7 +401,7 @@
         path.draw @highlightCtx
       else
         delete @markedLines[line]
-
+
     for id, info of @extraMarks
       if @inTree info.model
         path = @getHighlightPath info.model, info.style
@@ -508,7 +508,7 @@

       x -= @iceElement.offsetLeft
       y -= @iceElement.offsetTop
-
+
       while el?
         x -= el.offsetLeft - el.scrollLeft
         y -= el.offsetTop - el.scrollTop
@@ -570,7 +570,7 @@

     # Nope, it's not. Answer is null.
     else return null
-
+
   hook 'mousedown', 10, ->
     @iceElement.focus()

@@ -597,7 +597,7 @@
   # and might possibly do some bureaucracy in the future.
   Editor::addMicroUndoOperation = (operation) ->
     @undoStack.push operation
-
+
     # If someone has bound to mutation via
     # the public API, fire it.
     @fireEvent 'change', [operation]
@@ -880,7 +880,7 @@
         point.x + @draggingOffset.x,
         point.y + @draggingOffset.y
       )
-
+
       # Construct a quadtree of drop areas
       # for faster dragging
       @dropPointQuadTree = QUAD.init
@@ -907,7 +907,7 @@
                 h: 0
                 _ice_needs_shift: not @canDrop @draggingBlock, head.container
                 _ice_node: head.container
-
+
         head = head.next

       @dragCanvas.style.top = "#{position.y + getOffsetTop(@iceElement)}px"
@@ -936,7 +936,7 @@
       mainPoint = @trackerPointToMain(position)

       best = null; min = Infinity
-
+
       # Find the closest droppable block
       testPoints = @dropPointQuadTree.retrieve {
         x: mainPoint.x - MAX_DROP_DISTANCE
@@ -951,7 +951,7 @@
              @view.getViewNodeFor(point._ice_node).highlightArea?
             best = point._ice_node
             min = distance
-
+
       if best isnt @lastHighlight
         @clearHighlightCanvas()

@@ -961,7 +961,7 @@

   hook 'mouseup', 0, ->
     clearTimeout @discourageDropTimeout; @discourageDropTimeout = null
-
+
   Editor::canDrop = (drag, drop) ->
     unless drop? then return false
     unless @view.getViewNodeFor(drop).dropPoint? then return false
@@ -975,7 +975,7 @@
         return drop.accepts drag
     else
       return drag.socketLevel in [ANY_DROP, MOSTLY_BLOCK, BLOCK_ONLY]
-
+
   Editor::discourageDrop = (drag, drop) ->
     unless drop? then return false

@@ -985,7 +985,7 @@
     else
       return drag.socketLevel in [MOSTLY_VALUE]

-
+
   hook 'mouseup', 1, (point, event, state) ->
     # We will consume this event iff we dropped it successfully
     # in the root tree.
@@ -1087,7 +1087,7 @@
       block = block.parent

     return block is @tree
-
+
   # We can create floating blocks by dropping
   # blocks without a highlight.
   hook 'mouseup', 0, (point, event, state) ->
@@ -1129,7 +1129,7 @@
         @clearDrag()
         @redrawMain()
         return
-
+
       else if renderPoint.x - @scrollOffsets.main.x < 0
         renderPoint.x = @scrollOffsets.main.x

@@ -1219,14 +1219,15 @@
     # Append the element.
     @paletteWrapper.appendChild @paletteHeader

-    # Keep track of the row that we are on
-    # in our table layout
-    paletteHeaderRow = document.createElement 'div'
-    paletteHeaderRow.className = 'ice-palette-header-row'
-
-    @paletteHeader.appendChild paletteHeaderRow
-
+    paletteHeaderRow = null
     for paletteGroup, i in @paletteGroups then do (paletteGroup) =>
+      # Start a new row, if we're at that point
+      # in our appending cycle
+      if i % 2 is 0
+        paletteHeaderRow = document.createElement 'div'
+        paletteHeaderRow.className = 'ice-palette-header-row'
+        @paletteHeader.appendChild paletteHeaderRow
+
       # Clone all the blocks so as not to
       # intrude on outside stuff
       paletteGroup.blocks = (block.clone() for block in paletteGroup.blocks)
@@ -1240,13 +1241,6 @@

       paletteHeaderRow.appendChild paletteGroupHeader

-      # Start a new row, if we're at that point
-      # in our appending cycle
-      if i % 2 is 1
-        paletteHeaderRow = document.createElement 'div'
-        paletteHeaderRow.className = 'ice-palette-header-row'
-        @paletteHeader.appendChild paletteHeaderRow
-
       # When we click this element,
       # we should switch to it in the palette.
       clickHandler = =>
@@ -1419,7 +1413,7 @@
         if head.type is 'newline' then line++

       treeView = @view.getViewNodeFor @tree
-
+
       oldp = deepCopy [
         treeView.glue[line - 1],
         treeView.glue[line],
@@ -1433,7 +1427,7 @@
         treeView.glue[line],
         treeView.bounds[line].height
       ]
-
+
       # If the layout has not changed enough to affect
       # anything non-local, only redraw locally.
       if deepEquals newp, oldp
@@ -1448,7 +1442,7 @@
           boundingRectangle: rect

       else @redrawMain()
-
+
     # Otherwise, redraw the whole thing
     else
       @redrawMain()
@@ -1516,7 +1510,7 @@
         parseParent = @textFocus.parent

         newParse = coffee.parse(parseParent.stringify(), wrapAtRoot: false)
-
+
         if newParse.start.next?.container?.end is newParse.end.prev
           if focus is null
             newParse = newParse.start.next
@@ -2034,7 +2028,7 @@
       if head?
         @setTextInputFocus head.container, -1, -1
       return false
-
+
     else
       if @textFocus? then head = @textFocus.end
       else head = @cursor
@@ -2120,7 +2114,7 @@
       @redrawMain()
       @reparseHandwrittenBlocks()
       @setTextInputFocus newSocket
-
+
     else if @textFocus? then @setTextInputFocus null; @redrawMain()

   containsCursor = (block) ->
@@ -2196,7 +2190,7 @@

   # INDENT CREATE/DESTROY SUPPORT
   # ================================
-
+
   ###
   # CreateIndent undo operation
   class CreateIndentOperation extends UndoOperation
@@ -2480,7 +2474,7 @@
         translatingElements.push div

         @transitionContainer.appendChild div
-
+
         do (div, textElement, translationVectors, i) =>
           setTimeout (=>
             div.style.left = (textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x) + 'px'
@@ -2506,21 +2500,21 @@
         div.style.font = @fontSize + 'px ' + @fontFamily
         div.style.width = "#{@gutter.offsetWidth}px"
         translatingElements.push div
-
+
         div.className = 'ice-transitioning-element ice-transitioning-gutter'
         div.style.transition = "left #{translateTime}ms, top #{translateTime}ms"

         @mainScrollerStuffing.appendChild div
-
+
         do (div, line) =>
           # Set off the css transition
           setTimeout (=>
             div.style.left = '0px'
             div.style.top = (line * lineHeight - aceScrollTop + @scrollOffsets.main.y) + 'px'
           ), fadeTime
-
+
       @lineNumberWrapper.style.display = 'none'
-
+
       # Kick off fade-out transition

       @paletteWrapper.style.transition  =
@@ -2535,7 +2529,7 @@
         @iceElement.style.transition = "left #{translateTime}ms"
         @iceElement.style.left = '0px'
       ), fadeTime
-
+
       setTimeout (=>
         # Translate the ICE editor div out of frame.
         @iceElement.style.transition = ''
@@ -2559,7 +2553,7 @@

         for div in translatingElements
           div.parentNode.removeChild div
-
+
         if cb? then do cb
       ), fadeTime + translateTime

@@ -2572,7 +2566,7 @@

       unless setValueResult.success
         return setValueResult
-
+
       if @aceEditor.getFirstVisibleRow() is 0
         @mainScroller.scrollTop = 0
       else
@@ -2624,7 +2618,7 @@
           translatingElements.push div

           @transitionContainer.appendChild div
-
+
           do (div, textElement) =>
             setTimeout (=>
               div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x}px"
@@ -2656,7 +2650,7 @@
           translatingElements.push div

           @mainScrollerStuffing.appendChild div
-
+
           do (div, line) =>
             setTimeout (=>
               div.style.left = 0
@@ -2674,7 +2668,7 @@

         @iceElement.style.transition = "left #{translateTime}ms"
         @iceElement.style.left = "#{@paletteWrapper.offsetWidth}px"
-
+
         setTimeout (=>
           @iceElement.style.transition = ''

@@ -2685,7 +2679,7 @@

           for div in translatingElements
             div.parentNode.removeChild div
-
+
           @resize()
           if cb? then do cb
         ), translateTime + fadeTime
@@ -2801,7 +2795,7 @@
       @dragView.clearCache()

       @gutter.style.width = @aceEditor.renderer.$gutterLayer.gutterWidth + 'px'
-
+
       @redrawMain(); @redrawPalette()

   Editor::setFontFamily = (fontFamily) ->
@@ -2917,10 +2911,10 @@
       mainPoint = @trackerPointToMain point

       treeView = @view.getViewNodeFor @tree
-
+
       if @lastHoveredLine? and treeView.bounds[@lastHoveredLine].contains mainPoint
         return
-
+
       hoveredLine = @findLineNumberAtCoordinate point.y

       unless treeView.bounds[hoveredLine].contains mainPoint
@@ -2967,16 +2961,16 @@
     @redrawMain()

     return success: true
-
+
   Editor::setValue = (value) ->
-
+
     oldScrollTop = @aceEditor.session.getScrollTop()

     @aceEditor.setValue value, -1
     @aceEditor.resize true
-
+
     @aceEditor.session.setScrollTop oldScrollTop
-
+
     @setValue_raw value

   Editor::getValue = -> if @currentlyUsingBlocks then @tree.stringify() else @aceEditor.getValue()
@@ -3028,7 +3022,7 @@

       @aceEditor.setValue @getValue(), -1
       @aceEditor.resize true
-
+
       @aceEditor.session.setScrollTop oldScrollTop

       @iceElement.style.top = @iceElement.style.left =
@@ -3106,7 +3100,7 @@
   hook 'populate', 0, ->
     @touchScrollAnchor = new draw.Point 0, 0
     @lassoSelectStartTimeout = null
-
+
     @wrapperElement.addEventListener 'touchstart', (event) =>
       clearTimeout @lassoSelectStartTimeout

@@ -3229,7 +3223,7 @@

         @clearDrag()
         @redrawMain()
-
+
   # LINE NUMBER GUTTER CODE
   # ================================
   hook 'populate', 0, ->
@@ -3286,7 +3280,7 @@
       pivot = Math.floor (start + end) / 2

     return pivot
-
+
   hook 'redraw_main', 0, (changedBox) ->
     treeView = @view.getViewNodeFor @tree

@@ -3303,7 +3297,7 @@

     if changedBox
       @gutter.style.height = "#{Math.max @mainScroller.offsetHeight, treeView.totalBounds.height}px"
-
+
   ###
   getFontHeight = (family, size) ->
     testElement = document.createElement 'span'
@@ -3386,7 +3380,7 @@
         descent: gp.bottom
       fontMetricsCache[fontStyle] = result
     return result
-
+
   getFontHeight = (family, size) ->
     metrics = fontMetrics family, size
     return metrics.descent - metrics.ascent


Re-throw the same exceptions where possible.

--- js2py.coffee
+++ js2py.coffee
@@ -126,6 +126,8 @@
             body.unshift c
             return c.id

+  catchClauses = [] # TODO make this more precise
+
   estraverse.replace c,
     enter: (c, parent) ->
       switch c.type
@@ -274,6 +276,9 @@
               property: { type: 'Identifier', name: 'lower' }
             }), @)
             when_(match.any, (->), @))
+        when 'CatchClause'
+          catchClauses.push(c)
+          return

     leave: (c, parent) ->
       switch c.type
@@ -397,6 +402,14 @@
               callee: { type: 'Identifier', name: 'isinstance' }
               arguments: [c.left, right]
             }
+        when 'ThrowStatement'
+          if (lastCatch = catchClauses[catchClauses.length - 1]) and
+             JSON.stringify(lastCatch.param) == JSON.stringify(c.argument)
+            c.argument = null
+          return
+        when 'CatchClause'
+          catchClauses.pop()
+          return
   c

 ensure_block = (c) ->
@@ -482,7 +495,7 @@
       when 'CallExpression', 'NewExpression'
         "#{walk c.callee}(#{c.arguments.map(walk).join ', '})"
       when 'ThrowStatement'
-        p.addLine "raise #{walk c.argument}"
+        p.addLine "raise #{(walk c.argument) ? ''}"
       when 'TryStatement'
         p.addLine "try:"
         walk c.block


Fix all the arrow key cursor stuff

Fix all the arrow key cursor stuff

--- src/controller.coffee
+++ src/controller.coffee
@@ -2253,6 +2253,10 @@
       @hiddenInput.blur()
       @dropletElement.focus()

+  # Abort if there is no destination (usually means
+  # someone wants to travel outside the document)
+  return unless destination?
+
   # Now set the new cursor
   if destination instanceof model.Location
     destination = @tree.getFromLocation destination
@@ -2261,6 +2265,7 @@

   until @validCursorPosition(destination) and validate(destination)
     destination = (if direction is 'after' then destination.next else destination.prev)
+    return unless destination?

   @cursor = destination.getLocation()

@@ -2313,27 +2318,36 @@

 # Pressing the up-arrow moves the cursor up.
 hook 'keydown', 0, (event, state) ->
-  if event.which isnt UP_ARROW_KEY then return
-  @clearLassoSelection()
-  prev = @getCursor().prev ? @getCursor().start.prev
-  @setCursor prev, ((token) -> token.type isnt 'socketStart'), 'before'
-  @scrollCursorIntoPosition()
-
-# Pressing the down-arrow moves the cursor down.
-hook 'keydown', 0, (event, state) ->
-  if event.which isnt DOWN_ARROW_KEY then return
-  @clearLassoSelection()
-  next = @getCursor().next ? @getCursor().end.next
-  @setCursor next, ((token) -> token.type isnt 'socketStart'), 'after'
-  @scrollCursorIntoPosition()
+  if event.which is UP_ARROW_KEY
+    @clearLassoSelection()
+    prev = @getCursor().prev ? @getCursor().start.prev
+    @setCursor prev, ((token) -> token.type isnt 'socketStart'), 'before'
+    @scrollCursorIntoPosition()
+  else if event.which is DOWN_ARROW_KEY
+    @clearLassoSelection()
+    next = @getCursor().next ? @getCursor().end.next
+    @setCursor next, ((token) -> token.type isnt 'socketStart'), 'after'
+    @scrollCursorIntoPosition()
+  else if event.which is RIGHT_ARROW_KEY
+    @clearLassoSelection()
+    next = @getCursor().next ? @getCursor().end.next
+    @setCursor next, null, 'after'
+    @scrollCursorIntoPosition()
+  else if event.which is LEFT_ARROW_KEY
+    @clearLassoSelection()
+    prev = @getCursor().prev ? @getCursor().start.prev
+    @setCursor prev, null, 'before'
+    @scrollCursorIntoPosition()

 hook 'keydown', 0, (event, state) ->
   if event.which isnt TAB_KEY then return

   if event.shiftKey
-    @setCursor @cursor.next, ((token) -> token.type is 'socketStart'), 'before'
+    prev = @getCursor().prev ? @getCursor().start.prev
+    @setCursor prev, ((token) -> token.type is 'socketStart'), 'before'
   else
-    @setCursor @cursor.next, ((token) -> token.type is 'socketStart'), 'after'
+    next = @getCursor().next ? @getCursor().end.next
+    @setCursor next, ((token) -> token.type is 'socketStart'), 'after'
   event.preventDefault()

 Editor::deleteAtCursor = ->


1. 改进绑定

1. 改进绑定

--- src/core/model.coffee
+++ src/core/model.coffee
@@ -639,7 +639,11 @@
 					break
 				child = child.parent
 				i++
-		return item
+
+		if @items is @originItems
+			return item
+		else
+			return if item then true else null

 	_processMessage: (bindingPath, path, type, arg)->
 		if @onMessage?(path, type, arg) is false
@@ -664,7 +668,12 @@
 			else
 				entity = @findRelativeItem(arg.entity)
 				if entity
-					@refreshItem(entity: entity)
+					if entity instanceof cola.Entity
+						@refreshItem(entity: entity)
+					else
+						@retrieveData()
+					@refreshItems()
+					allProcessed = true
 				else
 					processMoreMessage = true

@@ -713,8 +722,8 @@
 			if @isParentOfTarget(@expressionPaths, path) then @itemsLoadingEnd(arg)

 		if processMoreMessage and @expression
-			if @expression.hasComplexStatement
-				cola.util.delay(@, "retrieve", 100, ()=>
+			if @expression.hasComplexStatement # and not @expressionPaths? and not @expression.hasDefinedPath
+				cola.util.delay(@, "retrieve", 100, ()->
 					@retrieveData()
 					@refreshItems()
 					return


Added logic to get parameters from Box

Added logic to get parameters from Box

--- routes/templatesRoute.coffee
+++ routes/templatesRoute.coffee
@@ -8,7 +8,8 @@
     data: ->
         privateTemplates: Template.find({owner: Meteor.userId()}, {sort: {name: 1}})
     onBeforeAction: ->
-        console.log(this.params.query.post_params)
+        console.log(this.params)
+        @next()

   @route 'managePrivate',


Events.trigger: Set a info.promise.

Events.trigger: Set a info.promise.

This Promise is resolved or rejected when the triggered event is done.

--- src/base/Events/Events.coffee
+++ src/base/Events/Events.coffee
@@ -192,6 +192,8 @@
 		info = event.getInfo()
 		waits = []
 		info.__waits = waits
+		waits_dfr = new CUI.Deferred()
+		info.promise = waits_dfr.promise()

 		# use the standard event system for this kind of events
 		bubble = event.isBubble()
@@ -265,7 +267,8 @@
 					# CUI.debug "adding stop node", listener_node[0]
 					stopNodes.push(listener_node[0])

-		return CUI.when(waits)
+		CUI.when(waits).done(waits_dfr.resolve).fail(waits_dfr.reject)
+		return info.promise

 	@ignore: (filter, doc=document) -> # , debug=false) ->


add settings to beautifier

add settings to beautifier

--- src/beautifiers/phpcbf.coffee
+++ src/beautifiers/phpcbf.coffee
@@ -14,6 +14,8 @@
         if (standard) then \
           standard else "PEAR"
       ]
+      phpcbf_path: (phpcbf_path) -> phpcbf_path or @which('phpcbf')
+
     PHP: true
   }


Added help via chat

Added help via chat

--- src/scripts/diagnostics.coffee
+++ src/scripts/diagnostics.coffee
@@ -14,6 +14,46 @@
 HEADERS = constants.HEADERS

 Organization = require('../models/organization').get()
+
+helpEnglish = "*Ibizan Help*\n
+              \n
+              To clock in with Ibizan, either @mention him in a public
+              channel, use the slash command, or DM him directly without
+              the @mention. Your command should follow this format:\n
+              \n
+              `@ibizan [mode] [time] [date] [project] [notes]`\n
+              \n
+              Examples:\n
+              @ibizan in\n
+              @ibizan out\n
+              @ibizan in at 9:15\n
+              @ibizan out 4:30p yesterday\n
+              @ibizan in #project-name\n
+              @ibizan 3.5 hours today\n
+              \n
+              Punches can be `in`, `out`, `vacation`, `sick` or `unpaid`
+              punches. You can also clock in independent blocks of time.
+              Projects must be registered in the worksheet labeled
+              'Projects' in the Ibizan spreadsheet, and won't be recognized
+              as projects in a command without a pound-sign
+              (i.e. #fight-club).\n
+              \n
+              If something is wrong with your punch, you can undo it by
+              doing `@ibizan undo`. You can also modify it manually using
+              the Ibizan spreadsheet, in the worksheet labeled 'Raw Data'.
+              If you want to see how much time you've worked today, do
+              `@ibizan today?`.\n
+              \n
+              If you make any manual changes to the spreadsheet, you should
+              run `/sync` or `@ibizan sync`. Running this resyncs Ibizan with
+              the spreadsheet.\n
+              \n
+              Due to some existing limitations in Google Sheets, changes
+              made directly to the spreadsheet are not immediately
+              reflected by Ibizan and must be followed up with a `/sync`.\n
+              \n
+              For more documentation, please check out
+              https://github.com/ibizan/ibizan.github.io/wiki"

 module.exports = (robot) ->

@@ -162,46 +202,14 @@
     body = req.body
     if body.token is process.env.SLASH_HELP_TOKEN
       res.status 200
-      response = "Ibizan Help\n
-                  \n
-                  To clock in with Ibizan, either @mention him in a public
-                  channel, use the slash command, or DM him directly without
-                  the @mention. Your command should follow this format:\n
-                  \n
-                  `@ibizan [mode] [time] [date] [project] [notes]`\n
-                  \n
-                  Examples:\n
-                  @ibizan in\n
-                  @ibizan out\n
-                  @ibizan in at 9:15\n
-                  @ibizan out 4:30p yesterday\n
-                  @ibizan in #project-name\n
-                  @ibizan 3.5 hours today\n
-                  \n
-                  Punches can be `in`, `out`, `vacation`, `sick` or `unpaid`
-                  punches. You can also clock in independent blocks of time.
-                  Projects must be registered in the worksheet labeled
-                  'Projects' in the Ibizan spreadsheet, and won't be recognized
-                  as projects in a command without a pound-sign
-                  (i.e. #fight-club).\n
-                  \n
-                  If something is wrong with your punch, you can undo it by
-                  doing `@ibizan undo`. You can also modify it manually using
-                  the Ibizan spreadsheet, in the worksheet labeled 'Raw Data'.
-                  If you want to see how much time you've worked today, do
-                  `@ibizan today?`.\n
-                  \n
-                  If you make any manual changes to the spreadsheet, you should
-                  run `/sync`. Running this resyncs Ibizan with the spreadsheet.
-                  Due to some existing limitations in Google Sheets, changes
-                  made directly to the spreadsheet are not immediately
-                  reflected by Ibizan and must be followed up with a `/sync`.\n
-                  \n
-                  For more documentation, please check out
-                  https://github.com/ibizan/ibizan.github.io/wiki"
-    else
-      res.status 401
-      response = "Bad token in Ibizan configuration"
-    res.json {
-      "text": response
-    }
+      response = helpEnglish
+    else
+      res.status 401
+      response = "Bad token in Ibizan configuration"
+    res.json {
+      "text": response
+    }
+
+  robot.respond /help/i, (res) ->
+    user = Organization.getUserBySlackName res.message.user.name
+    user.directMessage helpEnglish, Logger


Make redis-brain play nicely with a boxen'd redis

--- src/scripts/redis-brain.coffee
+++ src/scripts/redis-brain.coffee
@@ -18,7 +18,7 @@

 # sets up hooks to persist the brain into redis.
 module.exports = (robot) ->
-  info   = Url.parse process.env.REDISTOGO_URL || 'redis://localhost:6379'
+  info   = Url.parse process.env.REDISTOGO_URL || process.env.BOXEN_REDIS_URL || 'redis://localhost:6379'
   client = Redis.createClient(info.port, info.hostname)

   if info.auth


Fix #2152: Cast opened paths to string

Fix #2152: Cast opened paths to string

path.resolve() was throwing an exception if given path was not a string.
When iterating through all given paths, each entry is casted by JS to
most suitable type and paths in form of a number (i.e. '1234') were
cast to Number, hence the bug.

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -42,7 +42,7 @@
     app.removeListener 'open-url', addUrlToOpen

     args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
-      path.resolve(args.executedFrom ? process.cwd(), "#{pathToOpen}")
+      path.resolve(args.executedFrom ? process.cwd(), pathToOpen.toString())

     require('coffee-script').register()
     if args.devMode


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/point.coffee
+++ test/point.coffee
@@ -138,6 +138,8 @@
   test "#toString", ->
     p = Point(7, 5)
     ok eval(p.toString()).equal(p)
+
+  test "

   test ".centroid", ->
     centroid = Point.centroid(


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/point.coffee
+++ test/point.coffee
@@ -141,7 +141,7 @@

   test "#clamp", ->
     p = Point(10, 10)
-    p.
+    p.clamp

   test ".centroid", ->
     centroid = Point.centroid(


Sorting functions by name/arity in autocomplete

--- lib/elixir-autocomplete-provider.coffee
+++ lib/elixir-autocomplete-provider.coffee
@@ -142,7 +142,7 @@

   createSuggestionForFunction = (serverSuggestion, name, kind, signature, mod, desc, spec, prefix, pipeBefore, captureBefore) ->
     args = signature.split(',')
-    [func, arity] = name.split('/')
+    [_, func, arity] = name.match(/(.+)\/(\d+)/)
     [moduleParts..., postfix] = prefix.split('.')

     params = []
@@ -191,6 +191,8 @@
     description = markdownToHTML(description)

     {
+      func: func
+      arity: arity
       snippet: snippet
       displayText: displayText
       type: type
@@ -244,7 +246,7 @@
       "http://elixir-lang.org/docs/v#{ELIXIR_VERSION}/elixir/#{module}.html\##{func}/#{arity}"

   sortSuggestions = (suggestions) ->
-    sort_kind = (a, b) ->
+    sortKind = (a, b) ->
       priority =
         exception: 0 # unknown
         property:  0 # module attribute
@@ -256,13 +258,24 @@

       priority[a.type] - priority[b.type]

-    sort_text = (a, b) ->
+    sortText = (a, b) ->
       if a.displayText > b.displayText then 1 else if a.displayText < b.displayText then -1 else 0

-    sort_func = (a, b) ->
-      sort_kind(a,b) || sort_text(a, b)
-
-    suggestions.sort(sort_func)
+    isFunc = (suggestion) ->
+      !!(suggestion.func)
+
+    sortFunctionByName = (a, b) ->
+      return 0 if !isFunc(a) || !isFunc(b)
+      if a.func > b.func then 1 else if a.func < b.func then -1 else 0
+
+    sortFunctionByArity = (a, b) ->
+      return 0 if !isFunc(a) || !isFunc(b)
+      if a.arity > b.arity then 1 else if a.arity < b.arity then -1 else 0
+
+    sortFunc = (a, b) ->
+      sortKind(a, b) || sortFunctionByName(a, b) || sortFunctionByArity(a, b) || sortText(a, b)
+
+    suggestions.sort(sortFunc)

   markdownToHTML = (mdText) ->
     marked.setOptions({


ownprofile: link to static profile should open in new window

--- client/app/Applications/Members.kdapplication/ContentDisplays/staticprofilesettings.coffee
+++ client/app/Applications/Members.kdapplication/ContentDisplays/staticprofilesettings.coffee
@@ -48,6 +48,6 @@
     {{> @staticPageSwitch}}

-    Visit your Public Page
+    Visit your Public Page

     """


Added a few tests for before_alls, and the template for the other four hooks.

--- test/suite.coffee
+++ test/suite.coffee
@@ -150,6 +150,7 @@
           test_count_b.should.equal(0)
           test_count_c.should.equal(1)

+  # A suite
   describe 'with a before_all', ->

     before_each ->
@@ -171,3 +172,55 @@
           'suite.before_all'
           'suite.test'
         ])
+
+    # A suite
+    # with a before_all
+    describe 'and a subsuite', ->
+      subsuite = null
+      before_each ->
+        subsuite = new Suite()
+        suite.add_suite subsuite
+
+      it 'should not run anything with no tests', ->
+        suite.run()
+        run_log.should.eql([])
+
+      describe 'with tests', ->
+        before_each ->
+          subsuite.add_test new Test -> run_log.push 'subsuite.test_a'
+          subsuite.add_test new Test -> run_log.push 'subsuite.test_b'
+
+        it 'should run before_all then the subsuite tests', ->
+          suite.run()
+          run_log.should.eql([
+            'suite.before_all'
+            'subsuite.test_a'
+            'subsuite.test_b'
+          ])
+
+  # A suite
+  describe 'with a before_each', ->
+    before_each ->
+      suite.add_before_each new Runner -> run_log.push 'suite.before_each'
+
+    it 'should not be run with no tests', ->
+      suite.run()
+      run_log.should.eql([])
+
+  # A suite
+  describe 'with an after_all', ->
+    before_each ->
+      suite.add_after_all new Runner -> run_log.push 'suite.after_all'
+
+    it 'should not be run with no tests', ->
+      suite.run()
+      run_log.should.eql([])
+
+  # A suite
+  describe 'with an after_each', ->
+    before_each ->
+      suite.add_after_each new Runner -> run_log.push 'suite.after_each'
+
+    it 'should not be run with no tests', ->
+      suite.run()
+      run_log.should.eql([])


fix #2820 -- issue with jupyter kernel starting -- by listing for iopub or shell messages (not just iopub) to know when started.  I hope this is OK.

--- src/smc-project/jupyter/jupyter.coffee
+++ src/smc-project/jupyter/jupyter.coffee
@@ -162,9 +162,12 @@
                     @emit('execution_state', mesg.content?.execution_state)
                 @emit('iopub', mesg)

-            @once 'iopub', (m) =>
+            start_running = (m) =>
+                @removeListener('iopub', start_running)
+                @removeListener('shell', start_running)
+
                 # first iopub message from the kernel means it has started running
-                dbg("iopub: #{misc.to_json(m)}")
+                dbg("start_running: #{misc.to_json(m)}")
                 # We still wait a few ms, since otherwise -- especially in testing --
                 # the kernel will bizarrely just ignore first input.
                 # TODO: I think this a **massive bug** in Jupyter (or spawnteract or ZMQ)...
@@ -173,6 +176,9 @@
                     for cb in @_spawn_cbs
                         cb?()
                 setTimeout(f, 100)
+
+            @once('iopub', start_running)
+            @once('shell', start_running)

             kernel.spawn.on('close', @close)

@@ -190,7 +196,7 @@
                 max_delay   : 5000
                 factor      : 1.4
                 max_time    : 2*60000  # long in case of starting many at once -- we don't want them to all fail and start again and fail ad infinitum!
-                f : (cb) =>
+                f           : (cb) =>
                     @kernel_info(cb : =>)
                     cb(@_state == 'starting')