Start bracket and meta forwarding implementation

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -17,6 +17,9 @@
   ordered: false
   outputQ: []
   activateOnInput: true
+  forwardBrackets:
+    in: ['out', 'error']
+  bracketBuffer: {}

   constructor: (options) ->
     options = {} unless options
@@ -69,12 +72,28 @@

   isStarted: -> @started

+  # Ensures braket forwarding map is correct for the existing ports
+  prepareForwarding: ->
+    for inPort, outPorts of @forwardBrackets
+      unless inPort of @inPorts.ports
+        delete @forwardBrackets[inPort]
+        continue
+      tmp = []
+      for outPort in outPorts
+        tmp.push outPort if outPort of @outPorts.ports
+      if tmp.length is 0
+        delete @forwardBrackets[inPort]
+      else
+        @forwardBrackets[inPort] = tmp
+        @bracketBuffer[inPort] = []
+
   # Sets process handler function
   process: (handle) ->
     unless typeof handle is 'function'
       throw new Error "Process handler must be a function"
     unless @inPorts
       throw new Error "Component ports must be defined before process function"
+    @prepareForwarding()
     @handle = handle
     for name, port of @inPorts.ports
       do (name, port) =>
@@ -83,7 +102,12 @@
           @handleIP ip, port
     @

+  # Handles an incoming IP object
   handleIP: (ip, port) ->
+    if port.name of @forwardBrackets and
+    (ip.type is 'openBracket' or ip.type is 'closeBracket')
+      @bracketBuffer[port.name].push port.buffer.pop()
+      return
     return unless port.options.triggering
     result = {}
     input = new ProcessInput @inPorts, ip, @, port, result
@@ -97,17 +121,20 @@
   constructor: (@ports, @ip, @nodeInstance, @port, @result) ->
     @scope = @ip.scope

+  # Sets component state to `activated`
   activate: ->
     @result.__resolved = false
     if @nodeInstance.ordered
       @nodeInstance.outputQ.push @result

-  has: ->
+  # Returns true if a port (or ports joined by logical AND) has a new IP
+  has: (port = 'in') ->
     res = true
     res and= @ports[port].ready @scope for port in arguments
     res

-  get: ->
+  # Fetches IP object(s) for port(s)
+  get: (port = 'in') ->
     if @nodeInstance.ordered and
     @nodeInstance.activateOnInput and
     not ('__resolved' of @result)
@@ -115,7 +142,8 @@
     res = (@ports[port].get @scope for port in arguments)
     if arguments.length is 1 then res[0] else res

-  getData: ->
+  # Fetches `data` property of IP object(s) for given port(s)
+  getData: (port = 'in') ->
     ips = @get.apply this, arguments
     if arguments.length is 1
       return ips?.data ? undefined
@@ -125,15 +153,18 @@
   constructor: (@ports, @ip, @nodeInstance, @result) ->
     @scope = @ip.scope

+  # Sets component state to `activated`
   activate: ->
     @result.__resolved = false
     if @nodeInstance.ordered
       @nodeInstance.outputQ.push @result

+  # Checks if a value is an Error
   isError: (err) ->
     err instanceof Error or
     Array.isArray(err) and err.length > 0 and err[0] instanceof Error

+  # Sends an error object
   error: (err) ->
     multiple = Array.isArray err
     err = [err] unless multiple
@@ -145,6 +176,7 @@
     else
       throw e for e in err

+  # Sends a single IP object to a port
   sendIP: (port, packet) ->
     if typeof packet isnt 'object' or
     IP.types.indexOf(packet.type) is -1
@@ -158,6 +190,8 @@
     else
       @nodeInstance.outPorts[port].sendIP ip

+  # Sends packets for each port as a key in the map
+  # or sends Error or a list of Errors if passed such
   send: (outputMap) ->
     if @nodeInstance.ordered and
     not ('__resolved' of @result)
@@ -166,10 +200,28 @@
     for port, packet of outputMap
       @sendIP port, packet

+  # Alias for `complete()`
   sendDone: (outputMap) ->
+    @complete outputMap
+
+  # Sends the argument via `send()` and marks activation as `done()`
+  complete: (outputMap) ->
     @send outputMap
     @done()

+  # Makes a map-style component pass a result value to `out`
+  # keeping all IP metadata received from `in`,
+  # or modifying it if `options` is provided
+  pass: (data, options = {}) ->
+    unless 'out' of @ports
+      throw new Error 'output.pass() requires port "out" to be present'
+    for key, val of options
+      @ip[key] = val
+    @ip.data = data
+    @sendIP 'out', @ip
+    @done()
+
+  # Finishes process activation gracefully
   done: (error) ->
     @error error if error
     if @nodeInstance.ordered


Add missing Catalan, Arabic & Turkish for momentjs

--- app/assets/javascripts/darkswarm/all.js.coffee
+++ app/assets/javascripts/darkswarm/all.js.coffee
@@ -39,6 +39,9 @@
 #= require moment/pt.js
 #= require moment/ru.js
 #= require moment/sv.js
+#= require moment/ca.js
+#= require moment/ar.js
+#= require moment/tr.js
 #
 #= require modernizr
 #


Prevent display of negative data amounts.

--- app/assets/javascripts/filters/bytes.js.coffee
+++ app/assets/javascripts/filters/bytes.js.coffee
@@ -3,7 +3,7 @@
   ($window) ->
     SUFFIXES = [ 'B', 'KiB', 'MiB', 'GiB', 'TiB' ]
     (input) ->
-      value = parseInt(input || 0)
+      value = Math.max(parseInt(input || 0), 0)
       for suffix, i in SUFFIXES
         if value < 1024 || i == SUFFIXES.length - 1
           return $window.sprintf("%.02f%s", value, suffix)


Fix bug in quick load url retry logic.

Fix bug in quick load url retry logic.

--- app/assets/javascripts/quick_add_url.js.coffee
+++ app/assets/javascripts/quick_add_url.js.coffee
@@ -21,12 +21,10 @@
             success: ->
               clearInterval(timer)
               window.location.replace("/gend_images/new?src=#{data.id}")
-            error: ->
-              clearInterval(timer)
-              status.text('Error loading URL')
           count += 1
           if count >= 10
             clearInterval(timer)
+            status.text('Error loading URL')
         , 1000
       error: ->
         status.text('Error submitting URL')


fix (general, noticed with #514) - save multiple consecutive updates to the URL

--- app/assets/javascripts/search/persister.coffee
+++ app/assets/javascripts/search/persister.coffee
@@ -46,6 +46,8 @@
     else if window.history?.replaceState
       window.history.replaceState {}, '', @modifiedUrlString(changes)

+    $.query.parseNew(location.search, '')
+
   updateSearchForm: (changes) ->
     for field in @LOADABLE_FIELDS
       continue unless changes[field]?


Trim spaces from prefix and suffix

Trim spaces from prefix and suffix

--- app/lib/dom_text_mapper/dom_text_mapper.coffee
+++ app/lib/dom_text_mapper/dom_text_mapper.coffee
@@ -255,7 +255,7 @@
     prefixLen = start - prefixStart
     prefix = content.substr prefixStart, prefixLen
     suffix = content.substr end, prefixLen
-    [prefix, suffix]
+    [prefix.trim(), suffix.trim()]

   # Get the matching DOM elements for a given text range


Fix Synchronization spec

Fix Synchronization spec

--- spec/Synchronization.coffee
+++ spec/Synchronization.coffee
@@ -17,20 +17,17 @@
     datatype: 'int'
   error:
     datatype: 'object'
-
-noflo.helpers.WirePattern c,
-  in: ['dividend', 'divisor']
-  out: ['quotient', 'remainder']
-  async: true
-  forwardGroups: true
-, (input, groups, outs, done) ->
+c.process (input, output) ->
+  return unless input.hasData 'dividend', 'divisor'
+  dividend = input.getData 'dividend'
+  divisor = input.getData 'divisor'
   setTimeout ->
-    return done new Error 'Division by 0' if input.divisor == 0
-    if outs.quotient.isAttached()
-      outs.quotient.send parseInt input.dividend / input.divisor, 10
-    if outs.remainder.isAttached()
-      outs.remainder.send parseInt input.dividend % input.divisor, 10
-    done()
+    return output.done new Error 'Division by 0' if divisor == 0
+    if c.outPorts.quotient.isAttached()
+      output.send quotient: parseInt dividend / divisor, 10
+    if c.outPorts.remainder.isAttached()
+      output.send remainder: parseInt dividend % divisor, 10
+    output.done()
   , 0


use × html entity in alert function

use × html entity in alert function

--- shared/code/scraperwiki.coffee
+++ shared/code/scraperwiki.coffee
@@ -66,4 +66,4 @@
   # If [level] is a truthful value, the alert is printed in red.
   $a = $('
').addClass('alert').prependTo('body')
   $a.addClass('alert-error') if level
-  $a.html """<button type="button" class="close" data-dismiss="alert">×</button> #{title} #{message}"""
+  $a.html """<button type="button" class="close" data-dismiss="alert">×</button> #{title} #{message}"""


put back shuffle

put back shuffle

--- nodeko.coffee
+++ nodeko.coffee
@@ -513,6 +513,14 @@
       "<img src=\"http://www.gravatar.com/avatar/#{p.emailHash}?s=#{s || 40}&d=monsterid\" />"
 }

+_.shuffle = (a) ->
+  r = _.clone a
+  for i in [r.length-1 .. 0]
+    j = parseInt(Math.random() * i)
+    [r[i], r[j]] = [r[j], r[i]]
+  r
+
+
 # has to be last
 app.use '/', express.errorHandler({ dumpExceptions: true, showStack: true })


Fix functions spec

Fix functions spec

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -398,7 +398,7 @@

       {tokens} = grammar.tokenizeLine fixture
       expect(tokens[0].value).toBe 'Function'
-      expect(tokens[0]).toHaveScopes ["source.powershell", "meta.function", "storage.type"]
+      expect(tokens[0]).toHaveScopes ["source.powershell", "meta.function.powershell", "storage.type.powershell"]

   describe "Line continuations", ->


Fixed sorting

Fixed sorting

--- src/models/dao/sequelize.coffee
+++ src/models/dao/sequelize.coffee
@@ -26,7 +26,7 @@

     if options.sorting.length > 0
       # transform the sorting params into i.e. 'name DESC'
-      sorting = ("#{key} #{value.toUpperCase()}" for key, value of options.sorting)
+      sorting = ("#{sort.field} #{sort.type.toUpperCase()}" for sort in options.sorting)
       params.sorting = sorting.join ","

     if options.contains.length > 0


Added one-page tutorial text; deleted extra pages

--- app/assets/javascripts/components/tutorial-text/yale-transcribe-tutorial-text.cjsx
+++ app/assets/javascripts/components/tutorial-text/yale-transcribe-tutorial-text.cjsx
@@ -3,17 +3,7 @@
   pages: [
     {
       pageNumber: 0,
-      text: "One Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
-    },
-
-    {
-      pageNumber: 1,
-      text: "Two Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
-    },
-
-    {
-      pageNumber: 2,
-      text: "Three Lorem ipsum dolor sit amet, posuere a mauris, nostra quam nonummy, facilisis amet neque. Ut lacus eros venenatis ipsum, erat ut turpis, aliquam metus vitae volutpat sit sed. Eros maecenas malesuada quam leo laoreet, vel curabitur maecenas neque quam et libero, ut libero, vitae vel eros vestibulum, sagittis etiam nunc. Nonummy risus mattis, sed nisl. Quis non quis quis ultricies non facilisi."
+      text: "Type the text you see (do not correct for spelling, usage, etc.). If names and words are in all caps, however, type them with regular capitalization. You can click and drag the box to put it in a more convenient spot for typing. If the field was marked incorrectly and the box is in the wrong place, click Bad Region; If you cannot read the text in the field, click Illegible."
     }
   ]
-}+}


OutputSystemRunner superseded by View

OutputSystemRunner superseded by View

--- src/javascript/ecs/output_system_runner.coffee
+++ src/javascript/ecs/output_system_runner.coffee
@@ -1,15 +0,0 @@
-Immutable = require 'immutable'
-
-class OutputSystemRunner
-  constructor: ({@entityFinder, @ui, systems}) ->
-    @systems = Immutable.fromJS(systems)
-    @updateFns = @systems.map (s) -> s.get('update')
-
-  run: (input) ->
-    @updateFns.forEach (fn) =>
-      fn @entityFinder, input, @ui
-
-
-
-module.exports = OutputSystemRunner
-


cleanup console.log

