update trollbox default sending account logic

--- app/js/controllers/trollbox.coffee
+++ app/js/controllers/trollbox.coffee
@@ -1,4 +1,4 @@
-angular.module("app").controller "TrollboxController", ($scope, $modal, $log, $q, RpcService, Wallet, WalletAPI, BlockchainAPI, Blockchain, Growl, Info, Utils, Observer, $timeout, AD) ->
+angular.module("app").controller "TrollboxController", ($scope, $modal, $log, $q, RpcService, Wallet, WalletAPI, BlockchainAPI, Blockchain, Growl, Info, Utils, Observer, $timeout, $mdDialog, AD) ->
   chatAdPositionAcct    = Info.CHAT_ADD_POSITION_ACCT
   chatAdPricingID       = "plain1m"
   chatListLimit         = 50
@@ -42,7 +42,16 @@
             account_name: account_name,
             balance: balance,
             account_id: $scope.registered_accounts[account_name].id
-      $scope.from.account ||= $scope.accounts[0] if $scope.accounts.length > 0
+
+      # determine default chat account
+      # try to fetch from Wallet.current_account, otherwise, use the first account of
+      # avaialble accounts (registered and with balance)
+      unless $scope.from.account
+        $scope.from.account = $scope.accounts[0] if $scope.accounts.length > 0
+
+        if Wallet.current_account and (foundIndex = ($scope.accounts.findIndex (acct)-> acct.account_name == Wallet.current_account.name))
+          $scope.from.account = $scope.accounts[foundIndex]
+

       if skip_once
         skip_once = false
@@ -240,9 +249,7 @@

       # reset form
       $scope.chatBid.creative.creative.text = ''
-      form.message.$pristine = true
-      form.$pristine = true
-      # console.log "buy success"
+      form.message.$dirty = false

     , (error) ->
         if (error.response.data.error.code == 20010)


Unselect comment when focusing on comment form

--- app/js/views/create_comment.coffee
+++ app/js/views/create_comment.coffee
@@ -8,6 +8,7 @@

   events:
     'submit form': 'create'
+    'focusin': -> @collection.select null

   # Initialize the comment form
   #


add holding page while stats server is being fixed

--- app/pages/project/stats/index.cjsx
+++ app/pages/project/stats/index.cjsx
@@ -67,4 +67,13 @@

     <ProjectStatsPage {...queryProps} />

-module.exports = ProjectStatsPageController
+StatsHoldingPage = React.createClass
+  render: ->
+

+

+        Project statistics are currently unavalible and we are working to fix the issue.
+

+

+
+#module.exports = ProjectStatsPageController
+module.exports = StatsHoldingPage


remove some debuggers

remove some debuggers

--- client/ide/fs/fsitem.coffee
+++ client/ide/fs/fsitem.coffee
@@ -52,23 +52,15 @@

     file = null

-    debugger
-
     ok = osKite.vmStart()
     .then ->
-
-      debugger

       osKite.fsEnsureNonexistentPath(path: targetPath)
       .then (actualPath) ->
         command = "#{ commandPrefix } #{ escapeFilePath sourceItem.path } #{ escapeFilePath actualPath }"

-        debugger
-
         osKite.exec(command)
         .then ->
-
-          debugger

           file = FSHelper.createFile {
             path        : actualPath
@@ -272,7 +264,7 @@
     @osKite.vmStart()

     .then =>
-      @osKite.fsRemove(path: @getPath())
+      @osKite.fsRemove { path: @getPath(), recursive }

     .then (result) ->
       callback null, result


Resurrection: Fetch layout size from KiteStorage

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -901,6 +901,14 @@

     @writeToKiteStorage key, value
     @emit 'LayoutSizeChangesSaved' # TODO: Naming.
+
+
+  fetchLayoutSize: (callback, username = nick()) ->
+
+    key = "#{username}-LayoutSize"
+    @fetchFromKiteStorage callback, key
+
+
   removeWorkspaceSnapshot: (username = nick()) ->

     key = @getWorkspaceStorageKey username


Add logging for token generation

Add logging for token generation

--- services/web/app/coffee/Features/Project/ProjectDetailsHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectDetailsHandler.coffee
@@ -80,8 +80,15 @@
 		ProjectGetter.getProject project_id, {tokens: 1}, (err, project) ->
 			return callback(err) if err?
 			if project.tokens? and project.tokens.readOnly? and project.tokens.readAndWrite?
+				logger.log {project_id}, "project already has tokens"
 				return callback(null, project.tokens)
 			else
+				logger.log {
+					project_id,
+					has_tokens: project.tokens?,
+					has_readOnly: project?.tokens?.readOnly?,
+					has_readAndWrite: project?.tokens?.readAndWrite?
+				}, "generating tokens for project"
 				tokens = project.tokens || {}
 				if !tokens.readOnly?
 					tokens.readOnly = ProjectTokenGenerator.readOnlyToken()


disable voting popup

disable voting popup

--- core/app/backbone/views/evidence/vote_up_down_view.coffee
+++ core/app/backbone/views/evidence/vote_up_down_view.coffee
@@ -28,7 +28,7 @@
     if @model.isBelieving()
       @model.removeOpinion()
     else
-      @_openVoteUpPopup()
+      #@_openVoteUpPopup()
       @model.believe()

   _on_down_vote: ->
@@ -36,7 +36,7 @@
     if @model.isDisBelieving()
       @model.removeOpinion()
     else
-      @_openVoteDownPopup()
+      #@_openVoteDownPopup()
       @model.disbelieve()

   _open_vote_popup: (selector, contentView) ->


Show correct opacity on mouseover and mouseout

--- core/app/backbone/views/wheel/base_fact_wheel_view.coffee
+++ core/app/backbone/views/wheel/base_fact_wheel_view.coffee
@@ -93,8 +93,8 @@

     # Bind Mouse Events on the path
     if @options.respondsToMouse || @options.showsTooltips
-      path.mouseover _.bind(@mouseoverOpinionType, this, path, opinionType)
-      path.mouseout _.bind(@mouseoutOpinionType, this, path, opinionType)
+      path.mouseover _.bind(@mouseoverOpinionType, this, path, opinionType.type)
+      path.mouseout _.bind(@mouseoutOpinionType, this, path, opinionType.type)
       path.click _.bind(@clickOpinionType, this, opinionType.type)

     @opinionTypeRaphaels[opinionType.type] = path
@@ -145,8 +145,8 @@
       direction = if percentage > 50 then 1 else 0
       path: [["M", start.x, start.y], ["A", radius, radius, 0, direction, 0, end.x, end.y]]

-  mouseoverOpinionType: (path, opinionType) ->
-    destinationOpacity = if opinionType.is_user_opinion
+  mouseoverOpinionType: (path, opinion_type) ->
+    destinationOpacity = if @model.isUserOpinion(opinion_type)
                            @options.userOpinionStrokeOpacity
                          else
                            @options.hoverStrokeOpacity
@@ -156,8 +156,8 @@
       opacity: destinationOpacity
     , 200, "<>")

-  mouseoutOpinionType: (path, opinionType) ->
-    destinationOpacity = if opinionType.is_user_opinion
+  mouseoutOpinionType: (path, opinion_type) ->
+    destinationOpacity = if @model.isUserOpinion(opinion_type)
                            @options.userOpinionStrokeOpacity
                          else
                            @options.defaultStrokeOpacity


Make yeti use Entity.makeMeStatic/Dynamic

So it's less of 'kick da yeti around' game

--- app/models/Yeti.coffee
+++ app/models/Yeti.coffee
@@ -13,8 +13,8 @@
     ###
       Is the object Static or Dynamic?
     ###
-    settings.physicsType = 'static'
-    #settings.physicsType = 'dynamic'
+    # settings.physicsType = 'static'
+    settings.physicsType = 'dynamic'

     settings.ellipse = true

@@ -26,6 +26,8 @@
     super x, y, width, height, owningLevel, settings

     @spriteState.creationTime = Date.now()
+
+    @physBody.SetLinearVelocity(new @level.physicsManager.Vec2(10, 10))

   load: =>
@@ -43,14 +45,18 @@
   onTouchBegin: (body, point) =>
     console.log  'Hey, why do you start bumping into me?'
     @spriteState.moving = true
+    @makeMeStatic() if body.GetUserData()?.ent.name is 'Player'

   onTouch: (body, point, impulse) =>
+    console.log @physBody.GetLinearVelocity()
     console.log 'Hey, why do you keep touching me?'
+

   onTouchEnd: (body, point) =>
     console.log 'Hey, why do you stop touching me?'
     @spriteState.moving = false
+    @makeMeDynamic() if body.GetUserData()?.ent.name is 'Player'

   update: =>


- fixed code and multi-line comment on the same line

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -147,11 +147,15 @@
             if currSegment.code.length > 0
               segments.push currSegment
               currSegment = new @Segment
-            currSegment.comments.push line.replace(multiLineMatcher1,"").replace(multiLineMatcher2,"")
+            currSegment.comments.push line.replace(multiLineMatcher1, "").replace(multiLineMatcher2, "")
         else
           if match3?
+            if !incomment
+              if !options.commentsOnly
+                currSegment.code.push line
+            else
+              currSegment.comments.push ""
             incomment = false
-            currSegment.comments.push ""
           else
             if incomment
               if currSegment.code.length > 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -7,10 +7,5 @@
     ]

   animation = Animation(data)
-    initialState: 'stand'

-  animation.include(StateMachine)
-
-  animation.transition('run')
-
   equals animation.currentState(), "run"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -8,3 +8,14 @@

     start()
   , milliseconds
+
+asyncTest "Animation should increment frame on update", ->
+  animation = Animation.fromPixieId 45
+
+  milliseconds = 300
+
+  setTimeout ->
+    equals animation.update().frames[0], 1
+
+    start()
+  , milliseconds


fix nerd part 2

fix nerd part 2

--- nostalgia/nerd.coffee
+++ nostalgia/nerd.coffee
@@ -160,11 +160,6 @@
   a few functions that turn variables into readable formats
   ###

-  # some nice class variables for Nerd::readify.numberToEnglishString
-  ones = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "eighteen", "nineteen"]
-  tens = ["twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
-  scales = ["", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"]
-
   readify:
     ###
     generates an HTML list from anything, whether it be a multilevel JSON tree, a simple
@@ -270,6 +265,11 @@
     100000000 -> "one-hundred million"
     ###
     numberToEnglishString: (num) ->
+      # some nice class variables for Nerd::readify.numberToEnglishString
+      ones = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "eighteen", "nineteen"]
+      tens = ["twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
+      scales = ["", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion"]
+
       # Based on code that I found here:
       # <http://www.anujgakhar.com/2014/02/23/converting-number-to-words-with-javascript/>
       number = Math.abs(num)


Removed requires since tether has been updated

--- core/app/backbone/factlink.backbone.coffee
+++ core/app/backbone/factlink.backbone.coffee
@@ -6,13 +6,7 @@
 #= require react
 #= require react.backbone

-# TODO: Replace by just 'require tether' when
-# this has been merged: https://github.com/HubSpot/tether/pull/25
-#= require tether/utils
-#= require tether/tether
-#= require tether/constraint
-#= require tether/abutment
-#= require tether/shift
+#= require tether

 #= require_tree ../templates
 #= require_self


Tweak to fix Heroku time issue.

Tweak to fix Heroku time issue.

--- scripts/hello.coffee
+++ scripts/hello.coffee
@@ -15,8 +15,7 @@

 moment = require 'moment'

-# get current time
-currentHour = moment().hour()
+# default greeting
 greeting = 'Hello'

 # get the weather
@@ -53,8 +52,8 @@

 module.exports = (robot) ->
   robot.hear /hi|hello/i, (msg) ->
-
     # Time
+    currentHour = moment().hour()
     if currentHour < 12
       greeting = 'Good morning'
     if currentHour >= 12 && currentHour <= 17


added test for trimming key

added test for trimming key

--- test/lib/kevas.coffee
+++ test/lib/kevas.coffee
@@ -24,6 +24,12 @@
     {
       desc: 'with a single key alone in string'
       output:['{{key}}']
+      values: key:'value'
+      result:'value'
+    }
+    {
+      desc: 'with a single key surrouned by space'
+      output:['{{ key }}']
       values: key:'value'
       result:'value'
     }


made end of YomTob time visible

made end of YomTob time visible

--- source/javascripts/mincha.coffee
+++ source/javascripts/mincha.coffee
@@ -126,12 +126,12 @@
       $(".#{@chag()}.afternoon-shiur").find(".dow, .date").attr("rowspan", if @hebrew_date.isShabbat() then 6 else 3)
       $(".#{@chag()}.shema").find(".dow, .date").attr("rowspan", if @hebrew_date.isShabbat() then 7 else 4)
     @afternoon_shiur()
+    @rabbenu_tam_schedule() if @hebrew_date.isShabbat() || (@hebrew_date.is2ndDayOfYomTob() && !@hebrew_date.isErebShabbat())
     if @hebrew_date.isShabbat()
       $(".#{@chag()}.seudat-shelishit").removeClass('hidden').find(".time").html(time_format(@seudat_shelishit_time()))
       if @hebrew_date.isEreb9Ab()
         $(".#{@chag()} .ereb-9-ab").removeClass('hidden')
         $(".#{@chag()} .not-ereb-9-ab").addClass('hidden')
-      @rabbenu_tam_schedule()
       if @hebrew_date.isShabbatMevarechim()
         nextMonth = new HachrazatRoshChodesh(@hebrew_date)
         $(".#{@chag()}.molad").removeClass('hidden').find('td').html("#{nextMonth.moladAnnouncement()}<br>#{nextMonth.sephardicAnnouncement()}")


minor fixing in commented out debug log

minor fixing in commented out debug log

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -200,7 +200,7 @@
                             #console.log 'autorizazion request', __user, @id

                             if !@identity
-                                #console.log 'authorized user', __user.login
+                                #console.log 'no identity', __user?.login

                                 fn null, __user

@@ -209,7 +209,7 @@
                                 _auth (err, auth)=>
                                     return fn(err) if err

-                                    #console.log 'delegate authorize', identity.id
+                                    #console.log 'delegate authorize', @identity.id
                                     auth.authorize token, (err, user)=>

                                         fn err, __user = user


preserve corpora choice when logging out

git-svn-id: 00f01ee4f729ccc52c3e336777c3e81b5adc88db@252896 ef392f78-ee14-0410-84e5-9db46baae5bb

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -138,8 +138,15 @@
     s.logout = () ->
         authenticationProxy.loginObj = {}
         $.jStorage.deleteKey "creds"
-        $("#corpusbox").corpusChooser "redraw"
+        newCorpora = []
+        for corpus in settings.corpusListing.getSelectedCorpora()
+            if not settings.corpora[corpus].limited_access
+                newCorpora.push corpus
+        if _.isEmpty newCorpora
+            newCorpora = settings.preselected_corpora
+        settings.corpusListing.select newCorpora
         s.loggedIn = false
+        $("#corpusbox").corpusChooser "selectItems", newCorpora
         return

     N_VISIBLE = settings.visibleModes


Add some error handling when adapter has no queue


Change: Allow models to be wrapped in data element

Signed-off-by: Christian Roth <396398d2f664a436582eed8a8cfb08dd7671c8d6@port17.de>

--- assets/coffee/models/base/model.coffee
+++ assets/coffee/models/base/model.coffee
@@ -8,7 +8,7 @@
   exports.Model = class Model extends Backbone.RelationalModel
     _.extend @prototype, Chaplin.EventBroker
     _.extend @prototype, Chaplin.SyncMachine
-
+
     attributes = ['getAttributes', 'serialize', 'disposed']
     for attr in attributes
       @::[attr] = Chaplin.Model::[attr]
@@ -43,6 +43,9 @@

       super options

+    parse: (json) ->
+      if json.data then json.data else json
+
     # make usage of save more comfortable
     save: (cb, attributes = {}) ->
       super attributes, cb


titles

titles

--- apps/fair/index.coffee
+++ apps/fair/index.coffee
@@ -9,13 +9,19 @@
 app.set 'views', __dirname
 app.set 'view engine', 'jade'

-app.get '/:id/info', routes.info
-app.get '/:id/for-you', routes.forYou
-app.get '/:id/overview', routes.overview
-app.get '/:id/search', routes.search
+app.get '/:id/info', routes.info       # #{fair.name} | Visitor Information | Artsy
+app.get '/:id/for-you', routes.forYou  # #{fair.name} | Your Personal Fair Guide | Artsy
+app.get '/:id/overview', routes.overview # TODO: put in redirect to home
+app.get '/:id/search', routes.search # nope
 app.get '/:id/browse/show/:partner_id', routes.showRedirect
 app.get '/:id/browse', routes.browse
 app.get '/:id/browse/*', routes.browse

+# browse/booths - # #{fair.name} | Browse #{exhibitorsCount} Exhibitors | Artsy
+# browse/exhibitors - # #{fair.name} | See A-Z List of All Exhibitors | Artsy
+# browse/artists - # #{fair.name} | See A-Z List of All Artists | Artsy
+# browse/artworks - # #{fair.name} | Browse #{artworksCount} Artworks | Artsy
+# browse/section/:id - # #{fair.name} | Exhibitors at #{sectionName} | Artsy
+
 app.get '/:id/following/:type', routes.follows
 app.get '/:id/favorites', routes.favorites


[Gutter] Clear custom gutter decoration state objects instead of reassigning to new objects

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -473,12 +473,20 @@
   updateCustomGutterDecorationState: ->
     return unless @startRow? and @endRow? and @lineHeight?

-    @customGutterDecorations = {}
-    return if @model.isMini()
+    if @model.isMini()
+      # Mini editors have no gutter decorations.
+      # We clear instead of reassigning to preserve the reference.
+      @clearAllCustomGutterDecorations()

     for gutter in @model.getGutters()
       gutterName = gutter.name
-      @customGutterDecorations[gutterName] = {}
+      gutterDecorations = @customGutterDecorations[gutterName]
+      if not gutterDecorations
+        @customGutterDecorations[gutterName] = {}
+      else
+        # Clear the gutter decorations; they are rebuilt.
+        # We clear instead of reassigning to preserve the reference.
+        @clearDecorationsForCustomGutterName(gutterName)
       return if not @gutterIsVisible(gutter)

       relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1)
@@ -489,6 +497,18 @@
           height: @lineHeight * decorationRange.getRowCount()
           item: decoration.getProperties().item
           class: decoration.getProperties().class
+
+  clearAllCustomGutterDecorations: ->
+    allGutterNames = Object.keys(@customGutterDecorations)
+    for gutterName in allGutterNames
+      @clearDecorationsForCustomGutterName(gutterName)
+
+  clearDecorationsForCustomGutterName: (gutterName) ->
+    gutterDecorations = @customGutterDecorations[gutterName]
+    if gutterDecorations
+      allDecorationIds = Object.keys(gutterDecorations)
+      for decorationId in allDecorationIds
+        delete gutterDecorations[decorationId]

   gutterIsVisible: (gutterModel) ->
     isVisible = gutterModel.isVisible()


ok, now I think I solved this lovely regexp

--- src/toaster/grains/script.coffee
+++ src/toaster/grains/script.coffee
@@ -221,8 +221,8 @@
 				namespace = filefolder.replace(/\//g, ".").slice 0, -1

 				# if there is a class inside the file
-				rgx = "(class)+\\s+(\\w+)?($|\\s+(extends)\\s+(\\w+)$)"
-				if raw.match( new RegExp( rgx, "m" )?
+				rgx = "(class)+\\s+(\\w+)(\\s*$|\\s+extends\\s+\\w+\\s*$)"
+				if raw.match( new RegExp rgx, "m" )?

 					# if the file is not in the root src folder (outside any
 					# folder/package )
@@ -237,7 +237,7 @@
 					#		classname: ClassName
 					#		namespace: package.subpackage
 					#		classpath: package.subpackage.ClassName
-					classname = new RegExp( rgx, "gm").exec( raw )[ 2 ]
+					classname = raw.match( new RegExp( rgx, "m") )[2]

 					if namespace is ""
 						classpath = classname


Fix backspacing over attachments

Fix backspacing over attachments

--- src/trix/views/block_view.coffee
+++ src/trix/views/block_view.coffee
@@ -88,6 +88,8 @@
     span = document.createElement("span")
     span.setAttribute("data-trix-pending", "true")
     span.trixCursorTarget = true
+    span.trixPosition = position
+    span.trixIndex = @blockIndex
     span.appendChild(text)
     span


Using param.name for name of the author

Using param.name for name of the author

--- lib/editors.coffee
+++ lib/editors.coffee
@@ -75,10 +75,11 @@
       fun = (index) =>
         @_cursors.moveCursor param.id, index
     else
-      if cursor? and cursor.color? and cursor.color != param.color        @removeCursor param.id
+      if cursor? and cursor.color? and cursor.color != param.color
+        @removeCursor param.id

       fun = (index) =>
-        @_cursors.setCursor(param.id, index,
+        @_cursors.setCursor(param.id, index,
           param.name, param.color)

     if param.index?


use shell module on Linux

use shell module on Linux

--- lib/browse.coffee
+++ lib/browse.coffee
@@ -1,23 +1,15 @@
-{exec,execSync} = require('child_process')
+# Dependencies
 fs   = require 'fs'
 path = require 'path'
+shell = require 'shell'

 {CompositeDisposable} = require 'atom'

 module.exports = BrowsePackages =
   self: 'browse'
-  debug: false
   subscriptions: null
-  fileManager: null
-  configFile: atom.config.getUserConfigPath()
-  packageDir: atom.packages.getPackageDirPaths()[0]
-  linuxFileManagers: ['nautilus', 'dolphin', 'xdg-open', 'gnome-open', 'kde-open']
-
+
   activate: ->
-
-    # Find default file manager
-    @fileManager = @getFileManager()
-
     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
     @subscriptions = new CompositeDisposable

@@ -31,17 +23,17 @@
     @subscriptions.dispose()

   browsePackages: ->
-    if @fileManager isnt null
+    packageDir: atom.packages.getPackageDirPaths()[0]

-      # Does packages folder exist?
-      try
-        fs.accessSync(@packageDir, fs.F_OK)
-      catch error
-        atom.notifications.addError(@self, detail: error, dismissable: true)
-        return
+    # Does packages folder exist?
+    try
+      fs.accessSync(packageDir, fs.F_OK)
+    catch error
+      atom.notifications.addError(@self, detail: error, dismissable: true)
+      return

-      # Open packages folder
-      exec "#{@fileManager} #{@packageDir}"
+    # Open packages folder
+    shell.showItemInFolder(packageDir)

   revealFile: ->
     # Get parent folder of active file
@@ -51,23 +43,8 @@
     if file isnt null
       filePath = path.dirname(file?.path)

-      switch process.platform
-        when "darwin"
-          args = "-R #{file.path}"
-        when "win32"
-          args = "/select,#{file.path}"
-        when "linux"
-
-          # Refined Linux arguments
-          if @fileManager is "nautilus"
-            args = "-w #{filePath}"
-          else if @fileManager is "dolphin"
-            args = "--select #{filePath}"
-          else
-            args = filePath
-
       # Reveal file
-      exec "#{@fileManager} #{args}"
+      shell.showItemInFolder(file.path)
       return

     atom.notifications.addWarning("**#{@self}**: No active file", dismissable: false)
@@ -88,55 +65,18 @@
         continue

       # Open project folder
-      exec "#{@fileManager} #{project}"
+      shell.showItemInFolder(project)

   browseConfig: ->
-    configPath = path.dirname(@configFile)
+    configFile: atom.config.getUserConfigPath()
+    configPath = path.dirname(configFile)

-    if @fileManager isnt null
-      # Does config folder exist?
-      try
-        fs.accessSync(configPath, fs.F_OK)
-      catch error
-        atom.notifications.addError(@self, detail: error, dismissable: true)
-        return
+    # Does config folder exist?
+    try
+      fs.accessSync(configPath, fs.F_OK)
+    catch error
+      atom.notifications.addError(@self, detail: error, dismissable: true)
+      return

-      args = configPath
-
-      # Open config folder
-      exec "#{@fileManager} #{args}"
-
-  getFileManager: ->
-    fm = atom.config.get('browse.fileManager')
-
-    if typeof fm isnt 'undefined'
-      if @debug?
-        console.log "[#{@self}] Load from config: #{fm}"
-      return fm
-
-    switch process.platform
-      when "darwin"
-        return "open"
-      when "win32"
-        return "explorer"
-      when "linux"
-        # There are many possible file managers on Linux, let's iterate over
-        # the most popular ones
-        @loopWhich (result) ->
-          if typeof result isnt 'undefined'
-            fm = result.trim()
-            if @debug
-              console.log "[browse] Saving #{fm} for future use"
-            atom.config.set('browse.fileManager', fm)
-            return fm
-
-          atom.notifications.addWarning("**browse**: No supported file manager detected", dismissable: true)
-
-  loopWhich: (callback) ->
-    for fm in @linuxFileManagers
-
-      if @debug
-        console.log "[#{@self}] Trying: #{fm}"
-      exec "which #{fm}", (error, stdout, stderr) ->
-        if error is null
-          callback stdout
+    # Open config folder
+    shell.openItem(configPath)


revert, remove the stupid collection if line numbers at the end of lines

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -1,42 +1,24 @@
-# ## Code snippet helper
+## Code snippet helper
 # A helper class to deal with building code.

 CoffeeScript = @CoffeeScript or require 'coffee-script'

 class Code
   constructor: ->
-    @code = ""
-    @src_lines = []
+    @code = ''

-  #add str to @code, collect src lineno's from n, if nl then append src lineno's
-  add: (str,n) ->
-    @remember(n)
-    str = str.toString()
-    if str.match("\n$")
-      str = str.substring(0,str.length-1) + "#{@nll()}\n"
-    @code += str
+  add: (str) ->
+    @code += str.toString()
+    @

-  scope: (str, level, n) ->
-    @remember(n)
+  scope: (str, level=1) ->
     indent = strRepeat("  ", level)
     @code  = rtrim(@code) + "\n"
-    @code += indent + rtrim(str).replace(/\n/g, "\n#{indent}") + "#{@nll()}\n"
+    @code += indent + rtrim(str).replace(/\n/g, "\n#{indent}") + "\n"
     @

   toString: ->
     @code
-
-  nll: ->
-    return "" if @src_lines.length == 0
-    res = " ##{@src_lines.join(',')}"
-    @src_lines = []
-    res
-
-  remember: (n) ->
-    if n
-      @src_lines.push(n.lineno) unless n.lineno in @src_lines
-
-

 # ## String helpers
 # These are functions that deal with strings.
@@ -49,11 +31,11 @@
 #     paren '(hi)' => "(hi)"
 #
 paren = (string) ->
-    str = string.toString()
-    if str.substr(0, 1) == '(' and str.substr(-1, 1) == ')'
-      str
-    else
-      "(#{str})"
+  str = string.toString()
+  if str.substr(0, 1) == '(' and str.substr(-1, 1) == ')'
+    str
+  else
+    "(#{str})"

 # `strRepeat()`
 # Repeats a string a certain number of times.
@@ -62,7 +44,7 @@
 #     strRepeat('.', 3) => "..."
 #
 strRepeat = (str, times) ->
-    (str for i in [0...times]).join('')
+  (str for i in [0...times]).join('')

 # `trim()` *and friends*
 # String trimming functions.
@@ -142,4 +124,4 @@
   {Code, p, strEscape, unreserve, unshift, isSingleLine, trim,
   blockTrim, ltrim, rtrim, strRepeat, paren, truthy}

-module.exports = exports  if module?
+module.exports = exports  if module?


:fire: Remove old helpers

:fire: Remove old helpers

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -1,225 +0,0 @@
-{BufferedProcess, BufferedNodeProcess} = require('atom')
-path = require 'path'
-fs = require 'fs'
-path = require 'path'
-tmp = require('tmp')
-
-xcache = new Map
-XRegExp = null
-EventsCache = new WeakMap
-
-module.exports = Helpers =
-  # Based on an API demoed out in:
-  #   https://gist.github.com/steelbrain/43d9c38208bf9f2964ab
-
-  exec: (command, args = [], options = {}) ->
-    throw new Error "Nothing to execute." unless arguments.length
-    return @_exec(command, args, options, false)
-
-  execNode: (filePath, args = [], options = {}) ->
-    throw new Error "Nothing to execute." unless arguments.length
-    return @_exec(filePath, args, options, true)
-
-  _exec: (command, args = [], options = {}, isNodeExecutable = false) ->
-    options.stream ?= 'stdout'
-    options.throwOnStdErr ?= true
-    return new Promise (resolve, reject) ->
-      data = stdout: [], stderr: []
-      stdout = (output) -> data.stdout.push(output.toString())
-      stderr = (output) -> data.stderr.push(output.toString())
-      exit = ->
-        if options.stream is 'stdout'
-          if data.stderr.length and options.throwOnStdErr
-            reject(new Error(data.stderr.join('')))
-          else
-            resolve(data.stdout.join(''))
-        else if options.stream is 'both'
-          resolve(stdout: data.stdout.join(''), stderr: data.stderr.join(''))
-        else
-          resolve(data.stderr.join(''))
-      if isNodeExecutable
-        options.env ?= {}
-        for prop, value of process.env
-          options.env[prop] = value unless prop is 'OS'
-        spawnedProcess = new BufferedNodeProcess({command, args, options, stdout, stderr, exit})
-      else
-        spawnedProcess = new BufferedProcess({command, args, options, stdout, stderr, exit})
-      spawnedProcess.onWillThrowError(({error, handle}) ->
-        return reject(error) if error and error.code is 'ENOENT'
-        handle()
-        if error.code is 'EACCES'
-          error = new Error("Failed to spawn command `#{command}`. Make sure it's a file, not a directory and it's executable.")
-          error.name = 'BufferedProcessError'
-        reject(error)
-      )
-      if options.stdin
-        spawnedProcess.process.stdin.write(options.stdin.toString())
-        spawnedProcess.process.stdin.end() # We have to end it or the programs will keep waiting forever
-
-  rangeFromLineNumber: (textEditor, lineNumber, colStart) ->
-    throw new Error('Provided text editor is invalid') unless textEditor?.getBuffer?
-    if typeof lineNumber isnt 'number' or lineNumber isnt lineNumber or lineNumber < 0
-      return [[0, 0], [0, 1]]
-    buffer = textEditor.getBuffer()
-    maxLine = buffer.getLineCount() - 1
-    if lineNumber > maxLine
-      throw new Error("Line number (#{lineNumber}) greater than maximum line (#{maxLine})")
-    if typeof colStart isnt 'number' or colStart < 0
-      indentation = buffer.lineForRow(lineNumber).match(/^\s+/)
-      if indentation and indentation.length = 1
-        colStart = indentation[0].length
-      else
-        colStart = 0
-    lineLength = buffer.lineLengthForRow(lineNumber)
-    if colStart > lineLength
-      throw new Error("Column start (#{colStart}) greater than line length (#{lineLength})")
-    return [
-      [lineNumber, colStart],
-      [lineNumber, lineLength]
-    ]
-
-  createElement: (name) ->
-    el = document.createElement(name)
-    EventsCache.set(el, [])
-    el._addEventListener = el.addEventListener
-    el._removeEventListener = el.removeEventListener
-    el._cloneNode = el.cloneNode
-    el.addEventListener = (name, callback) ->
-      EventsCache.get(el).push({name, callback})
-      el._addEventListener(name, callback)
-    el.removeEventListener = (name, callback) ->
-      events = EventsCache.get(el)
-      i = events.length - 1
-      while i isnt -1
-        current = events[i]
-        if current.name is name and current.callback is callback
-          events.splice(i, 1)
-        --i
-      el._removeEventListener(name, callback)
-    el.cloneNode = (deep) ->
-      newEl = el._cloneNode(deep)
-      EventsCache.get(el).forEach((event) ->
-        newEl.addEventListener(event.name, event.callback)
-      )
-      return newEl
-    return el
-
-  # Due to what we are attempting to do, the only viable solution right now is
-  #   XRegExp.
-  #
-  # Follows the following format taken from 0.x.y API.
-  #
-  # file: the file where the issue Exists
-  # type: the type of issue occuring here
-  # message: the message to show in the linter views (required)
-  # line: the line number on which to mark error (required if not lineStart)
-  # lineStart: the line number to start the error mark (optional)
-  # lineEnd: the line number on end the error mark (optional)
-  # col: the column on which to mark, will utilize syntax scope to higlight the
-  #      closest matching syntax element based on your code syntax (optional)
-  # colStart: column to on which to start a higlight (optional)
-  # colEnd: column to end highlight (optional)
-  # We place priority on `lineStart` and `lineEnd` over `line.`
-  # We place priority on `colStart` and `colEnd` over `col.`
-  parse: (data, rawRegex, options = {}) ->
-    throw new Error "Nothing to parse" unless arguments.length
-    XRegExp ?= require('xregexp').XRegExp
-    options.baseReduction ?= 1
-    options.flags ?= ""
-    toReturn = []
-    if xcache.has(rawRegex)
-      regex = xcache.get(rawRegex)
-    else
-      xcache.set(rawRegex, regex = XRegExp(rawRegex, options.flags))
-    throw new Error("Input must be a string") unless typeof data is 'string'
-    for line in data.split(/\r?\n/)
-      match = XRegExp.exec(line, regex)
-      if match
-        options.baseReduction = 1 unless options.baseReduction
-        lineStart = 0
-        lineStart = match.line - options.baseReduction if match.line
-        lineStart = match.lineStart - options.baseReduction if match.lineStart
-        colStart = 0
-        colStart = match.col - options.baseReduction if match.col
-        colStart = match.colStart - options.baseReduction if match.colStart
-        lineEnd = 0
-        lineEnd = match.line - options.baseReduction if match.line
-        lineEnd = match.lineEnd - options.baseReduction if match.lineEnd
-        colEnd = 0
-        colEnd = match.col - options.baseReduction if match.col
-        colEnd = match.colEnd - options.baseReduction if match.colEnd
-        filePath = match.file
-        filePath = options.filePath if options.filePath
-        toReturn.push(
-          type: match.type,
-          text: match.message,
-          filePath: filePath,
-          range: [[lineStart, colStart], [lineEnd, colEnd]]
-        )
-    return toReturn
-
-  findFileAsync: (directory, names) ->
-    throw new Error "Specify a filename to find" unless arguments.length
-    unless names instanceof Array
-      names = [names]
-
-    chunks = directory.split(path.sep)
-    promise = Promise.resolve(null)
-
-    while chunks.length
-      currentDir = chunks.join(path.sep)
-      break unless currentDir
-
-      do (currentDir) ->
-        promise = promise.then (filePath) ->
-          return filePath unless filePath is null
-          return names.reduce (promise, name) ->
-            currentFile = path.join(currentDir, name)
-            return new Promise (resolve) ->
-              fs.access(currentFile, fs.R_OK, (err) ->
-                resolve(currentFile) unless err
-                resolve(null)
-              )
-          , Promise.resolve(null)
-
-        chunks.pop()
-
-    return promise
-
-  findFile: (startDir, names) ->
-    throw new Error "Specify a filename to find" unless arguments.length
-    unless names instanceof Array
-      names = [names]
-    startDir = startDir.split(path.sep)
-    while startDir.length and startDir.join(path.sep)
-      currentDir = startDir.join(path.sep)
-      for name in names
-        filePath = path.join(currentDir, name)
-        try
-          fs.accessSync(filePath, fs.R_OK)
-          return filePath
-      startDir.pop()
-    return null
-
-  tempFile: (fileName, fileContents, callback) ->
-    throw new Error('Invalid fileName provided') unless typeof fileName is 'string'
-    throw new Error('Invalid fileContent provided') unless typeof fileContents is 'string'
-    throw new Error('Invalid Callback provided') unless typeof callback is 'function'
-
-    return new Promise (resolve, reject) ->
-      tmp.dir {prefix: 'atom-linter_'}, (err, dirPath, cleanupCallback) ->
-        return reject(err) if err
-        filePath = path.join(dirPath, fileName)
-        fs.writeFile filePath, fileContents, (err) ->
-          if err
-            cleanupCallback()
-            return reject(err)
-          (
-            new Promise (resolve) ->
-              resolve(callback(filePath))
-          ).then((result) ->
-            fs.unlink(filePath, ->
-              fs.rmdir(dirPath)
-            )
-            return result
-          ).then(resolve, reject)


Using text for option elements

Using text for option elements

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -134,7 +134,8 @@

           optionName = value?.name or value
           bindObservable option, optionName, value, (newValue) ->
-            option.textContent = newValue
+            # IE8 likes option.text better
+            option.textContent = option.text = newValue

           element.appendChild option
           element.selectedIndex = index if value is element._value


Have module record descriptions.

Have module record descriptions.

--- records.coffee
+++ records.coffee
@@ -1,3 +1,7 @@
+###
+    Copyright (c) 2014 Sven Michael Klose <pixel@copei.de>
+###
+
 RECORD_INFOS =
   client: [
     name:   "user"
@@ -258,29 +262,26 @@
     ]
   ]

-###
-  address: [
+  menuitem: [
     name: "name"
-    type: "text"
-    desc: "Name"
-   ,
-    name: "vorname"
-    type: "text"
-    desc: "Vorname"
-   ,
-    name: "tel"
-    type: "tel"
-    desc: "Telefonnummer"
-  ]
-
-  location: [
-    name: "latitude"
-    type: "range"
-    desc: "Breitengrad"
-   ,
-    name: "longitude"
-    type: "range"
-    desc: "Längengrad"
+    type: "textline"
+    desc: "Beschriftung"
+   ,
+    name: "ad"
+    type: "selection"
+    desc: "Werbung"
+   ,
+    name: "is_active"
+    type: "radio"
+    desc: "aktiv?"
+   ,
+    name: "is_global"
+    type: "radio"
+    desc: "global?"
+   ,
+    name: "code"
+    type: "password"
+    desc: "Code"
   ]

   media: [
@@ -304,4 +305,178 @@
     type: "degree"
     desc: "Drehung"
   ]
-###
+
+  dashboard: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "file"
+    desc: "Medien"
+  ]
+
+  mediaviewer: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "file"
+    desc: "Medien"
+  ]
+
+  flippic: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "list"
+    desc: "Medien"
+    list: [
+      name: "front"
+      type: "media"
+      desc: "Vorderseite"
+     ,
+      name: "back"
+      type: "media"
+      desc: "Rückseite"
+     ,
+      name: "video"
+      type: "media"
+      desc: "Video"
+    ]
+  ]
+
+  coverflow: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "list"
+    desc: "Medien"
+    list: [
+      name: "front"
+      type: "media"
+      desc: "Vorderseite"
+     ,
+      name: "back"
+      type: "media"
+      desc: "Rückseite"
+     ,
+      name: "pdf"
+      type: "media"
+      desc: "PDF"
+    ]
+  ]
+
+  slidebook: [
+    name: "background"
+    type: "file"
+    desc: "Hintergrundbild"
+   ,
+    name: "items"
+    type: "list"
+    desc: "Medien"
+    list: [
+      name: "front"
+      type: "media"
+      desc: "Vorderseite"
+     ,
+      name: "back"
+      type: "media"
+      desc: "Rückseite"
+     ,
+      name: "pdf"
+      type: "media"
+      desc: "PDF"
+    ]
+  ]
+
+  memorie: [
+    name: "back"
+    type: "file"
+    desc: "Rückseite"
+   ,
+    name:  "items"
+    type:  "list"
+    limit: 12
+    desc:  "Medien"
+    list: [
+      name: "front"
+      type: "media"
+      desc: "Vorderseite"
+    ]
+  ]
+
+  pinboard: [
+    name:  "items"
+    type:  "list"
+    desc:  "Nachrichten"
+    list: [
+      name: "messsage"
+      type: "text"
+      desc: "Nachricht"
+    ]
+  ]
+
+  map: [
+    name: "type"
+    type: "selection"
+    opts: ["normal"]
+    desc: "Typ"
+   ,
+    name: "location"
+    type: "location"
+    desc: "Kartenmitte"
+   ,
+    name:  "pins"
+    type:  "list"
+    desc:  "Pins"
+    list: [
+      name: "pin"
+      type: "location"
+      desc: "Pin"
+    ]
+  ]
+
+  browser: [
+    name: "has_address_field"
+    type: "radio"
+    desc: "Adresszeile einblenden?"
+   ,
+    name: "has_keyboard"
+    type: "radio"
+    desc: "Tastatur einblenden?"
+   ,
+    name:  "blacklist"
+    type:  "list"
+    desc:  "Blacklist"
+    list: [
+      name: "url"
+      type: "url"
+      desc: "URL"
+    ]
+   ,
+    name:  "whitelist"
+    type:  "list"
+    desc:  "whitelist"
+    list: [
+      name: "url"
+      type: "url"
+      desc: "URL"
+    ]
+  ]
+
+  wall: [
+    name:  "blacklist"
+    type:  "list"
+    desc:  "Blacklist"
+    list: [
+      name: "item"
+      type: "media"
+      desc: "Medium"
+    ]
+  ]


Surround CSON with quotes

Surround CSON with quotes

CSON is kind of lax, but try to keep same style as rest of CSON files

--- settings/dart.cson
+++ settings/dart.cson
@@ -1,3 +1,3 @@
 '.source.dart':
-  editor:
-    commentStart: '// '
+  'editor':
+    'commentStart': '// '


Shared included models

Shared included models

--- lib/cursor.coffee
+++ lib/cursor.coffee
@@ -101,13 +101,11 @@
       return callback(null, null) if @_cursor.$one and not json
       json = [json] unless _.isArray(json)

-      if can_cache = !(@_cursor.$select or @_cursor.$whitelist) # don't cache if we may not have fetched the full model
-        models = (Utils.updateOrNew(item, @model_type) for item in json)
-      else
-        models = (model = new @model_type(@model_type::parse(item)); model.setLoaded(false); model for item in json)
-
-      @lookupIncludes models, (err, models) =>
-        return callback(err) if err
+      @prepareIncludes json, (err, json) =>
+        if can_cache = !(@_cursor.$select or @_cursor.$whitelist) # don't cache if we may not have fetched the full model
+          models = (Utils.updateOrNew(item, @model_type) for item in json)
+        else
+          models = (model = new @model_type(@model_type::parse(item)); model.setLoaded(false); model for item in json)
         return callback(null, if @_cursor.$one then models[0] else models)

   # @abstract Provided by a concrete cursor for a Backbone Sync type
@@ -145,29 +143,31 @@

     return models

-  lookupIncludes: (models, callback) ->
-    return callback(null, models) if not _.isArray(@_cursor.$include) or _.isEmpty(@_cursor.$include)
+  prepareIncludes: (json, callback) ->
+    return callback(null, json) if not _.isArray(@_cursor.$include) or _.isEmpty(@_cursor.$include)
     schema = @model_type.schema()
+    shared_related_models = {}
+
+    findOrNew = (related_json, reverse_model_type) =>
+      unless shared_related_models[related_json.id]
+        if reverse_model_type.cache
+          unless shared_related_models[related_json.id] = reverse_model_type.cache.get(related_json.id)
+            reverse_model_type.cache.set(related_json.id, shared_related_models[related_json.id] = new reverse_model_type(related_json))
+        else
+          shared_related_models[related_json.id] = new reverse_model_type(related_json)
+      return shared_related_models[related_json.id]

     for include in @_cursor.$include
       relation = schema.relation(include)
+      shared_related_models = {} # reset

-      for model in models
-        continue if _.isNull(related_models = model.get(include)) # nothing to bind
-        return callback(new Error "toModels lookupIncludes: could not find include '#{include}' for model #{@model_type.model_name}") if _.isUndefined(related_models) # nothing to bind
+      for model_json in json
+        # many
+        if _.isArray(related_json = model_json[include])
+          model_json[include] = (findOrNew(item, relation.reverse_model_type) for item in related_json)

-        related_models = related_models.models or [related_models]
-        for related_model in related_models
-          return callback(new Error "toModels lookupIncludes: expecting to find reverse model for #{model.id}") unless reverse_related_models = related_model.get(relation.reverse_relation.key)
+        # one
+        else if related_json
+          model_json[include] = findOrNew(related_json, relation.reverse_model_type)

-          # collection
-          if reverse_related_models.models
-            return callback(new Error "toModels lookupIncludes: Couldn't find model #{model.id}.") unless reverse_related_model = reverse_related_models.get(model.id)
-            reverse_related_models.models.splice(reverse_related_models.models.indexOf(reverse_related_model), 1, model) # no one is listening yet
-
-          # model
-          else
-            return callback(new Error "toModels lookupIncludes: Unexpected model. Expecting: #{model.id}. Found: #{reverse_related_models.id}") if reverse_related_models.id isnt model.id
-            related_model.attributes[relation.reverse_relation.key] = model # no one is listening yet
-
-    callback(null, models)
+    callback(null, json)


Unfocus spec

Unfocus spec

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -20,7 +20,7 @@
       afterEach ->
         atom.setSize(originalSize.width, originalSize.height)

-      fit 'sets the size of the window, and can retrieve the size just set', ->
+      it 'sets the size of the window, and can retrieve the size just set', ->
         atom.setSize(100, 400)
         expect(atom.getSize()).toEqual width: 100, height: 400


Issue #1547 'use strict' delete operands restricted

--- nodes.coffee
+++ nodes.coffee
@@ -1467,6 +1467,10 @@
     # In chains, there's no need to wrap bare obj literals in parens,
     # as the chained expression is wrapped.
     @first.front = @front unless isChain
+    if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
+      throw SyntaxError 'delete operand may not be argument or var'
+    if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
+        throw SyntaxError 'prefix increment/decrement may not have eval or arguments operand'
     return @compileUnary     o if @isUnary()
     return @compileChain     o if isChain
     return @compileExistence o if @operator is '?'


Use Buffer.from() instead of deprecated Buffer constructor

--- spec/file-spec.coffee
+++ spec/file-spec.coffee
@@ -416,7 +416,7 @@

     it 'honors the specified encoding', ->
       unicodeText = 'ё'
-      unicodeBytes = new Buffer('\x51\x04') # 'ё'
+      unicodeBytes = Buffer.from('\x51\x04') # 'ё'

       fs.writeFileSync(file.getPath(), unicodeBytes)

@@ -437,7 +437,7 @@
   describe 'createWriteStream()', ->
     it 'returns a stream to read the file', ->
       unicodeText = 'ё'
-      unicodeBytes = new Buffer('\x51\x04') # 'ё'
+      unicodeBytes = Buffer.from('\x51\x04') # 'ё'

       file.setEncoding('utf16le')
       stream = file.createWriteStream()
@@ -459,7 +459,7 @@

     beforeEach ->
       unicodeText = 'ё'
-      unicodeBytes = new Buffer('\x51\x04') # 'ё'
+      unicodeBytes = Buffer.from('\x51\x04') # 'ё'

     it 'should read a file in UTF-16', ->
       fs.writeFileSync(file.getPath(), unicodeBytes)


Easing leniency on pure statements.

Easing leniency on pure statements.

--- nodes.coffee
+++ nodes.coffee
@@ -88,12 +88,11 @@
   # Recursively traverses down the *children* of the nodes, yielding to a block
   # and returning true when the block finds a match. `contains` does not cross
   # scope boundaries.
-  contains: (block) ->
+  contains: (block, arg) ->
     contains = no
-    @traverseChildren false, (node) ->
-      if block node
-        contains = true
-        return false
+    @traverseChildren false, (node, arg) ->
+      if (rearg = block node, arg) is true then not contains = true else if arg? then rearg
+    , arg
     contains

   # Is this node of a certain type, or does it contain the type?
@@ -103,7 +102,11 @@
   # Convenience for the most common use of contains. Does the node contain
   # a pure statement?
   containsPureStatement: ->
-    @isPureStatement() or @contains (node) -> node.isPureStatement()
+    @isPureStatement() or @contains (node, func) ->
+      func(node) or if node instanceof While or node instanceof For
+        (node) -> node instanceof Return
+      else func
+    , (node) -> node.isPureStatement()

   # `toString` representation of the node, for inspecting the parse tree.
   # This is what `coffee --nodes` prints out.
@@ -126,10 +129,10 @@
     @eachChild (node) -> nodes.push node
     nodes

-  traverseChildren: (crossScope, func) ->
+  traverseChildren: (crossScope, func, arg) ->
     @eachChild (child) ->
-      return false if func(child) is false
-      child.traverseChildren crossScope, func
+      return false if (arg = func child, arg) is false
+      child.traverseChildren crossScope, func, arg

   invert: -> new Op '!', this


first little bit of commenting the nodes.coffee -- with some slight refactors

--- nodes.coffee
+++ nodes.coffee
@@ -1,91 +1,68 @@
+# `nodes.coffee` contains all of the node classes for the syntax tree. Most
+# nodes are created as the result of actions in the [grammar](grammar.html),
+# but some are created by other nodes as a method of code generation. To convert
+# the syntax tree into a string of JavaScript code, call `compile()` on the root.
+
+# Set up for both **Node.js** and the browser, by
+# including the [Scope](scope.html) class.
 if process?
   process.mixin require 'scope'
 else
   this.exports: this

-# Some helper functions
-
-# Tabs are two spaces for pretty printing.
-TAB: '  '
-TRAILING_WHITESPACE: /\s+$/gm
-
-# Keep the identifier regex in sync with the Lexer.
-IDENTIFIER:   /^[a-zA-Z$_](\w|\$)*$/
-
-
-# Merge objects.
-merge: (options, overrides) ->
-  fresh: {}
-  (fresh[key]: val) for key, val of options
-  (fresh[key]: val) for key, val of overrides if overrides
-  fresh
-
-# Trim out all falsy values from an array.
-compact: (array) -> item for item in array when item
-
-# Return a completely flattened version of an array.
-flatten: (array) ->
-  memo: []
-  for item in array
-    if item instanceof Array then memo: memo.concat(item) else memo.push(item)
-  memo
-
-# Delete a key from an object, returning the value.
-del: (obj, key) ->
-  val: obj[key]
-  delete obj[key]
-  val
-
-# Quickie helper for a generated LiteralNode.
-literal: (name) ->
-  new LiteralNode(name)
-
-# Mark a node as a statement, or a statement only.
+# Helper function that marks a node as a JavaScript *statement*, or as a
+# *pure_statement*. Statements must be wrapped in a closure when used as an
+# expression, and nodes tagged as *pure_statement* cannot be closure-wrapped
+# without losing their meaning.
 statement: (klass, only) ->
   klass::is_statement: -> true
-  (klass::is_statement_only: -> true) if only
-
-
-# The abstract base class for all CoffeeScript nodes.
-# All nodes are implement a "compile_node" method, which performs the
-# code generation for that node. To compile a node, call the "compile"
-# method, which wraps "compile_node" in some extra smarts, to know when the
-# generated code should be wrapped up in a closure. An options hash is passed
-# and cloned throughout, containing messages from higher in the AST,
-# information about the current scope, and indentation level.
+  (klass::is_pure_statement: -> true) if only
+
+# The **BaseNode** is the abstract base class for all nodes in the syntax tree.
+# Each subclass implements the `compile_node` method, which performs the
+# code generation for that node. To compile a node to JavaScript,
+# call `compile` on it, which wraps `compile_node` in some generic extra smarts,
+# to know when the generated code needs to be wrapped up in a closure.
+# An options hash is passed and cloned throughout, containing information about
+# the environment from higher in the tree (such as if a returned value is
+# being requested by the surrounding function), information about the current
+# scope, and indentation level.
 exports.BaseNode: class BaseNode

-  # This is extremely important -- we convert JS statements into expressions
-  # by wrapping them in a closure, only if it's possible, and we're not at
+  # Common logic for determining whether to wrap this node in a closure before
+  # compiling it, or to compile directly. We need to wrap if this node is a
+  # *statement*, and it's not a *pure_statement*, and we're not at
   # the top level of a block (which would be unnecessary), and we haven't
-  # already been asked to return the result.
+  # already been asked to return the result (because statements know how to
+  # return results).
   compile: (o) ->
     @options: merge o or {}
-    @indent:  o.indent
+    @tab:     o.indent
     del @options, 'operation' unless @operation_sensitive()
     top:      if @top_sensitive() then @options.top else del @options, 'top'
-    closure:  @is_statement() and not @is_statement_only() and not top and
+    closure:  @is_statement() and not @is_pure_statement() and not top and
               not @options.returns and not (this instanceof CommentNode) and
-              not @contains (node) -> node.is_statement_only()
+              not @contains (node) -> node.is_pure_statement()
     if closure then @compile_closure(@options) else @compile_node(@options)

-  # Statements converted into expressions share scope with their parent
-  # closure, to preserve JavaScript-style lexical scope.
+  # Statements converted into expressions via closure-wrapping share a scope
+  # object with their parent closure, to preserve the expected lexical scope.
   compile_closure: (o) ->
-    @indent: o.indent
+    @tab: o.indent
     o.shared_scope: o.scope
-    ClosureNode.wrap(this).compile(o)
+    ClosureNode.wrap(this).compile o

   # If the code generation wishes to use the result of a complex expression
-  # in multiple places, ensure that the expression is only ever evaluated once.
+  # in multiple places, ensure that the expression is only ever evaluated once,
+  # by assigning it to a temporary variable.
   compile_reference: (o) ->
-    reference: literal(o.scope.free_variable())
-    compiled:  new AssignNode(reference, this)
+    reference: literal o.scope.free_variable()
+    compiled:  new AssignNode reference, this
     [compiled, reference]

-  # Quick short method for the current indentation level, plus tabbing in.
+  # Convenience method to grab the current indentation level, plus tabbing in.
   idt: (tabs) ->
-    idt: (@indent || '')
+    idt: @tab or ''
     idt += TAB for i in [0...(tabs or 0)]
     idt

@@ -111,7 +88,7 @@
   unwrap:               -> this
   children:             []
   is_statement:         -> false
-  is_statement_only:    -> false
+  is_pure_statement:    -> false
   top_sensitive:        -> false
   operation_sensitive:  -> false

@@ -157,7 +134,7 @@

   # If this is the top-level Expressions, wrap everything in a safety closure.
   compile_root: (o) ->
-    o.indent: @indent: indent: if o.no_wrap then '' else TAB
+    o.indent: @tab: if o.no_wrap then '' else TAB
     o.scope: new Scope(null, this, null)
     code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
     code: code.replace(TRAILING_WHITESPACE, '')
@@ -168,23 +145,23 @@
   compile_with_declarations: (o) ->
     code: @compile_node(o)
     args: @contains (node) -> node instanceof ValueNode and node.is_arguments()
-    code: "${@idt()}arguments = Array.prototype.slice.call(arguments, 0);\n$code" if args
-    code: "${@idt()}var ${o.scope.compiled_assignments()};\n$code"  if o.scope.has_assignments(this)
-    code: "${@idt()}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this)
+    code: "${@tab}arguments = Array.prototype.slice.call(arguments, 0);\n$code" if args
+    code: "${@tab}var ${o.scope.compiled_assignments()};\n$code"  if o.scope.has_assignments(this)
+    code: "${@tab}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this)
     code

   # Compiles a single expression within the expressions body.
   compile_expression: (node, o) ->
-    @indent: o.indent
+    @tab: o.indent
     stmt:    node.is_statement()
     # We need to return the result if this is the last node in the expressions body.
-    returns: del(o, 'returns') and @is_last(node) and not node.is_statement_only()
+    returns: del(o, 'returns') and @is_last(node) and not node.is_pure_statement()
     # Return the regular compile of the node, unless we need to return the result.
     return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
     # If it's a statement, the node knows how to return itself.
     return node.compile(merge(o, {returns: true})) if node.is_statement()
     # Otherwise, we can just return the value of the expression.
-    return "${@idt()}return ${node.compile(o)};"
+    return "${@tab}return ${node.compile(o)};"

 # Wrap up a node as an Expressions, unless it already is one.
 Expressions.wrap: (nodes) ->
@@ -207,7 +184,7 @@
   is_statement: ->
     @value is 'break' or @value is 'continue'

-  is_statement_only: LiteralNode::is_statement
+  is_pure_statement: LiteralNode::is_statement

   compile_node: (o) ->
     idt: if @is_statement() then @idt() else ''
@@ -227,7 +204,7 @@

   compile_node: (o) ->
     return @expression.compile(merge(o, {returns: true})) if @expression.is_statement()
-    "${@idt()}return ${@expression.compile(o)};"
+    "${@tab}return ${@expression.compile(o)};"

 statement ReturnNode, true

@@ -308,7 +285,7 @@
     this

   compile_node: (o) ->
-    "${@idt()}//" + @lines.join("\n${@idt()}//")
+    "$@tab//" + @lines.join("\n$@tab//")

 statement CommentNode

@@ -426,10 +403,10 @@
     @exclusive: !!exclusive

   compile_variables: (o) ->
-    @indent: o.indent
+    @tab: o.indent
     [@from_var, @to_var]: [o.scope.free_variable(), o.scope.free_variable()]
     [from, to]:           [@from.compile(o), @to.compile(o)]
-    "$@from_var = $from; $@to_var = $to;\n${@idt()}"
+    "$@from_var = $from; $@to_var = $to;\n$@tab"

   compile_node: (o) ->
     return    @compile_array(o) unless o.index
@@ -556,7 +533,7 @@
       else
         "$code, "
     objects: objects.join('')
-    ending: if objects.indexOf('\n') >= 0 then "\n${@idt()}]" else ']'
+    ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
     "[$objects$ending"

@@ -566,7 +543,7 @@

   wrap: (array, expressions) ->
     expr: expressions.unwrap()
-    return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
+    return expressions if expr.is_pure_statement() or expr.contains (n) -> n.is_pure_statement()
     Expressions.wrap([new CallNode(
       new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
     )])
@@ -621,9 +598,9 @@
     return "$name: $val" if @context is 'object'
     o.scope.find name unless @is_value() and @variable.has_properties()
     val: "$name = $val"
-    return "${@idt()}$val;" if stmt
+    return "$@tab$val;" if stmt
     val: "($val)" if not top or o.returns
-    val: "${@idt()}return $val" if o.returns
+    val: "${@tab}return $val" if o.returns
     val

   # Implementation of recursive pattern matching, when assigning array or
@@ -632,7 +609,7 @@
   compile_pattern_match: (o) ->
     val_var: o.scope.free_variable()
     value: if @value.is_statement() then ClosureNode.wrap(@value) else @value
-    assigns: ["${@idt()}$val_var = ${ value.compile(o) };"]
+    assigns: ["$@tab$val_var = ${ value.compile(o) };"]
     o.top: true
     o.as_statement: true
     for obj, i in @variable.base.objects
@@ -646,7 +623,7 @@
         val: new ValueNode(literal(val_var), [new access_class(idx)])
       assigns.push(new AssignNode(obj, val).compile(o))
     code: assigns.join("\n")
-    code += "\n${@idt()}return ${ @variable.compile(o) };" if o.returns
+    code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns
     code

   compile_splice: (o) ->
@@ -691,7 +668,7 @@
     func: "($func)" if top and not @bound
     return func unless @bound
     inner: "(function$name_part() {\n${@idt(2)}return __func.apply(__this, arguments);\n${@idt(1)}});"
-    "(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n${@idt()}})(this)"
+    "(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)"

   top_sensitive: ->
     true
@@ -755,13 +732,13 @@
     set:        ''
     if not top
       rvar:     o.scope.free_variable()
-      set:      "${@idt()}$rvar = [];\n"
+      set:      "$@tab$rvar = [];\n"
       @body:    PushNode.wrap(rvar, @body) if @body
-    post:       if returns then "\n${@idt()}return $rvar;" else ''
-    pre:        "$set${@idt()}while ($cond)"
+    post:       if returns then "\n${@tab}return $rvar;" else ''
+    pre:        "$set${@tab}while ($cond)"
     return      "$pre null;$post" if not @body
     @body:      Expressions.wrap([new IfNode(@filter, @body)]) if @filter
-    "$pre {\n${ @body.compile(o) }\n${@idt()}}$post"
+    "$pre {\n${ @body.compile(o) }\n$@tab}$post"

 statement WhileNode

@@ -845,9 +822,9 @@
     o.top:        true
     attempt_part: @attempt.compile(o)
     error_part:   if @error then " (${ @error.compile(o) }) " else ' '
-    catch_part:   "${ (@recovery or '') and ' catch' }$error_part{\n${ @recovery.compile(o) }\n${@idt()}}"
-    finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n${@idt()}}"
-    "${@idt()}try {\n$attempt_part\n${@idt()}}$catch_part$finally_part"
+    catch_part:   "${ (@recovery or '') and ' catch' }$error_part{\n${ @recovery.compile(o) }\n$@tab}"
+    finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n$@tab}"
+    "${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part"

 statement TryNode