cleanup console.log

--- src/javascript/game/systems/room_system.coffee
+++ src/javascript/game/systems/room_system.coffee
@@ -40,7 +40,6 @@
     roomDef = mapRoom.roomDef

     # Spawn enemies:
-    console.log "setupRoomAction roomId=#{roomId} roomDef=",roomDef
     for [col,row,id] in (roomDef.enemies || [])
       x = roomPos.get('x') + (col * MapConfig.tileWidth)
       y = roomPos.get('y') + (row * MapConfig.tileHeight)
@@ -53,17 +52,10 @@
     voff = 8
     for itemDef  in (roomDef.items || [])
       {col,row,type,id} = itemDef
-      # console.log "itemDef:",itemDef,col,row,type,id
-      console.log "HEY"
       if @itemStillInWorld(id)
-        console.log "room_system: spawning item",itemDef
         x = roomPos.get('x') + (col * MapConfig.tileWidth) + hoff
         y = roomPos.get('y') + (row * MapConfig.tileHeight) + voff
         @newEntity Items.factory.createComponents(type, powerup: { itemId: id }, position: {x: x, y: y})
-      else
-        console.log "room_system: NOT spawning item, since it is not out there anymore",itemDef
-
-

     # Spawn doors:
     for [style,col,row] in ((roomDef.fixtures || {})['doors'] || [])


Fix compilation of unknown, they must be finally declared

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -757,7 +757,7 @@
       callJsMethodCompile
     else if (ctx.isMacroDeclared operatorName) and not ctx.isDeclared operatorName
       callMacroCompile
-    else if (isFake operator) or (ctx.isDeclared operatorName) and not ctx.arity operatorName
+    else if (isFake operator) or (ctx.isFinallyDeclared operatorName) and not ctx.arity operatorName
       callUnknownCompile
     else
       callKnownCompile) ctx, call


Wip on faster unification

Wip on faster unification

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -138,9 +138,9 @@
   fn (ctx = new Context), ast
   expressions = []
   visitExpressions ast, (expression) ->
-    expressions.push "#{collapse toHtml expression} :: #{highlightType substitute ctx.substitution, expression.tea}" if expression.tea
+    expressions.push "#{collapse toHtml expression} :: #{highlightType expression.tea}" if expression.tea
   types: values mapMap (__ highlightType, _type), subtractMaps ctx._scope(), builtInDefinitions()
-  subs: printSubstitution ctx.substitution
+  #subs: printSubstitution ctx.substitution
   fails: map formatFail, ctx.substitution.fails
   ast: expressions
   deferred: ctx.deferredBindings()
@@ -417,10 +417,15 @@
       name

   bindTypeVariables: (vars) ->
-    addAllToSet @_scope().boundTypeVariables, vars
+    for {name, ref} in vars
+      addToMap @_scope().boundTypeVariables, name, ref
+
+  bindTypeVariableNames: (names) ->
+    for name in names
+      addToMap @_scope().boundTypeVariables, name, {}

   allBoundTypeVariables: ->
-    substituteVarNames this, concatSets (for scope in @scopes
+    substituteVarNames this, concatMaps (for scope in @scopes
       scope.boundTypeVariables)...

   addToScopeConstraints: (constraints) ->
@@ -962,7 +967,7 @@
   # TODO: should we support bare records?
   #   [a: 2 b: 3]
   if not ctx.shouldDefer()
-    form.tea = markOrigin (tupleOfTypes map _tea, elems), form
+    form.tea = withOrigin (tupleOfTypes map _tea, elems), form

   if ctx.assignTo()
     combinePatterns compiledElems
@@ -1213,10 +1218,10 @@
   else
     if ctx.isAtBareDefinition() and expression.tea
       # Force context reduction
-      inferredType = (substitute ctx.substitution, expression.tea)
+      inferredType = expression.tea#(substitute ctx.substitution, expression.tea)
       deferConstraints ctx,
         inferredType.constraints,
-        inferredType.type
+        inferredType
     translatedExpression

 # Pushes the deferring to the parent scope
@@ -1265,7 +1270,7 @@
       ctx.declare name, id: id
     if deps.length > 0
       # log "adding top level lhs to deferred #{name}", deps
-      currentType = substitute ctx.substitution, type
+      currentType = type#substitute ctx.substitution, type
       ctx.addToDeferred
         name: name
         scopeIndex: ctx.currentScopeIndex()
@@ -1287,23 +1292,24 @@
 inferType = (ctx, name, type, constraints, polymorphic, scopeIndex) ->
   # For explicitly typed bindings, we need to check that the inferred type
   #   corresponds to the annotated
-  currentType = substitute ctx.substitution, type
+  currentType = type# substitute ctx.substitution, type
   if ctx.isFinallyTyped name, scopeIndex or ctx.currentScopeIndex()
     # TODO: check class constraints
     if not includesJsType currentType.type
       # Check the declared type
       declaredType = ctx.type name
       explicitType = freshInstance ctx, declaredType
-      updatedDeclaredType = quantifyUnbound ctx, (substitute ctx.substitution, explicitType)
+      updatedDeclaredType = quantifyUnbound ctx, explicitType#(substitute ctx.substitution, explicitType)
       unify ctx, currentType.type, explicitType.type
-      unifiedType = substitute ctx.substitution, explicitType
+      unifiedType = explicitType# substitute ctx.substitution, explicitType
       inferredType = quantifyUnbound ctx, unifiedType
       if not typeEq inferredType, updatedDeclaredType
+        console.log currentType, (printType currentType)
         ctx.extendSubstitution substitutionFail "#{name}'s declared type is too general, inferred #{plainPrettyPrint inferredType}"
       # Context reduction
       isDeclaredConstraint = (c) ->
         entailed = entail ctx, unifiedType.constraints, c
-      notDeclared = filter (__ _not, isDeclaredConstraint), checked = (substituteList ctx.substitution, constraints)
+      notDeclared = filter (__ _not, isDeclaredConstraint), checked = constraints #(substituteList ctx.substitution, constraints)
       {success, error} = deferConstraints ctx, notDeclared, currentType
       if error
         ctx.extendSubstitution error
@@ -1314,14 +1320,14 @@
   else
     if not ctx.isAtNonDeferrableDefinition()
       {success, error} = deferConstraints ctx,
-        (substituteList ctx.substitution, constraints),
+        constraints#(substituteList ctx.substitution, constraints),
         currentType
       if error
         ctx.extendSubstitution error
         return
       [deferredConstraints, retainedConstraints] = success
       # Finalizing type again after possibly added substitution when defer constraints
-      currentType = substitute ctx.substitution, type
+      currentType = type#substitute ctx.substitution, type
     # log "assign type", name, (printType (addConstraints currentType, retainedConstraints)), (printType quantifyUnbound ctx, (addConstraints currentType, retainedConstraints))
     if includesJsType currentType.type
       ctx.extendSubstitution substitutionFail "#{name}'s inferred type #{plainPrettyPrint currentType} includes Js"
@@ -1485,7 +1491,6 @@
         ctx.doDefer expression, dependencyName

 definitionPairCompile = (ctx, pattern, value) ->
-  # log "COMPILING", pattern
   ctx.definePattern pattern
   # log "deferrement before assign !!!", ctx.shouldDefer()
   compiled = expressionCompile ctx, value
@@ -1550,7 +1555,7 @@
       paramTypeVars = map newParamType, params
       paramTypes = map (__ toForAll, toConstrained), paramTypeVars
       ctx.newLateScope()
-      ctx.bindTypeVariables (map (({name}) -> name), paramTypeVars)
+      ctx.bindTypeVariables paramTypeVars
       # log "adding types", (map _symbol, params), paramTypes
       ctx.declareTyped paramNames, paramTypes
       for param in params
@@ -1866,11 +1871,10 @@

     methodDefinitions = pairs wheres
     ctx.newScope()
-    ctx.bindTypeVariables paramNames
+    ctx.bindTypeVariableNames paramNames
     definitionList ctx, methodDefinitions
     declarations = ctx.currentDeclarations()
     ctx.closeScope()
-
     for [name, def] in methodDefinitions
       (lookupInMap declarations, name.symbol)?.def = def

@@ -1991,7 +1995,7 @@
       ctx.newScope()

       assignMethodTypes ctx, instanceConstraint, freshInstanceType, instanceName,
-        classDefinition, instanceType, constraints
+        classDefinition
       definitions = pairs wheres
       methodsDeclarations = definitionList ctx,
         (prefixWithInstanceName ctx, definitions, instanceName)
@@ -2017,19 +2021,25 @@
 # with renamed type variables to avoid clashes
 assignMethodTypes = (ctx, typeExpression, freshInstanceType, instanceName, classDeclaration) ->
   # log "mguing", classDeclaration.constraint.types, freshInstanceType.type
-  sub = mostGeneralUnifier classDeclaration.constraint.types, freshInstanceType.type
-  if isFailed sub
+
+  # Fresh the bound variables in class type (A a) -> (A 5)
+  # Use the same substitution on the method types (Fn a String) -> (Fn 5 String)
+  classParams = findFree classDeclaration.constraint
+  freshingSub = mapMap ((kind) -> ctx.freshTypeVariable kind), classParams
+
+  freshedClassType = substitute freshingSub, classDeclaration.constraint.types
+  if isFailed mostGeneralUnifier freshedClassType, freshInstanceType.type
     malformed ctx, typeExpression, 'Type doesn\'t match class type'
     return null

-  ctx.bindTypeVariables setToArray (findFree freshInstanceType)
+  ctx.bindTypeVariableNames setToArray (findFree freshInstanceType)
   for name, {arity, type} of values classDeclaration.declarations
     freshType = freshInstance ctx, type
-    instanceSpecificType = substitute sub, freshType
+    instanceSpecificType = substitute freshingSub, freshType
     quantifiedType = quantifyUnbound ctx, instanceSpecificType
     # Prefix so the instance method doesn't shadow the class method
     prefixedName = instancePrefix instanceName, name
-    # Check against redefing a method
+    # TODO: Check against redefing a method
     ctx.preDeclare prefixedName,
       arity: arity
       type: quantifiedType
@@ -2519,6 +2529,13 @@
   quantify vars, type

 substituteVarNames = (ctx, varNames) ->
+  concatSets (for name, ref of values varNames
+    if ref.val
+      findFree ref.val
+    else
+      newSetWith name)...
+
+substituteVarNames_pure = (ctx, varNames) ->
   subbed = (name) =>
     (inSub ctx.substitution, name) or new TypeVariable name
   findFreeInList map subbed, setToArray varNames
@@ -2529,12 +2546,12 @@
   if not reducedConstraints.success
     return reducedConstraints
   fixedVars = ctx.allBoundTypeVariables()
-  impliedConstraints = substituteList ctx.substitution, reducedConstraints.success
+  impliedConstraints = reducedConstraints.success#substituteList ctx.substitution, reducedConstraints.success
   isFixed = (constraint) ->
     # log fixedVars, (printType constraint), (findUnconstrained constraint)
     isSubset fixedVars, (findUnconstrained constraint)
   [deferred, retained] = partition isFixed, impliedConstraints
-  quantifiedVars = findFree finalType = substitute ctx.substitution, type
+  quantifiedVars = findFree finalType = type#substitute ctx.substitution, type
   impliedVars = concatSets (map findConstrained, impliedConstraints)...
   validVars = concatSets quantifiedVars, impliedVars
   isAmbiguous = (constraint) ->
@@ -2561,7 +2578,7 @@
   toNormalize = map id, constraints
   normalized = []
   while _notEmpty toNormalize
-    constraint = substitute ctx.substitution, toNormalize.shift()
+    constraint = toNormalize.shift()#substitute ctx.substitution, toNormalize.shift()
     if isNormalizedConstraint constraint
       normalized.push constraint
     else
@@ -2605,7 +2622,7 @@

 # Whether constraints entail constraint
 entail = (ctx, constraints, constraint) ->
-  constraints = substituteList ctx.substitution, constraints
+  #constraints = substituteList ctx.substitution, constraints
   if entailedBySuperClasses ctx, constraints, constraint
     return yes
   instanceContraints = constraintsFromInstance ctx, constraint
@@ -2619,7 +2636,7 @@
     for superClassConstraint in constraintsFromSuperClasses ctx, c
       # if typeEq superClassConstraint, constraint
       if (sub = constraintsEqual superClassConstraint, constraint)
-        ctx.extendSubstitution sub
+        #ctx.extendSubstitution sub
         return yes
   no

@@ -2637,12 +2654,12 @@
   {className, type} = constraint
   for instance in (ctx.classNamed className).instances
     freshed = freshInstance ctx, instance.type
-    # log "trying to find the instance", (printType instance.type.type), (printType constraint)
+    # log "trying to find the instance", (printType freshed.type), (printType constraint)
     substitution = toMatchTypes freshed.type.types, constraint.types
     if substitution
       # log substitution
       ctx.extendSubstitution substitution
-      return map ((c) -> substitute substitution, c), freshed.constraints
+      return freshed.constraints#map ((c) -> substitute substitution, c), freshed.constraints
   null

 instanceLookupFailed = (constraint) ->
@@ -2962,7 +2979,7 @@
 #       is not a function then it can't need type class dictionaries
 #       ^.___ not necessarily, we could have a tuple of functions or similar
 irDefinitionTranslate = (ctx, {type, expression, reference, bare}) ->
-  finalType = substitute ctx.substitution, type
+  finalType = type#substitute ctx.substitution, type
   allConstraints = (addConstraintsFrom ctx,
     {type, name: reference?.name, scopeIndex: reference?.scopeIndex}, finalType).constraints
   reducedConstraints =
@@ -3006,7 +3023,7 @@
   {ir: irCallTranslate, type, op, args}

 irCallTranslate = (ctx, {type, op, args}) ->
-  finalType = addConstraintsFrom ctx, op, (substitute ctx.substitution, type)
+  finalType = addConstraintsFrom ctx, op, type#(substitute ctx.substitution, type)
   classParams =
     if op.ir is irReferenceTranslate and ctx.isMethod op.name, op.type
       []
@@ -3021,6 +3038,7 @@

 addConstraintsFrom = (ctx, {name, type, scopeIndex}, to) ->
   if scopeIndex? and (typed = ctx.savedDeclaration name, scopeIndex) and
+      typed.type and
       (_empty to.constraints) and
       (_notEmpty typed.type.type.constraints)
     addConstraints to, constraintsFromCanonicalType ctx, typed.type, type
@@ -3029,9 +3047,15 @@

 constraintsFromCanonicalType = (ctx, canonicalType, type) ->
   inferredType = freshInstance ctx, canonicalType
-  ctx.extendSubstitution matchType inferredType.type, (substitute ctx.substitution, type).type
+  # console.log "canonicalType", canonicalType, type
+  # console.log (printType canonicalType)
+  # console.log (printType inferredType)
+  # console.log (printType type.type)
+  ctx.extendSubstitution matchType inferredType.type, type.type#(substitute ctx.substitution, type).type
+  # console.log "after", (printType inferredType.type)
   reduceConstraints ctx, inferredType.constraints
-  (substitute ctx.substitution, inferredType).constraints
+  #(substitute ctx.substitution, inferredType).constraints
+  inferredType.constraints

 irReference = (name, type, scopeIndex) ->
   {ir: irReferenceTranslate, name, type, scopeIndex}
@@ -3040,7 +3064,7 @@
   if ctx.isMethod name, type
     translateIr ctx, (irMethod type, name)
   else
-    finalType = addConstraintsFrom ctx, {type, name: name, scopeIndex: scopeIndex}, substitute ctx.substitution, type
+    finalType = addConstraintsFrom ctx, {type, name: name, scopeIndex: scopeIndex}, type#substitute ctx.substitution, type
     classParams = dictsForConstraint ctx, finalType.constraints
     if classParams.length > 0
       {arity} = ctx.savedDeclaration name, scopeIndex
@@ -3056,7 +3080,7 @@
   {ir: irMethodTranslate, type, name}

 irMethodTranslate = (ctx, {type, name}) ->
-  finalType = substitute ctx.substitution, type
+  finalType = type#substitute ctx.substitution, type
   # log "irmethod", name, finalType.constraints
   resolvedMethod =
     for dict in dictsForConstraint ctx, finalType.constraints
@@ -3166,7 +3190,7 @@
   {ir: irJsCompatibleTranslate, type, expression}

 irJsCompatibleTranslate = (ctx, {type, expression}) ->
-  finalType = substitute ctx.substitution, type
+  finalType = type#substitute ctx.substitution, type
   translated = translateIr ctx, expression
   if isCustomCollectionType finalType
     (jsCall (jsAccess translated, 'toArray'), [])
@@ -4206,7 +4230,7 @@
 mapMap = (fn, set) ->
   initialized = newMap()
   for key, val of set.values
-    addToMap initialized, key, fn val
+    addToMap initialized, key, fn val, key
   initialized

 mapKeys = (fn, map) ->
@@ -4343,9 +4367,144 @@

 # end of Set

+# Unification and substitution ala Atze Dijkstra
+
+unify = (ctx, t1, t2) ->
+  throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
+  mostGeneralUnifier t1, t2
+
+mostGeneralUnifier = (t1, t2) ->
+  if t1.TypeVariable and t2.TypeVariable and t1.name is t2.name
+    emptySubstitution()
+  else if t1.TypeVariable and t1.ref.val
+    mostGeneralUnifier t1.ref.val, t2
+  else if t2.TypeVariable and t2.ref.val
+    mostGeneralUnifier t1, t2.ref.val
+  else if t1.TypeVariable
+    bindVariable t1, t2
+  else if t2.TypeVariable
+    mostGeneralUnifier t2, t1
+  else if t1.TypeConstr and t2.TypeConstr and t1.name is t2.name
+    emptySubstitution()
+  else if t1.TypeApp and t2.TypeApp
+    s1 = mostGeneralUnifier t1.op, t2.op
+    s2 = mostGeneralUnifier t1.arg, t2.arg
+    joinSubs s1, s2 # only errors now
+  else if t1.Types and t2.Types
+    if t1.types.length isnt t2.types.length
+      unifyFail t1, t2
+    else if _notEmpty t1.types
+      s1 = mostGeneralUnifier t1.types[0], t2.types[0]
+      s2 = mostGeneralUnifier (new Types t1.types[1...]), (new Types t2.types[1...])
+      joinSubs s1, s2 # only errors now
+    else
+      emptySubstitution()
+  else
+    unifyFail t1, t2
+
+bindVariable = (variable, type) ->
+  if inSet (findFree type), variable.name
+    typeFail "Types cannot match: ", variable, type
+    # newMapWith variable.name, "occurs check failed"
+  else if not kindsEq (kind variable), (kind type)
+    # newMapWith variable.name, "kinds don't match for #{variable.name} and #{safePrintType type}"
+    typeFail "Kinds of types don't match: ", variable, type
+  else
+    variable.ref.val = type
+    newSubstitution()
+
+# Maybe substitution if the types match
+# t1 can be more general than t2
+toMatchTypes = (t1, t2) ->
+  substitution = matchType t1, t2
+  if _notEmpty substitution.fails
+    null
+  else
+    substitution
+
+# Returns possible failures
+matchType = (t1, t2) ->
+  if t1.TypeVariable and t1.ref.val
+    if typeEq t1.ref.val, t2
+      emptySubstitution()
+    else
+      unifyFail t1, t2
+  else if t1.TypeVariable and kindsEq (kind t1), (kind t2)
+    t1.ref.val = t2 #newSubstitution t1.name, t2
+    # console.log "attaching", t1.name, t2
+    # console.log "attached", (printType t2)
+    newSubstitution()
+  else if t2.TypeVariable and t2.ref.val
+    matchType t1, t2.ref.val
+  else if t1.TypeConstr and t2.TypeConstr and t1.name is t2.name
+    emptySubstitution()
+  else if t1.TypeApp and t2.TypeApp
+    s1 = matchType t1.op, t2.op
+    s2 = matchType t1.arg, t2.arg
+    subUnion s1, s2
+  else if t1.Types and t2.Types
+    if t1.types.length isnt t2.types.length
+      unifyFail t1, t2
+    else if _notEmpty t1.types
+      s1 = matchType t1.types[0], t2.types[0]
+      # We imply functional dependency of the form A a b c | a -> b c
+      # s2 = mostGeneralUnifier (new Types (substituteList s1, t1.types[1...])), (new Types t2.types[1...])
+      s2 = unifyImpliedParams t1, t2
+      subUnion s1, s2
+    else
+      emptySubstitution()
+  else
+    unifyFail t1, t2
+
+unifyImpliedParams = (t1, t2) ->
+  mostGeneralUnifier (new Types t1.types[1...]), (new Types t2.types[1...])
+
+# Used to convert between quantified and not quantified
+substitute = (substitution, type) ->
+  if type.TypeVariable
+    if type.ref.val
+      substitute substitution, type.ref.val
+    else
+      substitution.values and (lookupInMap substitution, type.name) or type # should not happen
+  else if type.QuantifiedVar
+    substitution[type.var] or type
+  else if type.TypeConstr
+    new TypeConstr type.name, type.kind
+  else if type.TypeApp
+    new TypeApp (substitute substitution, type.op),
+      (substitute substitution, type.arg)
+  else if type.ForAll
+    new ForAll type.kinds, (substitute substitution, type.type)
+  else if type.Constrained
+    new Constrained (substituteList substitution, type.constraints),
+      (substitute substitution, type.type)
+  else if type.ClassConstraint
+    new ClassConstraint type.className, substitute substitution, type.types
+  else if type.Types
+    new Types substituteList substitution, type.types
+  else
+    type
+
+findFree = (type) ->
+  if type.TypeVariable
+    if type.ref.val
+      findFree type.ref.val
+    else
+      newMapWith type.name, type.kind
+  else if type.TypeApp
+    concatMaps (findFree type.op), (findFree type.arg)
+  else if type.Constrained
+    concatMaps (findFree type.type), (findFreeInList type.constraints)
+  else if type.ClassConstraint
+    findFree type.types
+  else if type.Types
+    findFreeInList type.types
+  else
+    newMap()
+
 # Type inference and checker ala Mark Jones

-unify = (ctx, t1, t2) ->
+unify_pure = (ctx, t1, t2) ->
   throw new Error "invalid args to unify" unless ctx instanceof Context and t1 and t2
   # TODO: need to taint the other type
   # if (includesJsType t1) or (includesJsType t2)
@@ -4354,7 +4513,7 @@
   ctx.extendSubstitution mostGeneralUnifier (substitute sub, t1), (substitute sub, t2)

 # Returns a substitution
-mostGeneralUnifier = (t1, t2) ->
+mostGeneralUnifier_pure = (t1, t2) ->
   if t1.TypeVariable
     bindVariable t1, t2
   else if t2.TypeVariable
@@ -4381,7 +4540,7 @@
 unifyFail = (t1, t2) ->
   typeFail "Types don't match: ", t1, t2

-bindVariable = (variable, type) ->
+bindVariable_pure = (variable, type) ->
   if type.TypeVariable and variable.name is type.name
     emptySubstitution()
   else if inSet (findFree type), variable.name
@@ -4408,7 +4567,7 @@

 # Maybe substitution if the types match
 # t1 can be more general than t2
-toMatchTypes = (t1, t2) ->
+toMatchTypes_pure = (t1, t2) ->
   substitution = matchType t1, t2
   if _notEmpty substitution.fails
     null
@@ -4416,7 +4575,7 @@
     substitution

 # Returns a substitution
-matchType = (t1, t2) ->
+matchType_pure = (t1, t2) ->
   if t1.TypeVariable and kindsEq (kind t1), (kind t2)
     newSubstitution t1.name, t2
   else if t1.TypeConstr and t2.TypeConstr and
@@ -4449,10 +4608,13 @@
     unifyFail t1, t2
     # newMapWith "could not unify", [(safePrintType t1), (safePrintType t2)]

-unifyImpliedParams = (t1, t2) ->
+unifyImpliedParams_pure = (t1, t2) ->
   mostGeneralUnifier (new Types t1.types[1...]), (new Types t2.types[1...])

-joinSubs = (s1,s2) ->
+joinSubs = (s1, s2) ->
+  subUnion s1, s2
+
+joinSubs_pure = (s1,s2) ->
   subUnion s1, (mapSub ((type) -> substitute s1, type), s2)

 mergeSubs = (s1, s2) ->
@@ -4477,6 +4639,12 @@
     name

 subUnion = (subA, subB) ->
+  union = emptySubstitution()
+  union.start = subA.start + subB.start
+  union.fails = [].concat subB.fails, subA.fails
+  union
+
+subUnion_pure = (subA, subB) ->
   union = emptySubstitution()
   start = Math.min (subStart subA), (subStart subB)
   end = Math.max (subLimit subA), (subLimit subB)
@@ -4488,7 +4656,12 @@
   union.fails = [].concat subB.fails, subA.fails
   union

-newSubstitution = (name, type) ->
+newSubstitution = ->
+  sub = emptySubstitution()
+  sub.start = 0
+  sub
+
+newSubstitution_pure = (name, type) ->
   sub = emptySubstitution()
   sub.vars[0] = type
   sub.start = name
@@ -4503,6 +4676,9 @@
   sub

 subLimit = (sub) ->
+  if sub.start is Infinity then -Infinity else sub.start
+
+subLimit_pure = (sub) ->
   if sub.start is Infinity then -Infinity else sub.start + sub.vars.length

 subStart = (sub) ->
@@ -4517,11 +4693,11 @@
 emptySubstitution = ->
   start: Infinity
   fails: []
-  vars: []
+  #vars: []

 # Unlike in Jones, we simply use substitute for both variables and quantifieds
 # - variables are strings, wheres quantifieds are ints
-substitute = (substitution, type) ->
+substitute_pure = (substitution, type) ->
   if type.TypeVariable
     substitution.vars and (inSub substitution, type.name) or
       substitution.values and (lookupInMap substitution, type.name) or type
@@ -4546,7 +4722,7 @@
 substituteList = (substitution, list) ->
   map ((t) -> substitute substitution, t), list

-findFree = (type) ->
+findFree_pure = (type) ->
   if type.TypeVariable
     newMapWith type.name, type.kind
   else if type.TypeApp
@@ -4582,7 +4758,6 @@
   # (String.fromCharCode 97 + nameIndex % 25) + suffix

 copyOrigin = (to, from) ->
-  clone = cloneType to
   if to.Constrained
     copyOrigin to.type, from.type
     for c, i in to.constraints
@@ -4591,13 +4766,12 @@
     if to.TypeApp
       copyOrigin to.op, from.op
       copyOrigin to.arg, from.arg
-    mutateMarkingOrigin clone, from.origin
-  clone
+    mutateMarkingOrigin to, from.origin
+  to

 mapOrigin = (type, expression) ->
-  clone = cloneType type
-  mutateMappingOrigin clone, expression
-  clone
+  mutateMappingOrigin type, expression
+  type

 mutateMappingOrigin = (type, expression) ->
   if type.Constrained
@@ -4610,8 +4784,9 @@
       mutateMappingOrigin type.arg, expression
     mutateMarkingOrigin type, expression

+# Used on builtin types
 markOrigin = (typeOrConstraint, expression) ->
-  clone = cloneType typeOrConstraint
+  clone = substitute newMap(), typeOrConstraint
   mutateMarkingOrigin clone, expression
   clone

@@ -4625,19 +4800,19 @@

 # Clones constrained types and parts of them
 # Class constraint arguments are not cloned
-cloneType = (t) ->
-  if t.TypeVariable
-    new TypeVariable t.name, t.kind
-  else if t.TypeConstr
-    new TypeConstr t.name, t.kind
-  else if t.QuantifiedVar
-    new QuantifiedVar t.var
-  else if t.TypeApp
-    new TypeApp (cloneType t.op), (cloneType t.arg)
-  else if t.ClassConstraint
-    new ClassConstraint t.className, t.types
-  else if t.Constrained
-    new Constrained (map cloneType, t.constraints), (cloneType t.type)
+# cloneType = (t) ->
+#   if t.TypeVariable
+#     new TypeVariable t.name, t.kind
+#   else if t.TypeConstr
+#     new TypeConstr t.name, t.kind
+#   else if t.QuantifiedVar
+#     new QuantifiedVar t.var
+#   else if t.TypeApp
+#     new TypeApp (cloneType t.op), (cloneType t.arg)
+#   else if t.ClassConstraint
+#     new ClassConstraint t.className, t.types
+#   else if t.Constrained
+#     new Constrained (map cloneType, t.constraints), (cloneType t.type)

 # Normalized constraint has a type which has type variable at its head
 #   that is either ordinary type variable or type variable standing for a constructor
@@ -4646,6 +4821,18 @@
   isNormalizedConstraintArgument constraint.types.types[0]

 isNormalizedConstraintArgument = (type) ->
+  if type
+    if type.TypeVariable
+      if type.ref.val
+        isNormalizedConstraintArgument type.ref.val
+      else
+        yes
+    else if type.TypeConstr
+      no
+    else if type.TypeApp
+      isNormalizedConstraintArgument type.op
+
+isNormalizedConstraintArgument_pure = (type) ->
   if type
     if type.TypeVariable
       yes
@@ -4654,16 +4841,22 @@
     else if type.TypeApp
       isNormalizedConstraintArgument type.op

-
 includesJsType = (type) ->
-  if type.TypeConstr
+  if type.TypeVariable and type.ref.val
+    includesJsType type.ref.val
+  else if type.TypeConstr
     typeEq type, jsType
   else if type.TypeApp
     (includesJsType type.op) or (includesJsType type.arg)

 typeEq = (a, b) ->
-  if a.TypeVariable and b.TypeVariable or
-      a.TypeConstr and b.TypeConstr
+  if a.TypeVariable and b.TypeVariable and a.name is b.name
+    yes
+  else if a.TypeVariable and a.ref.val
+    typeEq a.ref.val, b
+  else if b.TypeVariable and b.ref.val
+    typeEq a, b.ref.val
+  else if a.TypeConstr and b.TypeConstr
     a.name is b.name
   else if a.QuantifiedVar and b.QuantifiedVar
     a.var is b.var
@@ -4773,7 +4966,7 @@
   constructor: ->

 class TypeVariable
-  constructor: (@name, @kind) ->
+  constructor: (@name, @kind, @ref = {}) ->
     @TypeVariable = yes
 class TypeConstr
   constructor: (@name, @kind) ->
@@ -4812,7 +5005,7 @@
   new Constrained [], type

 toForAll = (type) ->
-  new ForAll [], type
+  new ForAll [], substitute newMap(), type

 quantifyAll = (type) ->
   quantify (findFree type), type
@@ -4895,10 +5088,13 @@

 printType = (type) ->
   if type.TypeVariable
-    if /^[0-9]/.test type.name
-      "_#{type.name}"
-    else
-      "#{type.name}"
+    if type.ref.val
+      printType type.ref.val
+    else
+      if /^[0-9]/.test type.name
+        "_#{type.name}"
+      else
+        "#{type.name}"
   else if type.QuantifiedVar
     "#{type.var}"
   else if type.TypeConstr


Pass in fake arguments to macros

Pass in fake arguments to macros

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2541,11 +2541,16 @@
     macroFn = eval compiledMacro
     if macroFn
       ctx.declareMacro ctx.definitionPattern(), (ctx, call) ->
+        args = _arguments call
         operatorCompile ctx, call if typed
         if splatted
-          constantToSource macroFn Immutable.List (_arguments call)
+          constantToSource macroFn Immutable.List args
         else
-          constantToSource macroFn (_arguments call)...
+          if macroFn.length isnt args.length
+            # tooMany = macroFn.length < args.length
+            # malformed ctx, call, "#{if tooMany then 'Too many' else 'Not enough'} arguments to #{macroName}"
+            args = join args, (fake_() for i in [args.length...macroFn.length])
+          constantToSource macroFn args...
       , extractDocs docs
     else
       malformed ctx, ctx.definitionPattern(), 'Macro failed to compile'


Failing test for throwing errors

Failing test for throwing errors

--- source/javascripts/spec/ModuleSpec.js.coffee
+++ source/javascripts/spec/ModuleSpec.js.coffee
@@ -85,3 +85,15 @@
     it 'should re-throw other errors', ->
       testFunc = -> specModule.define(-> throw('!'))
       expect(testFunc).toThrow()
+
+    it 'should stop calling if other error thrown', ->
+      specModule.testFunc1 = -> specModule.define(-> throw('!'))
+      specModule.testFunc2 = -> specModule.define(->)
+      spyOn(specModule, 'testFunc1').andCallThrough()
+      spyOn(specModule, 'testFunc2').andCallThrough()
+      expect(specModule.testFunc1).toThrow()
+      expect(specModule.testFunc2).not.toThrow()
+      expect(specModule.testFunc1).toHaveBeenCalled()
+      expect(specModule.testFunc1.calls.length).toBe(1)
+      expect(specModule.testFunc2).toHaveBeenCalled()
+      expect(specModule.testFunc2.calls.length).toBe(1)


file-tree-context-menu: Added "Other Apps" item to file "Open with" menu.

--- client/landing/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontextmenucontroller.coffee
@@ -368,6 +368,7 @@
       items[appName] = action: "openFile"

     items["separator"]            = type     : "separator"
+    items["Other Apps"]           = action   : "showOpenWithModal", separator : yes
     items["Search the App Store"] = disabled : yes
     items["Contribute an Editor"] = disabled : yes


group population and target creation

group population and target creation

--- src/mist-resolver.coffee
+++ src/mist-resolver.coffee
@@ -29,7 +29,6 @@

 module.exports = class MistResolver
   constructor: (@rootDir, @rootMist)->
-    @groupSubs = {}
     @groupRefs = {}

     @setupTargets()
@@ -44,9 +43,7 @@
       rule.targets = {}

   emitGroupOutput: (group, output)->
-    console.log "\x1b[31m", group, output, "\x1b[0m"
     (@groupRefs[group] = @groupRefs[group] || []).push output
-    console.log "\x1b[33m", @groupRefs, "\x1b[0m"

   ###
   # Generates templates for dependencies and outputs
@@ -84,18 +81,24 @@
         throw "unknown template type: #{input.type}"

   generateTargets: ->
+    groupSubs = {}
+    for rule in @rootMist.rules
+      for input in rule.src.inputs
+        if input.type is 'group'
+          (groupSubs[input.value] = groupSubs[input.value] || []).push rule
+
     for rule in @rootMist.rules
       for input in rule.src.inputs
         switch input.type
           when 'glob'
             results = Globber.performGlob input.value, @rootDir
             for result in results
-              @processInput rule, result
+              @processInput rule, result, null, groupSubs
           when 'group' then break
           else
             throw "unknown input type: #{input.type}"

-  processInput: (rule, input, group)->
+  processInput: (rule, input, group, groupSubs = {})->
     return if input of rule.targets

     processor = (fn)->
@@ -110,9 +113,15 @@
       outputs: rule.templates.outputs.map processor
       auxOutputs: rule.templates.auxOutputs.map processor

+    for k, a of rule.targets[input]
+      rule.targets[input][k] = a.flatten()
+
     for group in rule.src.groups
       for output in rule.targets[input].outputs
-        @emitGroupOutput group, output
+        (@groupRefs[group] = @groupRefs[group] || []).push output
+        if group of groupSubs
+          for rule in groupSubs[group]
+            @processInput rule, output, group, groupSubs

 ###
 # Make sure to always include `$1` in the replacement


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/follow.coffee
+++ test/follow.coffee
@@ -27,6 +27,6 @@
   rightEnemy.include(Follow)
   rightEnemy.follow(player)

-  ok rightEnemy.I.velocity.equal(Point(-1, 0)), 'rightEnemy should head toward player with velocity Point(-1, 0)'
+  ok rightEnemy.I.direct.equal(Point(-1, 0)), 'rightEnemy should head toward player with velocity Point(-1, 0)'

 module()


fix order of inclusion for image plugins

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -53,11 +53,11 @@
         'common/list'
         'oer/table'
         'extra/draganddropfiles'
-        'common/image'
         'oer/overlay'
         'oer/math'
         'oer/assorted'
         'ghbook/image'
+        'common/image'
         'oer/note'
         'oer/example'
         'oer/exercise'


Editor: navigate user to Activity when exit menu item clicked.

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -227,7 +227,9 @@

     @on "gotoLineMenuItemClicked", => @getActiveAceView().ace.showGotoLine()

-    @on "exitMenuItemClicked", => @appManager.quit @appManager.frontApp
+    @on "exitMenuItemClicked", =>
+      @appManager.quit @appManager.frontApp
+      KD.singletons.router.handleRoute "/Activity"

     @on "keyBindingsMenuItemClicked", => new EditorMacroView


pubnub: add toggling to client side

pubnub: add toggling to client side

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -269,6 +269,14 @@
       return callback {message: "Account not found", name: "NotFound"} unless owner
       callback null, owner

+  togglePubnub: ->
+    isPubnubEnabled = localStorage.isPubnubEnabled or KD.config.pubnub.enabled
+    localStorage.isPubnubEnabled = !isPubnubEnabled
+    location.reload()
+
+  isPubnubEnabled: ->
+    localStorage.isPubnubEnabled or KD.config.pubnub.enabled
+

 Object.defineProperty KD, "defaultSlug",
   get:->


Removed: jquery dependency to detect 'dom ready' state

--- client/app/initialize.coffee
+++ client/app/initialize.coffee
@@ -9,7 +9,7 @@

 # Waits for the DOM to be ready
-$ ->
+window.onload = ->

     # set date locale here
     #moment.locale 'fr'


Prevent loop on exceptions

Prevent loop on exceptions

--- client/app/initialize.coffee
+++ client/app/initialize.coffee
@@ -2,21 +2,24 @@
 # Send client side errors to server
 window.onerror = (msg, url, line, col, error) ->
     console.error msg, url, line, col, error
-    data =
-        data:
-            type: 'error'
-            error:
-                msg: msg
-                full: error.toString()
-                stack: error.stack
-            url: url
-            line: line
-            col: col
-            href: window.location.href
-    xhr = new XMLHttpRequest()
-    xhr.open 'POST', 'activity', true
-    xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
-    xhr.send JSON.stringify(data)
+    exception = error?.toString() or msg
+    if exception isnt window.lastError
+        data =
+            data:
+                type: 'error'
+                error:
+                    msg: msg
+                    full: error.toString()
+                    stack: error.stack
+                url: url
+                line: line
+                col: col
+                href: window.location.href
+        xhr = new XMLHttpRequest()
+        xhr.open 'POST', 'activity', true
+        xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
+        xhr.send JSON.stringify(data)
+        window.lastError = exception

 window.onload = ->

@@ -73,12 +76,15 @@

     catch e
         console.error e
-        # Send client side errors to server
-        data =
-            data:
-                type: 'error'
-                exception: e.toString()
-        xhr = new XMLHttpRequest()
-        xhr.open 'POST', 'activity', true
-        xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
-        xhr.send JSON.stringify(data)
+        exception = e.toString()
+        if exception isnt window.lastError
+            # Send client side errors to server
+            data =
+                data:
+                    type: 'error'
+                    exception: exception
+            xhr = new XMLHttpRequest()
+            xhr.open 'POST', 'activity', true
+            xhr.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
+            xhr.send JSON.stringify(data)
+            window.lastError = exception


Implement defensive check into user area getter

--- client/app/lib/remote.coffee
+++ client/app/lib/remote.coffee
@@ -15,7 +15,12 @@
     apiDescriptor  : globals.REMOTE_API
     resourceName   : globals.config.resourceName ? 'koding-social'
     getSessionToken: getSessionToken
-    getUserArea    : -> kd.getSingleton('groupsController').getUserArea()
+
+    getUserArea    : ->
+
+      {groupsController} = kd.singletons
+      groupsController?.getUserArea()
+
     fetchName      : do ->
       cache = {}
       (nameStr, callback)->


github-notify: expand loops to get rid of duplicate executions for each room.

--- scripts/github-notify.coffee
+++ scripts/github-notify.coffee
@@ -53,18 +53,20 @@
     data = req.body
     repo = data.repository.full_name

-    for room in repo2rooms(robot, repo)
-      try
-        switch req.header("X-Github-Event")
-          when "pull_request"
-            announcePullRequest robot, data, (what) ->
+    rooms = repo2rooms(robot, repo)
+    try
+      switch req.header("X-Github-Event")
+        when "pull_request"
+          announcePullRequest robot, data, (what) ->
+            for room in repo2rooms(robot, repo)
               robot.messageRoom room, what
-          when "pull_request_review_comment"
-            announcePullRequestReviewComment robot, data, (what) ->
+        when "pull_request_review_comment"
+          announcePullRequestReviewComment robot, data, (what) ->
+            for room in repo2rooms(robot, repo)
               robot.messageRoom room, what
-      catch error
-        robot.messageRoom room, "GitHub 通知処理中にエラーが発生しました: #{error}"
-        console.log "github pull request notifier error: #{error}. Request: #{req.body}"
+    catch error
+      robot.messageRoom room, "GitHub 通知処理中にエラーが発生しました: #{error}"
+      console.log "github pull request notifier error: #{error}. Request: #{req.body}"

     res.end ""


dont need testing for this

dont need testing for this

--- test/victory_test.coffee
+++ test/victory_test.coffee
@@ -1,17 +0,0 @@
-chai = require 'chai'
-sinon = require 'sinon'
-chai.use require 'sinon-chai'
-
-expect = chai.expect
-
-describe 'victory', ->
-  beforeEach ->
-    @robot =
-      respond: sinon.spy()
-      hear: sinon.spy()
-
-    require('../src/victory')(@robot)
-
-  it 'registers a hear listener', ->
-    expect(@robot.hear).to.have.been.calledWith(/victory/)
-


Route: Call user environment data provider workspace getter

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -33,17 +33,8 @@

   { machineLabel, workspaceSlug, channelId } = workspace

-  ws = null
-
-  if checkWorkspace machineLabel, workspaceSlug, channelId
-    ws = workspace
-
-  return ws
-
-
-checkWorkspace = (machineLabel, workspaceSlug, channelId) ->
-
-  return dataProvider.validateCollaborationWorkspace machineLabel, workspaceSlug, channelId
+  if dataProvider.findWorkspace machineLabel, workspaceSlug, channelId
+    return workspace

 loadIDE = (data) ->


Fix tests to reflect the new combined method

--- test/linode.coffee
+++ test/linode.coffee
@@ -16,7 +16,7 @@
   describe 'finding the plan', ->
     plan_id = null
     nock = nocks.plans()
-
+
     before (done) ->
       Linode._get_plan 512, 20, (err, pid) ->
         plan_id = pid
@@ -136,35 +136,23 @@
     it 'gets the instance from the name', ->
       instance.id.should.equal 206097

-  describe 'when getting the public ip address from its linode id', ->
+  describe 'when getting the public and private ip address from its linode id', ->
     list_ip_specific = nocks.list_ip_specific()
-    ip_address = null
-
-    before (done) ->
-      Linode._get_ip 206097, (err, i) ->
-          ip_address = i
-          done()
+    public_ip_address = null
+    private_ip_address = null
+
+    before (done) ->
+      Linode._get_ips 206097, (err, pub_i, priv_i) ->
+        public_ip_address = pub_i
+        private_ip_address = priv_i
+        done()

     it 'calls linode.list on a specific linodeid', ->
       list_ip_specific.isDone().should.be.true

     it 'gets the instance from the name', ->
-      ip_address.should.equal '176.58.105.104'
-
-  describe 'when getting the private ip address from its linode id', ->
-    list_ip_specific = nocks.list_ip_specific()
-    ip_address = null
-
-    before (done) ->
-      Linode._get_private_ip 206097, (err, i) ->
-          ip_address = i
-          done()
-
-    it 'calls linode.list on a specific linodeid', ->
-      list_ip_specific.isDone().should.be.true
-
-    it 'gets the instance from the name', ->
-      ip_address.should.equal '192.168.194.104'
+      public_ip_address.should.equal '176.58.105.104'
+      private_ip_address.should.equal '192.168.194.104'

   describe 'when destroying an instance', ->
     delete_nock = nocks.delete()


Fix for dynamic adminSegment

Fix for dynamic adminSegment

--- client/source/buckets.coffee
+++ client/source/buckets.coffee
@@ -17,7 +17,7 @@
 module.exports = class BucketsApp extends Chaplin.Application
   title: 'Buckets'
   initialize: (@options = {}) ->
-    @initRouter routes, {root: '/admin/'}
+    @initRouter routes, {root: "/#{@options.adminSegment}/"}
     @initDispatcher
       controllerPath: 'client/source/controllers/'
       controllerSuffix: '_controller.coffee'


fix(draft-session): Never show body with transforms applied

--- src/flux/stores/draft-editing-session.coffee
+++ src/flux/stores/draft-editing-session.coffee
@@ -170,7 +170,7 @@
         continue if key is 'id'
         continue if nextDraft[key] is undefined
         nextValues[key] = nextDraft[key]
-      @_draft = Object.assign(new Message(), @_draft, nextValues)
+      @_setDraft(Object.assign(new Message(), @_draft, nextValues))
       @trigger()

   _changeSetAltered: =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -12,6 +12,6 @@

   sprite = Sprite.load spriteUrl, callback

-  ok
+  ok sprite

 module()


Prevent undesired reads

Prevent undesired reads

--- client/startup/unread.coffee
+++ client/startup/unread.coffee
@@ -15,8 +15,9 @@
 		subscriptions = ChatSubscription.find({open: true}, { fields: { unread: 1, alert: 1, rid: 1, t: 1, name: 1, ls: 1 } })

 		rid = undefined
-		if FlowRouter.getRouteName() in ['channel', 'group', 'direct']
-			rid = Session.get 'openedRoom'
+		Tracker.nonreactive ->
+			if FlowRouter.getRouteName() in ['channel', 'group', 'direct']
+				rid = Session.get 'openedRoom'

 		for subscription in subscriptions.fetch()
 			if subscription.rid is rid and (subscription.alert or subscription.unread > 0)


Don't explode if 'core.themes' config key is undefined

--- src/app/general-config-panel.coffee
+++ src/app/general-config-panel.coffee
@@ -68,7 +68,7 @@
     for name in atom.getAvailableThemeNames()
       @availableThemeList.append(@buildThemeLi(name, draggable: true))

-    for name in config.get("core.themes")
+    for name in config.get("core.themes") ? []
       @enabledThemeList.append(@buildThemeLi(name))

     @enabledThemeList.sortable()


When a message is posted trigger client-message-event

--- src/app/main/main.controller.coffee
+++ src/app/main/main.controller.coffee
@@ -48,7 +48,10 @@
       return true

     $scope.post = ->
-      $scope.onMessage($scope.message.body, $scope.position)
+      $scope.channel.trigger("client-message-event",
+        message: $scope.message.body,
+        position: $scope.getPositionToDegreeOfAccuracy()
+      )

     $scope.onMessage = (message, position) ->
       pos = new google.maps.LatLng(position.coords.latitude, position.coords.longitude)


CSSLoader

CSSLoader

- Limit check for existing "cssRules" to IE after load. Seems that Chrome does this
  asynchronously and since we get a proper 404 error from Chrome anyway, it seems
  reasonable to enable that hack for IE only.

--- src/base/CSSLoader/CSSLoader.coffee
+++ src/base/CSSLoader/CSSLoader.coffee
@@ -124,14 +124,15 @@
 					console.warn("CSSLoader.loadTheme: Caught event load second time, ignoring. IE does that for some reason.")
 					return

-				# ok, let's check if the style sheet loaded actually applies
-				# rules. IE tends to ignore 404 here.
-				for styleSheet in document.styleSheets
-					if styleSheet.href == css_href # this is the css loaded
-						if styleSheet.cssRules.length == 0 # we assume loading failed
-							console.error("CSSLoader: Loaded a stylesheet with no rules: ", css_href, styleSheet)
-							dfr.reject(css_href)
-							return
+				if CUI.browser.isIE
+					# ok, let's check if the style sheet loaded actually applies
+					# rules. IE tends to ignore 404 here.
+					for styleSheet in document.styleSheets
+						if styleSheet.href == css_href # this is the css loaded
+							if not styleSheet.cssRules?.length # we assume loading failed
+								console.error("CSSLoader: Loaded a stylesheet with no rules: ", css_href, styleSheet)
+								dfr.reject(css_href)
+								return

 				old_css_nodes = []
 				for css_node in DOM.matchSelector(document.head, "link[name='"+@__cssName+"']") # :not([loading])")


DataField

DataField

- When "hiding" a DataField on construct, trigger visibility event, so
  that a wrapping form can hide the date field.

--- src/base/DataField/DataField.coffee
+++ src/base/DataField/DataField.coffee
@@ -306,7 +306,7 @@
 		if @isDisabled()
 			@disable()
 		if @isHidden()
-			@hide()
+			@hide(true)
 		@setOpacity()
 		@callOnOthers("render")
 		@_onRender?(@)


OCD condition

OCD condition

--- src/blueprint-ast-to-runtime.coffee
+++ src/blueprint-ast-to-runtime.coffee
@@ -58,7 +58,7 @@
           }

         # expand URI parameters