@@ -860,7 +837,7 @@
     @children: [@expression: expression]

   compile_node: (o) ->
-    "${@idt()}throw ${@expression.compile(o)};"
+    "${@tab}throw ${@expression.compile(o)};"

 statement ThrowNode, true

@@ -944,7 +921,7 @@
       for_part:     "$index_var = 0, $for_part, $index_var++"
     else
       index_var:    null
-      source_part:  "$svar = ${ @source.compile(o) };\n${@idt()}"
+      source_part:  "$svar = ${ @source.compile(o) };\n$@tab"
       var_part:     "$body_dent$name = $svar[$ivar];\n" if name
       if not @object
         lvar:       scope.free_variable()
@@ -963,11 +940,11 @@
     if @object
       o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
       for_part: "$ivar in $svar) { if (__hasProp.call($svar, $ivar)"
-    return_result:  "\n${@idt()}$return_result;" unless top_level
+    return_result:  "\n$@tab$return_result;" unless top_level
     body:           body.compile(merge(o, {indent: body_dent, top: true}))
     vars:           if range then name else "$name, $ivar"
     close:          if @object then '}}\n' else '}\n'
-    "$set_result${source_part}for ($for_part) {\n$var_part$body\n${@idt()}$close${@idt()}$return_result"
+    "$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$@tab$return_result"

 statement ForNode