-        if action.attributes?.uriTemplate? and action.attributes?.uriTemplate != ''
+        if action.attributes?.uriTemplate
           uri = action.attributes.uriTemplate
         else
           uri = resource['uriTemplate']


fbp-graph doesn't allow edges to non-existing nodes

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -477,79 +477,44 @@
       return

   describe 'with a faulty graph', ->
+    loader = null
+    before (done) ->
+      loader = new noflo.ComponentLoader root
+      loader.listComponents (err) ->
+        return done err if err
+        loader.components.Split = Split
+        done()
     it 'should fail on connect with non-existing component', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Baz'
       g.addNode 'Repeat2', 'Split'
       g.addEdge 'Repeat1', 'out', 'Repeat2', 'in'
       nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
-    it 'should fail on connect with missing target node', (done) ->
-      g = new noflo.Graph
-      g.addNode 'Repeat1', 'Split'
-      g.addNode 'Repeat2', 'Split'
-      g.addEdge 'Repeat1', 'out', 'Repeat3', 'in'
-      nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
-    it 'should fail on connect with missing source node', (done) ->
-      g = new noflo.Graph
-      g.addNode 'Repeat1', 'Split'
-      g.addNode 'Repeat2', 'Split'
-      g.addEdge 'Repeat3', 'out', 'Repeat2', 'in'
-      nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
-    it 'should fail on connect with missing IIP target node', (done) ->
-      g = new noflo.Graph
-      g.addNode 'Repeat1', 'Split'
-      g.addNode 'Repeat2', 'Split'
-      g.addEdge 'Repeat1', 'out', 'Repeat2', 'in'
-      g.addInitial 'hello', 'Repeat3', 'in'
-      nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
+      nw.loader = loader
+      nw.connect (err) ->
+        chai.expect(err).to.be.an 'error'
+        done()
     it 'should fail on connect with missing target port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
       g.addNode 'Repeat2', 'Split'
       g.addEdge 'Repeat1', 'out', 'Repeat2', 'foo'
       nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
+      nw.loader = loader
+      nw.connect (err) ->
+        chai.expect(err).to.be.an 'error'
+        done()
     it 'should fail on connect with missing source port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
       g.addNode 'Repeat2', 'Split'
       g.addEdge 'Repeat1', 'foo', 'Repeat2', 'in'
       nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
+      nw = new noflo.Network g
+      nw.loader = loader
+      nw.connect (err) ->
+        chai.expect(err).to.be.an 'error'
+        done()
     it 'should fail on connect with missing IIP target port', (done) ->
       g = new noflo.Graph
       g.addNode 'Repeat1', 'Split'
@@ -557,9 +522,7 @@
       g.addEdge 'Repeat1', 'out', 'Repeat2', 'in'
       g.addInitial 'hello', 'Repeat1', 'baz'
       nw = new noflo.Network g
-      nw.loader.listComponents (err) ->
-        return done err if err
-        nw.loader.components.Split = Split
-        nw.connect (err) ->
-          chai.expect(err).to.be.an 'error'
-          done()
+      nw.loader = loader
+      nw.connect (err) ->
+        chai.expect(err).to.be.an 'error'
+        done()


Basic tests for debug setting

Basic tests for debug setting

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -115,8 +115,8 @@
     describe 'with new edge', ->
       before ->
         n.loader.components.Split = Split