@@ -1054,12 +1031,12 @@
     com_dent:     if child then @idt() else ''
     prefix:       if @comment then "${ @comment.compile(cond_o) }\n$com_dent" else ''
     body:         Expressions.wrap([@body]).compile(o)
-    if_part:      "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n${@idt()}}"
+    if_part:      "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n$@tab}"
     return if_part unless @else_body
     else_part: if @is_chain()
       ' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true}))
     else
-      " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n${@idt()}}"
+      " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}"
     "$if_part$else_part"

   # Compile the IfNode into a ternary operator.
@@ -1067,3 +1044,43 @@
     if_part:    @condition.compile(o) + ' ? ' + @body.compile(o)
     else_part:  if @else_body then @else_body.compile(o) else 'null'
     "$if_part : $else_part"
+
+# Constants
+# ---------
+
+# Tabs are two spaces for pretty printing.
+TAB: '  '
+TRAILING_WHITESPACE: /\s+$/gm
+
+# Keep the identifier regex in sync with the Lexer.
+IDENTIFIER:   /^[a-zA-Z$_](\w|\$)*$/
+
+# Utility Functions
+# -----------------
+
+# Merge objects.
+merge: (options, overrides) ->
+  fresh: {}
+  (fresh[key]: val) for key, val of options
+  (fresh[key]: val) for key, val of overrides if overrides
+  fresh
+
+# Trim out all falsy values from an array.
+compact: (array) -> item for item in array when item
+
+# Return a completely flattened version of an array.
+flatten: (array) ->
+  memo: []
+  for item in array
+    if item instanceof Array then memo: memo.concat(item) else memo.push(item)
+  memo
+
+# Delete a key from an object, returning the value.
+del: (obj, key) ->
+  val: obj[key]
+  delete obj[key]
+  val
+
+# Quickie helper for a generated LiteralNode.
+literal: (name) ->
+  new LiteralNode(name)