-        g.addNode 'A', 'Split',
-        g.addNode 'B', 'Split',
+        g.addNode 'A', 'Split'
+        g.addNode 'B', 'Split'
       after ->
         g.removeNode 'A'
         g.removeNode 'B'
@@ -640,3 +640,29 @@
       n = new noflo.Network g
       chai.expect(n.baseDir).to.equal root
       chai.expect(n.loader.baseDir).to.equal root
+  describe 'debug setting', ->
+    n = null
+    g = null
+    before (done) ->
+      g = new noflo.Graph
+      g.baseDir = root
+      n = new noflo.Network g
+      n.loader.listComponents (err, components) ->
+        return done err if err
+        n.loader.components.Split = Split
+        g.addNode 'A', 'Split'
+        g.addNode 'B', 'Split'
+        g.addEdge 'A', 'out', 'B', 'in'
+        n.connect done
+    it 'should initially have debug enabled', ->
+      chai.expect(n.getDebug()).to.equal true
+    it 'should have propagated debug setting to connections', ->
+      chai.expect(n.connections[0].debug).to.equal n.getDebug()
+    it 'calling setDebug with same value should be no-op', ->
+      n.setDebug true
+      chai.expect(n.getDebug()).to.equal true
+      chai.expect(n.connections[0].debug).to.equal n.getDebug()
+    it 'disabling debug should get propagated to connections', ->
+      n.setDebug false
+      chai.expect(n.getDebug()).to.equal false
+      chai.expect(n.connections[0].debug).to.equal n.getDebug()


Send nodeId instead so we can test complex graphs more easily

--- spec/Scoping.coffee
+++ spec/Scoping.coffee
@@ -23,7 +23,7 @@
     forwardGroups: true
   , (data, groups, out, callback) ->
     setTimeout ->
-      out.send data + 'foo'
+      out.send data + c.nodeId
       callback()
     , 1

@@ -37,7 +37,7 @@
   c.process (input, output) ->
     data = input.getData 'in'
     setTimeout ->
-      output.sendDone data + 'bar'
+      output.sendDone data + c.nodeId
     , 1

 describe 'Scope isolation', ->
@@ -81,7 +81,7 @@
         'CONN'
         '< 1'
         '< a'
-        'DATA bazfoobar'
+        'DATA bazWpPc'
         '>'
         '>'
         'DISC'


Close browser after last window exits on windows

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -146,6 +146,9 @@
       else
         @openPath(pathToOpen: "atom://config")

+    app.on 'window-all-closed', ->
+      app.quit() if process.platform is 'win32'
+
     app.on 'will-quit', =>
       fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.


Only released versions should check for updates

Fixes atom/release-notes#6

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -129,6 +129,8 @@

   # Enable updates unless running from a local build of Atom.
   setupAutoUpdater: ->
+    return if /\w{7}/.test(@version) # Only released versions should check for updates.
+
     autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}"

     autoUpdater.on 'checking-for-update', =>


Include possible overleaf subscription in feature setting

--- services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee
@@ -8,6 +8,7 @@
 logger = require("logger-sharelatex")
 ObjectId = require('mongoose').Types.ObjectId
 ReferalAllocator = require("../Referal/ReferalAllocator")
+Modules = require '../../infrastructure/Modules'

 oneMonthInSeconds = 60 * 60 * 24 * 30

@@ -106,17 +107,26 @@
 				SubscriptionLocator.getUsersSubscription user_id, cb
 			groupSubscription: (cb)->
 				SubscriptionLocator.getGroupSubscriptionMemberOf user_id, cb
+			overleafPlanCode: (cb) ->
+				Modules.hooks.fire 'getOverleafPlanCode', user_id, cb
 		async.series jobs, (err, results)->
 			if err?
 				logger.err err:err, user_id:user, "error getting subscription or group for _setUsersMinimumFeatures"
 				return callback(err)
-			{subscription, groupSubscription} = results
-			if subscription? and subscription.planCode? and subscription.planCode != Settings.defaultPlanCode
+			{subscription, groupSubscription, overleafPlanCode} = results
+			# Group Subscription
+			if groupSubscription? and groupSubscription.planCode?
+				logger.log user_id:user_id, "using group which user is memor of for features"
+				UserFeaturesUpdater.updateFeatures user_id, groupSubscription.planCode, callback
+			# Personal Subscription
+			else if subscription? and subscription.planCode? and subscription.planCode != Settings.defaultPlanCode
 				logger.log user_id:user_id, "using users subscription plan code for features"
 				UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
-			else if groupSubscription? and groupSubscription.planCode?
-				logger.log user_id:user_id, "using group which user is memor of for features"
-				UserFeaturesUpdater.updateFeatures user_id, groupSubscription.planCode, callback
+			# Overleaf Subscription
+			else if overleafPlanCode?
+				logger.log user_id: user_id, "using the overleaf plan for features"
+				UserFeaturesUpdater.updateFeatures user_id, overleafPlanCode, callback
+			# Default
 			else
 				logger.log user_id:user_id, "using default features for user with no subscription or group"
 				UserFeaturesUpdater.updateFeatures user_id, Settings.defaultPlanCode, (err)->


"Travis CI (Part 47)"

"Travis CI (Part 47)"

--- spec/linter-opencl-spec.coffee
+++ spec/linter-opencl-spec.coffee
@@ -13,6 +13,7 @@
       atom.config.set('linter-opencl.debug', false)
       atom.packages.activatePackage('language-opencl')
       atom.packages.activatePackage('linter-opencl')
+      console.log(atom.packages.resolvePackagePath('linter-opencl'))
       return atom.packages.activatePackage("linter-opencl")

   it 'should be in the packages list', ->
@@ -23,7 +24,7 @@

   it 'find an error in error.cl', ->
     waitsForPromise ->
-      filePath = dirPath + '/error.cl'
+      filePath = '/home/travis/build/BenSolus/linter-opencl/spec/files/error.cl'
       expect(filePath).toExistOnDisk()
       return atom.workspace.open(filePath).then (editor) ->
         return linter(editor).then (messages) ->
@@ -33,7 +34,7 @@

   it 'find an error in error.cl', ->
     waitsForPromise ->
-      filePath = dirPath + '/correct.cl'
+      filePath = '/home/travis/build/BenSolus/linter-opencl/spec/files/correct.cl'
       expect(filePath).toExistOnDisk()
       return atom.workspace.open(filePath).then (editor) ->
         return linter(editor).then (messages) ->


Load new page on derive

Load new page on derive

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -1,6 +1,8 @@
 define (require) ->
   _ = require('underscore')
   session = require('cs!session')
+  linksHelper = require('cs!helpers/links')
+  router = require('cs!router')
   EditableView = require('cs!helpers/backbone/views/editable')
   BookPopoverView = require('cs!./popovers/book/book')
   template = require('hbs!./header-template')
@@ -56,7 +58,7 @@
       @regions.button.append new BookPopoverView
         model: @model
         owner: @$el.find('.info .btn')
-
+
     isEditable: () ->
       if @model.isBook()
         return @model.get('currentPage')?.isEditable()
@@ -69,4 +71,14 @@
       $summary.find('h5').toggleClass('active')
       @$el.find('.abstract').toggle()

-    derivePage: () -> @model.deriveCurrentPage()
+    derivePage: () ->
+      options =
+        success: (model) =>
+          @model.setPage(@model.get('contents').indexOf(model)+1)
+          # Update the url bar path
+          href = linksHelper.getPath 'contents',
+            model: @model
+            page: @model.getPageNumber()
+          router.navigate(href, {trigger: false, analytics: true})
+
+      @model.deriveCurrentPage(options)


Fixed merge conflict that blocked badges from being awarded via rubrics at all

--- app/assets/javascripts/angular/controllers/GradeRubricCtrl.js.coffee
+++ app/assets/javascripts/angular/controllers/GradeRubricCtrl.js.coffee
@@ -169,29 +169,8 @@
         }
       )
     )
-    params
-
-  $scope.tierBadgesParams = ()->
-    params = []
-    angular.forEach($scope.gradedMetrics, (metric, index)->
-      angular.forEach(metric.tiers, (tier, index)->
-        angular.forEach(tier.badges, (badge, index)->
-          params.push {
-            name: badge.name,
-            tier_id: tier.id,
-            metric_id: tier.metric_id,
-            id: badge.id,
-            badge_id: badge.badge.id,
-            description: badge.description,
-            point_total: badge.point_total,
-            icon: badge.icon,
-            multiple: badge.multiple
-          }
-        )
-      )
-    )
-    params
-
+    return params
+
   $scope.gradedRubricParams = ()->
     {
       points_given: $scope.pointsGiven(),
@@ -247,8 +226,36 @@
       $scope.courseBadges[badge.id] = courseBadge
     )

-  TierBadgePrototype = (id, tier, badge, attrs={})->
-    this.id = id
+  MetricBadgePrototype = (metric, badge, attrs={})->
+    this.metric = metric
+    this.badge = badge
+    this.create()
+    this.name = badge.name
+    this.metric_id = metric.id
+    this.badge_id = badge.id
+    this.description = badge.description
+    this.point_total = badge.point_total
+    this.icon = badge.icon
+    this.multiple = badge.multiple
+
+  MetricBadgePrototype.prototype =
+    create: ()->
+      self = this
+
+      $http.post("/metric_badges", self.createParams()).success(
+        (data,status)->
+          self.id = data.existing_metric_badge.id
+      )
+      .error((err)->
+        alert("create failed!")
+        return false
+      )
+
+    createParams: ()->
+      metric_id: this.metric.id,
+      badge_id: this.badge.id
+
+  TierBadgePrototype = (tier, badge, attrs={})->
     this.tier = tier
     this.badge = badge
     this.name = badge.name
@@ -309,6 +316,21 @@
         description: self.description,
         rubric_id: self.rubricId
       }
+
+    loadMetricBadge: (metricBadge)->
+      self = this
+      courseBadge = self.availableBadges[metricBadge.badge_id]
+      loadedBadge = new MetricBadgePrototype(self, angular.copy(courseBadge))
+      self.badges[courseBadge.id] = loadedBadge # add metric badge to metric
+      delete self.availableBadges[courseBadge.id] # remove badge from available badges on metric
+
+    # Badges
+    loadMetricBadges: (metricBadges)->
+      self = this
+      angular.forEach(metricBadges, (metricBadge, index)->
+        if (self.availableBadges[metricBadge.badge_id])
+          self.loadMetricBadge(metricBadge)
+      )

     index: ()->
       this.order()
@@ -385,7 +407,7 @@
      loadTierBadge: (tierBadge)->
        self = this
        courseBadge = self.availableBadges[tierBadge.badge_id]
-       loadedBadge = new TierBadgePrototype(tierBadge.id, self, angular.copy(courseBadge))
+       loadedBadge = new TierBadgePrototype(self, angular.copy(courseBadge))
        self.badges[courseBadge.id] = loadedBadge # add tier badge to tier
        delete self.availableBadges[courseBadge.id] # remove badge from available badges on tier

@@ -397,4 +419,4 @@
            self.loadTierBadge(tierBadge)
        )

-  ]
+  ]


tewak publishable inputs js

tewak publishable inputs js

--- app/cells/folio/console/publishable_inputs/publishable_inputs.coffee
+++ app/cells/folio/console/publishable_inputs/publishable_inputs.coffee
@@ -10,8 +10,3 @@
       now = moment()
       $input.val now.format(format)
       $input.data(now.format())
-  else
-    $parent
-      .find('.f-c-publishable-inputs__input')
-      .val('')
-      .data('')


Use resource path from load settings

Use resource path from load settings

--- spec/theme-manager-spec.coffee
+++ spec/theme-manager-spec.coffee
@@ -6,11 +6,10 @@

 describe "ThemeManager", ->
   themeManager = null
+  resourcePath = atom.getLoadSettings().resourcePath

   beforeEach ->
-    themeManager = new ThemeManager
-      packageManager: atom.packages
-      resourcePath: window.resourcePath
+    themeManager = new ThemeManager({packageManager: atom.packages, resourcePath})

   afterEach ->
     themeManager.deactivateThemes()


array_extensions

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -235,13 +235,13 @@
 Call the given iterator once for each pair of objects in the array.

-  [1, 2, 3, 4].eachPair (a, b) ->
-    # 1, 2
-    # 1, 3
-    # 1, 4
-    # 2, 3
-    # 2, 4
-    # 3, 4
+[1, 2, 3, 4].eachPair (a, b) ->
+  # 1, 2
+  # 1, 3
+  # 1, 4
+  # 2, 3
+  # 2, 4
+  # 3, 4

 @name eachPair
@@ -275,16 +275,10 @@

 @name eachWithObject
 @methodOf Array#
-
-@param {Object} object The object to pass to the iterator on each
-visit.
-@param {Function} iterator Function to be called once for
-each element in the array.
-@param {Object} [context] Optional context parameter to be
-used as `this` when calling the iterator function.
-
-@returns this
-@type Array
+@param {Object} object The object to pass to the iterator on each visit.
+@param {Function} iterator Function to be called once for each element in the array.
+@param {Object} [context] Optional context parameter to be used as `this` when calling the iterator function.
+@returns {Array} this
 ###
 Array::eachWithObject = (object, iterator, context) ->
   this.each (element, i, self) ->


Upcase URL initialism.

Upcase URL initialism.

Yep, I'm a pedant, but I'm okay with it. :-)

--- menus/open-on-github.cson
+++ menus/open-on-github.cson
@@ -5,7 +5,7 @@
       'label': 'Open On GitHub'
       'submenu': [
         { 'label': 'Blame', 'command': 'open-on-github:blame' }
-        { 'label': 'Copy Url', 'command': 'open-on-github:copy-url' }
+        { 'label': 'Copy URL', 'command': 'open-on-github:copy-url' }
         { 'label': 'File', 'command': 'open-on-github:file' }
         { 'label': 'History', 'command': 'open-on-github:history' }
       ]


Fixed shadows

Fixed shadows

--- engine/material.coffee
+++ engine/material.coffee
@@ -92,7 +92,7 @@
             # GPU_DYNAMIC_SAMPLER_2DBUFFER = 12,
             # And 15 was distance wrongly, it's GPU_DYNAMIC_OBJECT_AUTOBUMPSCALE
             else if u.type == 14 # shadow texture
-                @textures.push @scene.objects[u.lamp].shadow_fb.texture
+                @textures.push {loaded: true, tex: @scene.objects[u.lamp].shadow_fb.texture}
                 tex_uniforms.push u.varname
             else if u.type == 13 and @scene # 2D image
                 tex = @scene.loader.load_texture u.image, u.filepath, u.filter, u.wrap, u.size


breadcrumbs update iwth correct or incorrect state as you complete questions on past-due homework review pages :smile:

--- src/components/task/breadcrumb.cjsx
+++ src/components/task/breadcrumb.cjsx
@@ -10,11 +10,16 @@
   componentWillMount: ->
     {crumb} = @props

+    TaskStepStore.setMaxListeners(20)
+    TaskStepStore.on('step.completed', @update)
+
     if crumb.type is 'step' and TaskStepStore.isPlaceholder(crumb.data.id)
       TaskStepStore.on('step.completed', @checkPlaceholder)
       TaskStepStore.on('step.loaded', @update)

   removeListeners: ->
+    TaskStepStore.setMaxListeners(10)
+    TaskStepStore.off('step.completed', @update)
     TaskStepStore.off('step.completed', @checkPlaceholder)
     TaskStepStore.off('step.loaded', @update)

@@ -29,9 +34,8 @@
   update: (id) ->
     {crumb} = @props

-    if (crumb.data.id is id) and not TaskStepStore.isPlaceholder(crumb.data.id)
+    if (crumb.data.id is id)
       @setState({})
-      @removeListeners()

   propTypes:
     crumb: React.PropTypes.object.isRequired


Ease future debugging of CAF demuxer

Ease future debugging of CAF demuxer

--- src/containers/caf/caf-demux.coffee
+++ src/containers/caf/caf-demux.coffee
@@ -34,20 +34,27 @@
         @list.push(buffer)

         if !@metadata && @stream.available(64) # Number out of my behind
-            if @stream.readString(4) != 'caff'
+            magicCookie = @stream.readString(4)
+
+            if magicCookie != 'caff'
                 console.log("Invalid CAF, does not begin with 'caff'"); debugger