adjusts specs for new path checking (we really need CI or I need to stop forgetting to run the specs... :D)

--- spec/juno-spec.coffee
+++ spec/juno-spec.coffee
@@ -24,19 +24,17 @@
     it "can validate the existence of a julia binary", ->
       path = require 'path'
       checkPath = (p) -> juno.connection.process.checkPath p
-      waitsForPromise ->
-        checkPath(path.join(__dirname, "juno-spec.coffee")).then (exists) ->
-          expect(exists).toBe(true)
-      waitsForPromise ->
-        checkPath(path.join(__dirname, "doesn't-exist.coffee")).then (exists) ->
-          expect(exists).toBe(false)
+      waitsFor (done) ->
+        checkPath(path.join(__dirname, "juno-spec.coffee")).then -> done()
+      waitsFor (done) ->
+        checkPath(path.join(__dirname, "doesn't-exist.coffee")).catch -> done()

     it "can validate the existence of a julia command", ->
       checkPath = (p) -> juno.connection.process.checkPath p
-      waitsForPromise ->
-        checkPath("julia").then (exists) -> expect(exists).toBe(true)
-      waitsForPromise ->
-        checkPath("nojulia").then (exists) -> expect(exists).toBe(false)
+      waitsFor (done) ->
+        checkPath("julia").then -> done()
+      waitsFor (done) ->
+        checkPath("nojulia").catch -> done()

   describe "when booting the client", ->
     bootPromise = null