-            @metadata = {}
-
-            @metadata.caff = {
-                version:            @stream.readUInt16()
-                flags:              @stream.readUInt16()
+            @metadata = {
+                caff: {
+                    version:        @stream.readUInt16()
+                    flags:          @stream.readUInt16()
+                }
             }

-            if @stream.readString(4) != 'desc'
+            descCookie = @stream.readString(4)
+
+            if descCookie != 'desc'
                 console.log("Invalid CAF, 'caff' is not followed by 'desc'"); debugger

-            unless @stream.readUInt32() == 0 && @stream.readUInt32() == 32
+            descSizeA = @stream.readUInt32()
+            descSizeB = @stream.readUInt32()
+
+            unless descSizeA == 0 && descSizeB == 32
                 console.log("Invalid 'desc' size, should be 32"); debugger

             @metadata.desc = {


Add detail progress

Add detail progress

--- src/sketch/sketch.coffee
+++ src/sketch/sketch.coffee
@@ -83,6 +83,46 @@
     get: -> @_workspace
     set: (v) -> @_workspace = v
   )
+
+  ###*
+  @static
+  @property {number} STEP_START
+    Constant number for step starting
+  @readonly
+  ###
+  @classProperty("STEP_START", value: STEP_START = 0)
+
+  ###*
+  @static
+  @property {number} STEP_PROGRESS
+    Constant number for step in progress
+  @readonly
+  ###
+  @classProperty("STEP_PROGRESS", value: STEP_PROGRESS = 1)
+
+  ###*
+  @static
+  @property {number} STEP_FINISHED
+    Constant number for step finished
+  @readonly
+  ###
+  @classProperty("STEP_FINISHED", value: STEP_FINISHED = 2)
+
+  ###*
+  @static
+  @property {number} STEP_SKIPPED
+    Constant number for step skipped
+  @readonly
+  ###
+  @classProperty("STEP_SKIPPED", value: STEP_SKIPPED = 3)
+
+  ###*
+  @static
+  @property {number} STEP_ABORTED
+    Constant number for step aborted
+  @readonly
+  ###
+  @classProperty("STEP_ABORTED", value: STEP_ABORTED = 4)

   #--------------------------------------------------------------------------------
   # Events
@@ -243,7 +283,9 @@
     return @_items.reduce(
       # Save all files in sketch
       (promise, item) =>
-        return item.editor.save() if item.editor?
+        if item.editor?
+          return promise unless newDirFs? or item.editor.modified
+          return promise.then(=> item.editor.save())
         return promise unless newDirFs?
         # Relocate file
         return promise.then(=>
@@ -427,7 +469,7 @@
     Build sketch
   @param {boolean} [force=false]
     Force build all files
-  @param {function(string,number,Error)} [progress=null]
+  @param {function(string,number,number,Error)} [progress=null]
     Hook function for show progress
   @return {Promise}
     Promise object
@@ -458,18 +500,17 @@
     done = 0
     return buildItems.reduce(
       (promise, item) =>
-        unless force
-          if (item.source?.lastModified or item.lastModified) < @_lastBuilt
-            return promise  # Skip
+        if !force and ((item.source?.lastModified or item.lastModified) < @_lastBuilt)
+          return promise  # Skip
         return promise.then(=>
           engine = item.engine
-          progress?(item.path, (100 * done / buildItems.length))
+          progress?(item.path, (100 * done / buildItems.length), STEP_START)
           return item.builder.build().catch((error) =>
-            progress?(item.path, null, error)
+            progress?(item.path, null, STEP_ABORTED, error)
             return Promise.reject(error)
           )
         ).then(=>
-          progress?(item.path, (100 * ++done / buildItems.length))
+          progress?(item.path, (100 * ++done / buildItems.length), STEP_FINISHED)
           return
         ).delay(1).then(=>
           @_lastBuilt = Date.now()
@@ -482,7 +523,7 @@
     Transfer sketch (Rubic->Board)
   @param {boolean} [force=false]
     Force download all files
-  @param {function(string,number,Error)} [progress=null]
+  @param {function(string,number,number,Error)} [progress=null]
     Hook function for show progress
   @return {Promise}
     Promise object
@@ -494,8 +535,9 @@
     done = 0
     return transferItems.reduce(
       (promise, item) =>
+        written = false
         return promise.then(=>
-          progress?(item.path, (100 * done / transferItems.length))
+          progress?(item.path, (100 * done / transferItems.length), STEP_START)
           return @_dirFs.readFile(item.path).catch((error) =>
             progress?(item.path, null, error)
             return Promise.reject(error)
@@ -512,14 +554,22 @@
               return  # Ignore errors on readFile()
             )
           ).then((compare) =>
-            if !force and compareData?
+            if !force and compare?
               return if arrayEqual(new Uint8Array(content), new Uint8Array(compare))
+            written = true
             return boardFs.writeFile(item.path, content).then(=>
               item.setTransfered()
+            ).catch((error) =>
+              progress?(item.path, null, STEP_ABORTED, error)
+              return Promise.reject(error)
             )
           )
         ).then(=>
-          progress?(item.path, (100 * ++done / transferItems.length))
+          progress?(
+            item.path
+            (100 * ++done / transferItems.length)
+            if written then STEP_FINISHED else STEP_SKIPPED
+          )
           return
         )
       @_board.requestFileSystem("internal").then((fs) =>


note if no rules

note if no rules

--- js/coffee/banrules.coffee
+++ js/coffee/banrules.coffee
@@ -7,7 +7,7 @@
     main = get('bread')
     app(main, mk('a', { style: "font-size: 2rem;", href: "javascript:void(addRule());"}, "Add a new rule"))
     app(main, mk('br'))
-    if isArray(json.rules)
+    if isArray(json.rules) and json.rules.length > 0
         banRules = json.rules
         app(main, mk('h3', {}, "Current rules:"))
         ul = mk('ul')
@@ -15,4 +15,6 @@
             li = mk('li', {}, item.name)
             app(ul, li)
         app(main, ul)
-        +    else
+        app(main, mk('h3', {}, "Doesn't seem like there are any rules yet..."))
+


Delete event.coffee

Delete event.coffee

更新為js檔案

--- src/devtools/panel/app/event.coffee
+++ src/devtools/panel/app/event.coffee
@@ -1,60 +0,0 @@
-# 事件控制器
-((exports)->
-  exports.tohken = {} if !exports.tohken
-  exports.tohken.event = {} if !exports.tohken.event
-  # 用来模拟数据的恢复
-  exports.tohken.event.handle = null
-  # 启动
-  exports.tohken.event.start = (environment)->
-    exports.tohken.event.fatigue.call(environment)
-    # 按三分钟更新的定时器
-    exports.tohken.event.handle = setInterval( ->
-      exports.tohken.event.action.call(environment)
-    , 1000)
-  # 终止
-  exports.tohken.event.stop = ->
-    return if exports.tohken.event.handle == null
-    clearInterval(exports.tohken.event.handle)
-  # 进程
-  exports.tohken.event.action = ->
-    exports.tohken.event.fatigue.call this
-    'done'
-  # 疲劳演算
-  exports.tohken.event.fatigue = ->
-    # 自然恢复，每分钟+3，上限为49，在出阵和远征时不进行回复
-    # 因为战斗后会返回新的数据所以跳过
-    return if @status['in_battle']
-    return if @status['last_router'] == 'battle/battle'
-    return if @status['last_router'] == 'sally/forward'
-    if @config['cadOffList'] == 1
-      return if @status['router'] == 'party/list'
-    change = false
-    # 开始循环增加
-    _.forEach @data['sword']['data'], (v, k)=>
-      # 换一种科学的计算方式…
-      sword   = @data['sword']['data'][k]
-      status  = parseInt(sword['status'], 10)
-      return if status == 3
-      fatigue = parseInt(sword['fatigue'], 10)
-      return if fatigue >= 49
-      last    = Date.parse "#{sword['recovered_at']} GMT+0900"
-      now     = Date.now()
-      time    = now - last
-      vfatigue = Math.floor(time / 60000)
-      if (fatigue + vfatigue) >= 49
-        vfatigue = 49 - fatigue
-      # 合并数据
-      if parseInt(sword['vfatigue'], 10) != parseInt(vfatigue, 10)
-        #三分钟三点
-        vfix = vfatigue - (vfatigue % 3)
-        # 合并数据
-        if (fatigue + vfatigue) >= 49
-          change = true
-          sword['vfatigue'] = vfatigue
-        else
-          change = true
-          sword['vfatigue'] = vfix
-      'done'
-    if change
-      exports.tohken.parse.view.call this, 'party'
-)(window)


safer core, does not rely on init being called

--- lib/core.coffee
+++ lib/core.coffee
@@ -9,14 +9,17 @@
 getTopicId = (topic) -> topic.split('.').pop()

 defaultConfig =
-  timeout: 2000

 module.exports = core =
+  config:
+    timeout: 2000
+  responders: {}
+
   init: (config) ->
+    _.merge core.config, config
+
     # Require each axiom module.
     # Pass to load.
-    core.config = _.merge {}, defaultConfig
-    core.config = _.merge core.config, config

     # Initialize mapping of 'responderIds's to responder metadata
     core.responders = {}


more positioning stuff

more positioning stuff


client: sidebar: show relevant message on sidebar when users arent allowed to create stacks

--- client/app/lib/components/sidebarstacksection/sidebarnostacks.coffee
+++ client/app/lib/components/sidebarstacksection/sidebarnostacks.coffee
@@ -1,8 +1,26 @@
 React = require 'kd-react'
 Link  = require 'app/components/common/link'
+canCreateStacks = require 'app/util/canCreateStacks'
+isAdmin = require 'app/util/isAdmin'

 module.exports = class SidebarNoStacks extends React.Component
+
+  renderMessage: ->
+    if canCreateStacks()
+

+        Your stacks has not been
+        fully configured yet, please
+        finalize onboarding steps.
+

+    else
+

+        Your stacks has not been
+        fully configured yet, please
+        wait until your admin sets
+        them up for you.
+

+

   render: ->
@@ -13,11 +31,7 @@
       </Link>

-

-            Your stacks has not been
-            fully configured yet, please
-            finalize onboarding steps.
-

+          {@renderMessage()}


Paste files

Paste files

--- src/trix/controllers/input_controller.coffee
+++ src/trix/controllers/input_controller.coffee
@@ -4,6 +4,8 @@
 {defer} = Trix.Helpers

 class Trix.InputController
+  pastedFileCount = 0
+
   @keyNames:
     0x08: "backspace"
     0x0D: "return"
@@ -93,12 +95,22 @@

     paste: (event) ->
       event.preventDefault()
-      if html = event.clipboardData.getData("text/html")
+      paste = event.clipboardData
+
+      if html = paste.getData("text/html")
         @delegate?.inputControllerWillPasteText()
         @responder?.insertHTML(html)
-      else if string = event.clipboardData.getData("text/plain")
+      else if string = paste.getData("text/plain")
         @delegate?.inputControllerWillPasteText()
         @responder?.insertString(string)
+
+      if "Files" in paste.types
+        if file = paste.items?[0]?.getAsFile?()
+          if not file.name and extension = extensionForFile(file)
+            file.name = "pasted-file-#{++pastedFileCount}.#{extension}"
+          @delegate?.inputControllerWillAttachFiles()
+          if @responder?.insertFile(file)
+            file.trixInserted = true

     compositionstart: (event) ->
       @delegate?.inputControllerWillStartComposition?()
@@ -145,3 +157,6 @@
         @delegate?.inputControllerWillPerformTyping()
         @responder?.deleteWordBackward()
         event.preventDefault()
+
+  extensionForFile = (file) ->
+    file.type?.match(/\/(\w+)$/)?[1]


ben stir me

ben stir me

--- scripts/simpleResponses.coffee
+++ scripts/simpleResponses.coffee
@@ -1,5 +1,6 @@
 # Commands:
 #   hubot kait disapproves - Kait disapproves!
+#   hubot ben stir* - Ben is sexy coffee


[client][app][activity] changed query

[client][app][activity] changed query

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -187,7 +187,7 @@
     currentGroup     = groupsController.getCurrentGroup()

     fetch = (slug)=>
-      if slug isnt 'koding' or not KD.config.useNeo4j
+      if slug isnt 'koding' and not KD.config.useNeo4j
         @fetchActivitiesDirectly options, callback
       else
         @isExempt (exempt)=>


global to window

global to window

--- client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/webtermappview.coffee
@@ -79,7 +79,7 @@

       if query.fullscreen
         windowController = KD.getSingleton("windowController")
-        windowController.clearUnloadListeners "global"
+        windowController.clearUnloadListeners "window"

         windowController.on "clientIdChanged", =>
           window.parent.postMessage "clientIdChanged", "*"


missing slash at end of directory for public link

--- salvus/page/project_files.cjsx
+++ salvus/page/project_files.cjsx
@@ -1131,6 +1131,8 @@
         url = document.URL
         url = url[0...url.indexOf('/projects/')]
         display_url = "#{url}/projects/#{@props.project_id}/files/#{misc.encode_path(single_item)}"
+        if @props.file_map[misc.path_split(single_item).tail].isdir
+            display_url += '/'

                 {display_url}


chore(angular): add build options for angular

--- config/config-options.coffee
+++ config/config-options.coffee
@@ -1,18 +1,21 @@
 # API - Prep Options
-# dist.dir                                          = (string) defaults to 'dist'
-# dist.client.dir                                   = (string) defaults to 'client'
-# dist.client[images|libs|scripts|styles|views].dir = (string) defaults to property name
-# dist.client.spa.file                              = (string) defaults to 'spa.html'
-# dist.server.dir                                   = (string) defaults to 'server'
-# dist.server.file                                  = (string) defaults to 'routes.js'
-# src.dir                                           = (string) defaults to 'src'
-# src.client.dir                                    = (string) defaults to 'client'
-# src.client[images|libs|scripts|styles|views].dir  = (string) defaults to property name
-# src.server.dir                                    = (string) defaults to 'server'
-# ports.server                                      = (int)    defaults to 3000
-# ports.reload                                      = (int)    defaults to 3001
-# order[scripts|styles][first|last]                 = (array)  expects file paths
-# ======================================================================================
+# dist.dir                                          = (string)  defaults to 'dist'
+# dist.client.dir                                   = (string)  defaults to 'client'
+# dist.client[images|libs|scripts|styles|views].dir = (string)  defaults to property name
+# dist.client.spa.file                              = (string)  defaults to 'spa.html'
+# dist.server.dir                                   = (string)  defaults to 'server'
+# dist.server.file                                  = (string)  defaults to 'routes.js'
+# src.dir                                           = (string)  defaults to 'src'
+# src.client.dir                                    = (string)  defaults to 'client'
+# src.client[images|libs|scripts|styles|views].dir  = (string)  defaults to property name
+# src.server.dir                                    = (string)  defaults to 'server'
+# ports.server                                      = (int)     defaults to 3000
+# ports.reload                                      = (int)     defaults to 3001
+# order[scripts|styles][first|last]                 = (array)   expects file paths
+# angular.modules                                   = (array)   additional angular modules
+# angular.version                                   = (string)  defaults to '1.4.x'
+# angular.dev.useTemplateCache                      = (boolean) defaults to false
+# ========================================================================================
 module.exports = (config, options) ->
 	log    = require "#{config.req.helpers}/log"
 	isType = require "#{config.req.helpers}/isType"
@@ -54,9 +57,17 @@
 		options.order.styles.first  = null if not isType.array options.order.styles.first
 		options.order.styles.last   = null if not isType.array options.order.styles.last

+	formatAngularOptions = ->
+		options.angular = {} if not isType.object options.angular
+		options.angular.dev     = {} if not isType.object options.angular.dev
+		options.angular.modules = null if not isType.array options.angular.modules
+		options.angular.version = null if not isType.string options.angular.version
+		options.angular.dev.useTemplateCache = null if not isType.boolean options.angular.dev.useTemplateCache
+
 	formatOptions()
 	formatServerOptions()
 	formatPortOptions()
+	formatAngularOptions()
 	formatOrderOptions()

 	# logs
@@ -67,4 +78,3 @@
 	# ======
 	options

-


c-enter inserts linebreak in console instead of `newline after current line`

--- keymaps/julia-client.cson
+++ keymaps/julia-client.cson
@@ -71,3 +71,10 @@
   'ctrl-j ctrl-c': 'julia-client:clear-console'
   'ctrl-j ctrl-k': 'julia-client:kill-julia'
   'ctrl-j ctrl-,': 'julia-client:settings'
+
+'.platform-win32 ink-console atom-text-editor:not([mini]),
+ .platform-linux ink-console atom-text-editor:not([mini])':
+  'ctrl-enter': 'editor:newline'
+
+'.platform-darwin ink-console atom-text-editor:not([mini])':
+  'cmd-enter': 'editor:newline'


Add 'linter-swiftlint.additionalOptions' configuration

--- lib/init.coffee
+++ lib/init.coffee
@@ -4,6 +4,11 @@
       type: 'string'
       title: 'the path to SwiftLint\'s configuration file that relative to project or absolute'
       default: '.swiftlint.yml'
+    additionalOptions:
+      type: 'string'
+      title: 'additional options to SwiftLint\'s `lint` command
+              other than `--config` and `--use-stdin`'
+      default: ''
     swiftlintExecutablePath:
       type: 'string'
       title: 'the path to SwiftLint'
@@ -36,6 +41,8 @@
             .find -> true # take first item if exists

         parameters = parameters.concat ["--config", config] if config and fs.existsSync(config)
+        additionalOptions = atom.config.get('linter-swiftlint.additionalOptions')
+        parameters = parameters.concat additionalOptions if additionalOptions
         options = {stdin: input, throwOnStdErr: false}
         helpers.exec(command, parameters, options).then (output) ->
           helpers.parse(output, regex, {filePath: filePath})


Delete HTML balises of error message server side.

Fixes #256

--- server/models/konnector.coffee
+++ server/models/konnector.coffee
@@ -119,7 +119,7 @@
             data =
                 isImporting: false
                 lastImport: new Date()
-                importErrorMessage: err.message
+                importErrorMessage: err.message.replace /<[^>]*>/ig, ''
         else
             data =
                 isImporting: false