further refactors to Class

further refactors to Class

--- nodes.coffee
+++ nodes.coffee
@@ -686,6 +686,14 @@
         node.klass    = name
         node.context  = name if node.bound

+  # Ensure that all functions bound to the instance are proxied in the
+  # constructor.
+  addBoundFunctions: (o) ->
+    if @boundFuncs.length
+      for bvar in @boundFuncs
+        bname = bvar.compile o
+        @ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);"
+
   # Merge the properties from a top-level object as prototypal properties
   # on the class.
   addProperties: (node, name) ->
@@ -702,26 +710,17 @@
           func.bound = no
       assign

-  # Instead of generating the JavaScript string directly, we build up the
-  # equivalent syntax tree and compile that, in pieces. You can see the
-  # constructor, property assignments, and inheritance getting built out below.
-  compileNode: (o) ->
-    ctor = null
-
-    decl  = @determineName()
-    name  = decl or @name or '_Class'
-    lname = new Literal name
-    @setContext name
-
+  # Walk the body of the class, looking for prototype properties to be converted.
+  walkBody: (name) ->
     for node, i in exps = @body.expressions
       if node instanceof Value and node.isObject(true)
         exps[i] = compact @addProperties node, name
       else if node instanceof Code
-        if ctor
+        if @ctor
           throw new Error 'cannot define more than one constructor in a class'
         if node.bound
           throw new Error 'cannot define a constructor as a bound function'
-        ctor = node
+        @ctor = node
         exps[i] = null
       else
         node.traverseChildren false, (n2) =>
@@ -730,23 +729,33 @@
               if expr2 instanceof Value and expr2.isObject(true)
                 n2.expressions[j] = compact @addProperties expr2, name
             n2.expressions = flatten n2.expressions
-
     @body.expressions = exps = compact flatten exps
-    unless ctor
-      ctor = new Code
-      if @parent
-        ctor.body.push new Call 'super', [new Splat new Literal 'arguments']
-    ctor.ctor     = ctor.name = name
-    ctor.klass    = null
-    ctor.noReturn = yes
-    exps.unshift new Extends lname, @parent if @parent
-    exps.unshift ctor
-    exps.push lname
-
-    if @boundFuncs.length
-      for bvar in @boundFuncs
-        bname = bvar.compile o
-        ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);"
+
+  # Make sure that a constructor is defined for the class, and properly
+  # configured.
+  ensureConstructor: (name) ->
+    if not @ctor
+      @ctor = new Code
+      @ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] if @parent
+    @ctor.ctor     = @ctor.name = name
+    @ctor.klass    = null
+    @ctor.noReturn = yes
+
+  # Instead of generating the JavaScript string directly, we build up the
+  # equivalent syntax tree and compile that, in pieces. You can see the
+  # constructor, property assignments, and inheritance getting built out below.
+  compileNode: (o) ->
+    decl  = @determineName()
+    name  = decl or @name or '_Class'
+    lname = new Literal name
+
+    @setContext name
+    @walkBody name
+    @ensureConstructor name
+    @body.expressions.unshift new Extends lname, @parent if @parent
+    @body.expressions.unshift @ctor
+    @body.expressions.push lname
+    @addBoundFunctions o

     klass = new Parens new Call(new Code [], @body), true
     klass = new Assign new Value(lname), klass if decl and @variable?.isComplex()


Suppress deserialization warning during pane spec

--- spec/pane-spec.coffee
+++ spec/pane-spec.coffee
@@ -732,6 +732,8 @@
       expect(newPane.activeItem).toEqual editSession2

     it "does not show items that cannot be deserialized", ->
+      spyOn(console, 'warn')
+
       pane.showItem(view2)
       paneState = pane.serialize()
       paneState.get('items').set(pane.items.indexOf(view2), {deserializer: 'Bogus'}) # nuke serialized state of active item


Check whether file exists without expecting an exception if not.

--- lib/loader.coffee
+++ lib/loader.coffee
@@ -3,6 +3,7 @@
 path = require('path')
 normalize = path.normalize
 fs = require('fs')
+existsSync = `(fs.existsSync) ? fs.existsSync : path.existsSync` # node 0.8 moved to fs
 stripExtension= require('./strip_extension')
 moduleExtensions= ['.js','.coffee','.json']

@@ -19,11 +20,7 @@
   return (b < a) - (a < b)

 isFileSync= (name)->
-  try
-    fs.statSync(name).isFile()
-  catch e
-    throw e unless e.code == 'ENOENT'
-    false
+  existsSync(name) && fs.statSync(name).isFile()

 # Would adding the proper extension to the given name find a file whose extension
 # suggested that it could be a module?


Extract function creation from module class

--- lib/module.coffee
+++ lib/module.coffee
@@ -1,5 +1,21 @@
+Ex2js = {}
+Ex2js.Function = require('../lib/function')
+
 class Module
   constructor: (@name) ->
+  methods: []
+  exports: 'exports'
+  returnAst: ->
+    {
+      type: 'ReturnStatement'
+      argument:
+        type: 'Identifier'
+        name: @exports
+    }
+  functionAst: ->
+    func = new Ex2js.Function([@exports])
+    func.body.push @returnAst()
+    func.ast()

   ast: ->
     type: 'VariableDeclaration'
@@ -11,28 +27,7 @@
         name: @name
       init:
         type: 'CallExpression'
-        callee:
-          type: 'FunctionExpression'
-          id: null
-          params: [
-            type: 'Identifier'
-            name: 'exports'
-          ]
-          defaults: []
-          body:
-            type: 'BlockStatement'
-            rest: null
-            generator: false
-            expression: false
-            body: [
-              # Module functions and variables go here
-              {
-                type: 'ReturnStatement'
-                argument:
-                  type: 'Identifier'
-                  name: 'exports'
-              }
-            ]
+        callee: @functionAst()
         arguments: [
           type: 'LogicalExpression'
           operator: '||'


Better description

Better description

--- lib/pg-hoff.coffee
+++ lib/pg-hoff.coffee
@@ -41,7 +41,7 @@
         pascaliseAutocompletions:
             type: 'boolean'
             default: true
-            description: 'Replace text in autocompletions that matches ^[a-z] and [_][a-z] with uppercase'
+            description: 'user_name becomes User_Name'
         unQuoteFunctionNames:
             type: 'boolean'
             default: true


add option searchAllPanes (open exist editor)

--- lib/related.coffee
+++ lib/related.coffee
@@ -17,10 +17,12 @@
     atom.commands.add 'atom-workspace', 'titanium:openStyle': => @openFile('tss')
     atom.commands.add 'atom-workspace', 'titanium:openController': => @openFile('js')

+
   openFile: (type)->
     currentFilePath = atom.workspace.getActiveTextEditor().getPath()
-
+
     targetPath = currentFilePath.replace alloyRegExp, (match, p1, p2, p3, offset, string)->
       return "/#{alloyDirectoryMap[type]}/#{p2}.#{type}"
-
-    atom.workspace.open targetPath
+
+    atom.workspace.open targetPath,
+      searchAllPanes : true


Fix crash in session.deserialize

Fix crash in session.deserialize

Resolves #243

--- lib/session.coffee
+++ lib/session.coffee
@@ -1541,8 +1541,8 @@
     provisionsCache.set(sessionContext, provisions)
     virtualParametersCache.set(sessionContext, virtualParameters)

-    for d in sessionContext.declarations
-      common.addPathMeta(d[0])
+    for decs in sessionContext.declarations
+      common.addPathMeta(d[0]) for d in decs

     deviceData = initDeviceData()


added back logger

added back logger

--- lib/server.coffee
+++ lib/server.coffee
@@ -51,7 +51,7 @@
       , q()
     .then =>
       # configure middleware
-      # @app.use express.logger()
+      @app.use express.logger()
       @app.use @router.respond.bind(@router)
       @app.use @router.respond_error.bind(@router)
       @app.use @router.not_found.bind(@router)


missing typo

missing typo

--- addon/mixins/tile-core.coffee
+++ addon/mixins/tile-core.coffee
@@ -2,6 +2,7 @@
 `import DS from 'ember-data'`

 {computed} = Ember
+{oneWay} = computed

 TileCoreMixin = Ember.Mixin.create
   type: "tile"


Fix scope

Fix scope

--- grammars/jison.cson
+++ grammars/jison.cson
@@ -456,7 +456,7 @@
 injections:
   # Putting parentheses around the selector following L: may be required; see
   # https://github.com/github/linguist/issues/3612.
-  "L:(meta.action.jison - (comment | string)), source.js.embedded.source":
+  "L:(meta.action.jison - (comment | string)), source.js.embedded.jison":
     patterns: [
       {
         name:  "variable.language.semantic-value.jison"


Add support for factor variables. Fixes #70

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -15,13 +15,13 @@
     'include': '#unicode-regex-functions'
   }
   {
+    'include': '#constants'
+  }
+  {
     'include': '#functions'
   }
   {
     'include': '#comments'
-  }
-  {
-    'include': '#constants'
   }
   {
     'include': '#subscripts'
@@ -842,7 +842,7 @@
   'docblockr-comment':
     'patterns': [
       {
-        'match': '(?<!\\w)(@(error|ERROR))\\b'
+        'match': '(?<!\\w)(@(error|ERROR|Error))\\b'
         'captures':
           '1':
             'name': 'invalid.illegal.name.stata'
@@ -1345,6 +1345,9 @@
   'constants':
     'patterns': [
       {
+        'include': '#factorvariables'
+      }
+      {
         'match': '\\b(?i:(\\d+\\.\\d*(e[\\-\\+]?\\d+)?))(?=[^a-zA-Z_])'
         'name': 'constant.numeric.float.stata'
       }
@@ -1367,6 +1370,74 @@
       {
         'match': '\\b_all\\b'
         'name': 'constant.language.allvars.stata'
+      }
+    ]
+  'factorvariables':
+    'patterns': [
+      {
+        'match': '\\b(i|c|o)\\.(?=[a-zA-Z_]|\\([a-zA-Z_])'
+        'name': 'constant.language.factorvars.stata'
+      }
+      {
+        'match': '\\b(i?b)((\\d+)|n)\\.(?=[a-zA-Z_]|\\([a-zA-Z_])'
+        'captures':
+          '0':
+            'name': 'constant.language.factorvars.stata'
+          '3':
+            'patterns': [
+              {
+                'include': '#constants'
+              }
+            ]
+      }
+      {
+        'match': '\\b(i?b)(\\()(#\\d+|first|last|freq)(\\))\\.(?=[a-zA-Z_]|\\([a-zA-Z_])'
+        'captures':
+          '0':
+            'name': 'constant.language.factorvars.stata'
+          '2':
+            'name': 'keyword.operator.parentheses.stata'
+          '3':
+            'patterns': [
+              {
+                'include': '#constants'
+              }
+              {
+                'include': '#operators'
+              }
+            ]
+          '4':
+            'name': 'keyword.operator.parentheses.stata'
+      }
+      {
+        'match': '\\b(i?o?)(\\d+)\\.(?=[a-zA-Z_]|\\([a-zA-Z_])'
+        'captures':
+          '0':
+            'name': 'constant.language.factorvars.stata'
+          '2':
+            'patterns': [
+              {
+                'include': '#constants'
+              }
+            ]
+      }
+      {
+        'match': '\\b(i?o?)(\\()(.*?)(\\))(\\.)(?=[a-zA-Z_]|\\([a-zA-Z_])'
+        'captures':
+          '1':
+            'name': 'constant.language.factorvars.stata'
+          '2':
+            'name': 'keyword.operator.parentheses.stata'
+          '3':
+            'patterns': [
+              {
+                'include': '$self'
+              }
+            ]
+          '4':
+            'name': 'keyword.operator.parentheses.stata'
+          '5':
+            'name': 'constant.language.factorvars.stata'
       }
     ]
   'operators':


fan speed control

fan speed control

--- grow-systems.coffee
+++ grow-systems.coffee
@@ -84,7 +84,7 @@
 			@time.delay delay, =>
 				subState = !subState
 				fan.power subState
-				controlSpeed()
+				@controlSpeed()

 		@property 'power',


add knowledge base article to alert

add knowledge base article to alert

--- lib/windows.coffee
+++ lib/windows.coffee
@@ -12,12 +12,11 @@
   installLocation = path.join(process.env['ProgramFiles'], 'Learn IDE')

   if (fs.existsSync(installLocationX86) || fs.existsSync(installLocation))
-    alert('old copy of Learn IDE detected')
+    alert("You appear to have two versions of the Learn IDE installed. This usually happens when upgrading from v1.9 to v2. Please uninstall v1.9 following the instructions at https://theflatironschool.zendesk.com/hc/en-us/articles/235711268")
     v1InitFile = path.join(atom.getConfigDirPath(), 'ide-init.coffee')
     if fs.existsSync(v1InitFile)
       contents = fs.readFileSync(v1InitFile, 'utf8')
       return if contents.indexOf(id) > -1
-      alert('no id present injecting code')
       codeToInject = fs.readFileSync(__filename, 'utf8') + '\n\nv1pkg = atom.packages.loadPackage("integrated-learn-environment")\nif v1pkg\n  v1pkg.enable()\n'
       fs.writeFileSync(v1InitFile, contents + '\n' + codeToInject + '\n')
 )()


Changed setRelatedContent to only allow content to be passed in

--- src/code/providers/provider-interface.coffee
+++ src/code/providers/provider-interface.coffee
@@ -29,7 +29,7 @@
   setContent: (content, options = {}) ->
     @_ = content
     @dirty = if options.hasOwnProperty('dirty') then options.dirty else true
-    @_
+    @

   getText: -> if isString(@_) then @_ else JSON.stringify @_
   initText: (text) -> @setText text, {dirty: false}
@@ -44,18 +44,16 @@
     super content
     {@relatedContent} = options
     @relatedContent or= {}
-    @dirtyRelatedContent = {}

   getRelatedContent: (name) ->
     @relatedContent[name]
-  initRelatedContent: (name, relatedContent) ->
-    @setRelatedContent name, relatedContent, {dirty: false}
-  setRelatedContent: (name, relatedContent, options = {}) ->
-    if not (relatedContent instanceof CloudRelatedContent)
-      relatedContent = new CloudRelatedContent relatedContent, {mainContent: @}
-    @relatedContent[name] = relatedContent
-    @dirtyRelatedContent[name] = if options.hasOwnProperty('dirty') then options.dirty else true
-    relatedContent
+  initRelatedContent: (name, content) ->
+    @setRelatedContent name, content, {dirty: false}
+  setRelatedContent: (name, content, options = {}) ->
+    if not @relatedContent.hasOwnProperty name
+      @relatedContent[name] = new CloudRelatedContent null, {mainContent: @}
+    @relatedContent[name].setContent content, options
+    @

 class CloudRelatedContent extends BaseCloudContent
   constructor: (content, options = {}) ->


Improved reconnect.

Improved reconnect.

--- app/assets/javascripts/app/lib/websocket.js.coffee
+++ app/assets/javascripts/app/lib/websocket.js.coffee
@@ -90,6 +90,9 @@
       console.log( "onopen" )

       # close error message if exists
+      if @error_delay
+        clearTimeout(@error_delay)
+        @error_delay = undefined
       if @error
         @error.modalHide()
         @error = undefined
@@ -107,7 +110,7 @@

     @ws.onmessage = (e) =>
       pipe = JSON.parse( e.data )
-      console.log( "ws:onmessage", pipe )
+      console.log( 'ws:onmessage', pipe )

       # go through all blocks
       for item in pipe
@@ -136,17 +139,19 @@
       @send(data)

     @ws.onclose = (e) =>
-      console.log( "onclose", e )
+      console.log( 'onclose', e )

-      # show error message
+      # show error message, first try to reconnect
       if !@error
-        @error = new App.ErrorModal(
-          message: 'No connection to websocket, trying to reconnect...'
-        )
+        message = =>
+          @error = new App.ErrorModal(
+            message: 'No connection to websocket, trying to reconnect...'
+          )
+        @error_delay = @delay message, 7000

-      # try reconnect after 5 sec.
-      @delay @connect, 5000
+      # try reconnect after 4.5 sec.
+      @delay @connect, 4500

     @ws.onerror = ->
-      console.log( "onerror" )
+      console.log( 'onerror' )


Fix response loading twice on fill_out start

--- app/assets/javascripts/components/feed_view.coffee
+++ app/assets/javascripts/components/feed_view.coffee
@@ -2,9 +2,6 @@

 @FeedView = React.createClass
   displayName: 'FeedView'
-
-  componentWillMount: ->
-    Screensmart.store.dispatch Screensmart.Actions.updateResponse()

   render: ->
     div


Add manageable to search params in projects

--- app/assets/javascripts/controllers/projects.coffee
+++ app/assets/javascripts/controllers/projects.coffee
@@ -3,6 +3,7 @@

   defaultSearchFields:
     query: null
+    manageable: true
     between_created_at:
       starts_at: null
       ends_at: null


Adding more X headers.

Adding more X headers.

--- grammars/eml.cson
+++ grammars/eml.cson
@@ -68,6 +68,8 @@
     { 'match': '^.*(?i)resent-to.*?:', 'name': 'constant.other.eml' },

     # X-Headers
+    { 'match': '^(?i)auto-submitted.*?:', 'name': 'constant.other.eml' },
+    { 'match': '^(?i)errors-to.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-antiabuse.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-auto-response-suppress.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-bulkmail.*?:', 'name': 'constant.other.eml' },
@@ -83,8 +85,6 @@
     { 'match': '^(?i)x-forefront-antispam-report.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-forefront-prvs.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-forwarded-for.*?:', 'name': 'constant.other.eml' },
-    { 'match': '^(?i)x-forwarded-for.*?:', 'name': 'constant.other.eml' },
-    { 'match': '^(?i)x-forwarded-to.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-forwarded-to.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-gm-message-state.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-gmail-received.*?:', 'name': 'constant.other.eml' },
@@ -119,8 +119,9 @@
     { 'match': '^(?i)x-originating-email.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-originating-ip.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-originatororg.*?:', 'name': 'constant.other.eml' },
+    { 'match': '^(?i)x-pstn-levels.*?:', 'name': 'constant.other.eml' },
+    { 'match': '^(?i)x-pstn-neptune.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-received.*?:', 'name': 'constant.other.eml' },
-    { 'match': '^(?i)x-report-abuse.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-report-abuse.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-sender.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-source-args.*?:', 'name': 'constant.other.eml' },
@@ -133,6 +134,7 @@
     { 'match': '^(?i)x-spam-zendesk-score.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-spam_bar.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-spam_report.*?:', 'name': 'constant.other.eml' },
+    { 'match': '^(?i)x-spam_report:.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-spam_score.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-spam_score_int.*?:', 'name': 'constant.other.eml' },
     { 'match': '^(?i)x-tmn.*?:', 'name': 'constant.other.eml' },


Add `.dformat` and `grap.defines` to Pic filetypes

--- grammars/pic.cson
+++ grammars/pic.cson
@@ -1,6 +1,12 @@
 name: "Pic"
 scopeName: "source.pic"
-fileTypes: ["chem", "pic"]
+fileTypes: [
+	"chem"
+	"dformat"
+	"grap.defines"
+	"grap.tex.defines"
+	"pic"
+]
 firstLineMatch: "^#[!/].*\\bpic2plot\\b"
 patterns: [
 	{include: "#binary"}


fix(kanban): delete unclassified userstories swimlane in regular kanban

--- app/coffee/modules/kanban/kanban-usertories.coffee
+++ app/coffee/modules/kanban/kanban-usertories.coffee
@@ -255,7 +255,7 @@
         @.refreshSwimlanes()

     refreshSwimlanes: () ->
-        if !@.swimlanes
+        if !@.swimlanes || !@.project.swimlanes
             return

         @.usByStatusSwimlanes = Immutable.Map()


Clean up tests

Clean up tests

--- test/browsers.coffee
+++ test/browsers.coffee
@@ -83,12 +83,10 @@
       browsers.selected.should.eql ['ie 3', 'ie 2', 'chrome 3', 'chrome 2']

     it 'has aliases', ->
-      ( new Browsers(data, ['fx 1'])    ).selected.should.eql ['ff 1']
       ( new Browsers(data, ['fx >= 1']) ).selected.should.eql ['ff 1']

     it 'ignores case', ->
-      ( new Browsers(data, ['Firefox 1'])    ).selected.should.eql ['ff 1']
-      ( new Browsers(data, ['Firefox >= 1']) ).selected.should.eql ['ff 1']
+      ( new Browsers(data, ['Firefox 1']) ).selected.should.eql ['ff 1']

   describe 'prefix()', ->


Update groc task configuration

Update groc task configuration

--- grunt/groc.coffee
+++ grunt/groc.coffee
@@ -3,4 +3,4 @@
     'src/**/*.js', 'README.md'
   ]
   options:
-    'out': 'doc/'
+    'out': 'pages/doc/'


Delay to avoid premature stream ending

Delay to avoid premature stream ending

--- components/SCDDetect.coffee
+++ components/SCDDetect.coffee
@@ -20,27 +20,31 @@
     out.write(chunk)
   stream.on 'end', () ->
     try
-      onEnd tmpFile, cascade, callback
+      # Delay it a bit to avoid premature stream ending
+      setTimeout () ->
+        onEnd tmpFile, cascade, callback
+      , 100
     catch e
       callback e
       tmpFile.unlink()

 onEnd = (tmpFile, cascade, callback) ->
   bin = path.join __dirname, '../build/Release/scddetect'
-  console.log tmpFile.path
   exec "#{bin} #{tmpFile.path} #{cascade}", (err, stdout, stderr) ->
-    console.log 'stdout', stdout
-    console.log 'stderr', stderr
-    console.log 'err', err
-    tmpFile.unlink()
+    if stderr
+      callback stderr
+      tmpFile.unlink()
+      return
     if err
       callback err
+      tmpFile.unlink()
       return
     else
       out = JSON.parse stdout
       if out.length > 1
         out.sort (a,b) -> return b.confidence-a.confidence
       callback null, out
+      tmpFile.unlink()

 exports.getComponent = ->
   c = new noflo.Component


Use a proper URL parser

Use a proper URL parser

--- components/StoreUser.coffee
+++ components/StoreUser.coffee
@@ -1,4 +1,5 @@
 noflo = require 'noflo'
+urlParser = require 'url'

 downloadAvatar = (avatarUrl, callback) ->
   return callback null, null unless avatarUrl
@@ -18,14 +19,17 @@
   req.send()

 cleanUpUrl = (out, callback) ->
-  [url, query] = window.location.href.split '?'
-  return callback() unless query
+  url = urlParser.parse window.location.href
+  # Clear query params, if any
+  delete url.search
+  newUrl = urlParser.format url
+  return callback() if newUrl is url
   if window.history?.replaceState
     # We can manipulate URL without reloading page
-    window.history.replaceState {}, 'clear', window.location.pathname
+    window.history.replaceState {}, 'clear', url.pathname
     return callback()
   # Old-school redirect
-  out.send url
+  out.send newUrl
   do callback
   return


Remove unused tests since setup is now handled by Cavalry

--- test/surveyor.coffee
+++ test/surveyor.coffee
@@ -444,61 +444,6 @@
     surveyor.propagateRoutingTable table, (err) ->
       assert.equal err, null
       done()
-  it 'should run setup jobs before spawning', (done) ->
-    model.slaves["setupTestSlave"] =
-      ip: "127.0.0.1"
-    model.manifest =
-      opts=
-        repo: "setupTest"
-        setup: [
-          "npm",
-          "install"
-        ]
-        command: [
-          "node",
-          "server.js"
-        ]
-        commit: '1'
-    server.once "request", (req, res) ->
-      req.on 'data', (data) ->
-        parsed = JSON.parse data.toString()
-        assert.equal req.url, "/#{util.apiVersion}/exec"
-        res.end JSON.stringify
-          code: 0
-        server.once "request", (req, res) ->
-          req.on 'data', (data) ->
-            parsed = JSON.parse data.toString()
-            assert.equal req.url, "/#{util.apiVersion}/spawn"
-            res.end()
-            done()
-    surveyor.spawn "setupTestSlave", opts, (err, info) ->
-  it 'shouldnt spawn if the setup job failed', (done) ->
-    model.slaves["setupTestSlave"] =
-      ip: "127.0.0.1"
-    model.manifest =
-      opts=
-        repo: "setupTest"
-        setup: [
-          "npm",
-          "install"
-        ]
-        command: [
-          "node",
-          "server.js"
-        ]
-        commit: '1'
-    server.once "request", (req, res) ->
-      req.on 'data', (data) ->
-        parsed = JSON.parse data.toString()
-        assert.equal req.url, "/#{util.apiVersion}/exec"
-        res.end JSON.stringify
-          code: 1
-        server.once "request", (req, res) ->
-          throw new Error "setup job recieved"
-    surveyor.spawn "setupTestSlave", opts, (err, info) ->
-      assert err instanceof Error
-      assert.equal info.data.code, 1
-      done()
   it 'should calculate load correctly', ->
     model.manifest =
       load:


Set default binder priorities. [#409]

Set default binder priorities. [#409]

3000: blocking binders (if, unless, each-*)
2000: two-way binders (value, checked, unchecked)
1000: function / event handler binders (on-*)

--- src/binders.coffee
+++ src/binders.coffee
@@ -31,10 +31,14 @@
 # property when the input is checked or unchecked (two-way binder).
 Rivets.public.binders.checked =
   publishes: true
+  priority: 2000
+
   bind: (el) ->
     Rivets.Util.bindEvent el, 'change', @publish
+
   unbind: (el) ->
     Rivets.Util.unbindEvent el, 'change', @publish
+
   routine: (el, value) ->
     if el.type is 'radio'
       el.checked = el.value?.toString() is value?.toString()
@@ -46,10 +50,14 @@
 # unchecked (two-way binder).
 Rivets.public.binders.unchecked =
   publishes: true
+  priority: 2000
+
   bind: (el) ->
     Rivets.Util.bindEvent el, 'change', @publish
+
   unbind: (el) ->
     Rivets.Util.unbindEvent el, 'change', @publish
+
   routine: (el, value) ->
     if el.type is 'radio'
       el.checked = el.value?.toString() isnt value?.toString()
@@ -60,6 +68,7 @@
 # (two-way binder).
 Rivets.public.binders.value =
   publishes: true
+  priority: 2000

   bind: (el) ->
     @event = if el.tagName is 'SELECT' then 'change' else 'input'
@@ -83,6 +92,7 @@
 # Inserts and binds the element and it's child nodes into the DOM when true.
 Rivets.public.binders.if =
   block: true
+  priority: 3000

   bind: (el) ->
     unless @marker?
@@ -120,6 +130,7 @@
 # (negated version of `if` binder).
 Rivets.public.binders.unless =
   block: true
+  priority: 3000

   bind: (el) ->
     Rivets.public.binders.if.bind.call @, el
@@ -136,6 +147,7 @@
 # Binds an event handler on the element.
 Rivets.public.binders['on-*'] =
   function: true
+  priority: 1000

   unbind: (el) ->
     Rivets.Util.unbindEvent el, @args[0], @handler if @handler
@@ -147,6 +159,7 @@
 # Appends bound instances of the element in place for each item in the array.
 Rivets.public.binders['each-*'] =
   block: true
+  priority: 3000

   bind: (el) ->
     unless @marker?


make moving things within a chapter in toc save properly

--- scripts/gh-book/opf-file.coffee
+++ scripts/gh-book/opf-file.coffee
@@ -47,7 +47,7 @@
       @tocNodes.on 'tree:add',    (model, collection, options) => @tocNodes.add model, options
       @tocNodes.on 'tree:remove', (model, collection, options) => @tocNodes.remove model, options

-      @getChildren().on 'tree:change add remove', (model, collection, options) =>
+      @getChildren().on 'change add remove tree:change tree:add tree:remove', (model, collection, options) =>
         setNavModel(options)
       @getChildren().on 'change reset', (collection, options) =>
         # HACK: `?` is because `inherits/container.add` calls `trigger('change')`


More CoffeeScript - like function call

More CoffeeScript - like function call

--- src/capsule.coffee
+++ src/capsule.coffee
@@ -4,7 +4,7 @@
     constructor: (fn, elem) ->
       [fn, elem] = [null, fn] if fn instanceof $

-      fn.call(@, @) if fn?
+      fn.call @, @ if fn?

       @data = {}