and a second pass, still needs testing

and a second pass, still needs testing

--- src/contracts.coffee
+++ src/contracts.coffee
@@ -1,6 +1,91 @@
+###
+contracts.coffee
+http://disnetdev.com/contracts.coffee
+
+Copyright 2011, Tim Disney
+Released under the MIT License
+###
 Contracts = (->
+  "use strict"
+
+  enabled = true
+  unproxy = []
+
+  unproxy = (->
+    if WeakMap?
+      weak = true
+      unproxy = new WeakMap()
+    else
+      weak = false
+      unproxy = []
+
+    set: (p, c) ->
+      if weak
+        unproxy.set p, c
+      else
+        unproxy.push
+          proxy: p
+          contract: c
+
+    get: (p) ->
+      if weak
+        if (p isnt null) and typeof p is "object" or typeof p is "function"
+          unproxy.get p
+        else
+          undefined
+      else
+        pc = unproxy.filter (el) ->
+          p is el.proxy
+        throw "assumption failed: unproxy object stores multiple unique proxies"  if pc.length > 1
+        if pc.length is 1
+          pc[0]
+        else
+          undefined
+  )()
+
+  Utils =
+    # walk the proto chain to get the property descriptor
+    getPropertyDescriptor: (obj, prop) ->
+      o = obj
+      loop
+        desc = Object.getOwnPropertyDescriptor(o, prop)
+        return desc  if desc isnt `undefined`
+        o = Object.getPrototypeOf(o)
+        break unless o isnt null
+      undefined
+
+    # merges props of o2 into o1 return o1
+    merge: (o1, o2) ->
+      o3 = {}
+      f = (o) ->
+        for name of o
+          o3[name] = o[name]  if o.hasOwnProperty(name)
+
+      f o1
+      f o2
+      o3
+
+    hasNoHoles: (obj) ->
+      i = 0
+      while i < obj.length
+        return false  unless i of obj
+        i++
+      true
+
+    # if a1 and a2 are differently sized, return the empty list
+    zip: (a1, a2) ->
+      ret = []
+      if not Array.isArray(a1) or not Array.isArray(a2) or (a1.length isnt a2.length)
+        ret = []
+      else
+        i = 0
+        while i < a1.length
+          ret.push [ a1[i], a2[i] ]
+          i++
+      ret
+
+
   checkOptions = (a, b) ->
-    name = undefined
     pOpt = true
     for name of a
       if a[name] instanceof Contract
@@ -9,42 +94,57 @@
     for name of b
       pOpt = false  unless name of a
     pOpt
+
+  # Parses out filename and line number. Expects an array where the 0th entry
+  # is the file location and line number
+  # [...Str] -> [Str, Num] or null
   findCallsite = (trace) ->
-    match = undefined
     t = trace[0]
+    # string looks like {adsf}@file:///path/to/file.js:42
     re = /@(.*):(\d*)$/
     match = re.exec(t)
     if match
       [ match[1], parseInt(match[2], 10) ]
     else
       null
+
+  # (ModuleName, ModuleName, Str, [Contract]) -> \bot
   _blame = (toblame, other, msg, parents) ->
-    server = undefined
-    err = undefined
-    st = undefined
-    callsite = undefined
     ps = parents.slice(0)
     server = (if toblame.isServer then toblame else other)
-    m = "Contract violation: " + msg + "\n" + "Value guarded in: " + server + " -- blame is on: " + toblame + "\n"
+    m = "Contract violation: " + msg + "\n" +
+        "Value guarded in: " + server + " -- blame is on: " + toblame + "\n"
+
     m += "Parent contracts:\n" + ps.reverse().join("\n")  if ps
+
     err = new Error(m)
     st = printStackTrace(e: err)
     err.cleaned_stacktrace = st
+
+    # pretend the error was thrown at the place in usercode where the violation occured
     callsite = findCallsite(st)
     if callsite
+      # by setting these fields tools like firebug will link to the
+      # appropriate place in the user code
       err.fileName = callsite[0]
       err.lineNumber = callsite[1]
     throw err
+
+  # (ModuleName, ModuleName, Contract, any, [Contract]) -> \bot
   blame = (toblame, other, contract, value, parents) ->
     cname = contract.cname or contract
-    msg = "expected <" + cname + ">" + ", actual: " + (if typeof (value) is "string" then "\"" + value + "\"" else value)
+    msg = "expected <" + cname + ">" +
+          ", actual: " + (if typeof (value) is "string" then "\"" + value + "\"" else value)
+
     throw _blame(toblame, other, msg, parents)
-  blameM = (toblame, other, msg, parents) ->
-    _blame toblame, other, msg, parents
+
+  blameM = (toblame, other, msg, parents) -> _blame toblame, other, msg, parents
+
+  # creates an identity proxy handler
   idHandler = (obj) ->
     getOwnPropertyDescriptor: (name) ->
       desc = Object.getOwnPropertyDescriptor(obj, name)
-      desc.configurable = true  if desc isnt `undefined`
+      desc.configurable = true  if desc isnt undefined
       desc

     getPropertyDescriptor: (name) ->
@@ -61,7 +161,7 @@
     defineProperty: (name, desc) ->
       Object.defineProperty obj, name, desc

-    delete: (name) ->
+    'delete': (name) ->
       delete obj[name]

     fix: ->
@@ -69,7 +169,7 @@
         return Object.getOwnPropertyNames(obj).map((name) ->
           Object.getOwnPropertyDescriptor obj, name
         )
-      `undefined`
+      undefined

     has: (name) ->
       name of obj
@@ -93,41 +193,63 @@

     keys: ->
       Object.keys obj
+
+
   Contract = (cname, ctype, handler) ->
     @handler = handler
     @cname = cname
     @ctype = ctype
     @parent = null
+
+  Contract:: =
+    check: check = (val, pos, neg, parentKs, stack) ->
+      c = unproxy.get(val)
+      if c and c.equals(this)
+        # don't bother wrapping twice though we want to run the handler
+        # for the initialization check that happens and just ignore the return
+        @handler val, pos, neg, parentKs, stack
+        val
+      else
+        @handler val, pos, neg, parentKs, stack
+
+    toContract: ->
+      this
+
+    toString: ->
+      @cname
+
+    equals: (other) ->
+      throw "Equality checking must be overridden"
+
   ModuleName = (filename, linenum, isServer) ->
     @filename = filename
     @linenum = linenum
     @isServer = isServer
+
+  ModuleName::toString = ->
+    @filename + (if @linenum is "" then "" else (":" + @linenum))
+
+  Function::toContract = ->
+    name = "<user defined: " + @toString() + ">"
+    check this, name
+
   check = (p, name) ->
-    c = undefined
-    c = new Contract(name, "check", check = (val, pos, neg, parentKs, stack) ->
+    c = new Contract name, "check", (val, pos, neg, parentKs, stack) ->
       if p(val, stack)
         val
       else
         blame pos, neg, this, val, parentKs
-    )
+
     c.equals = (other) ->
       (@cname is other.cname) and (@handler is other.handler)

     c
+
   fun = (dom, rng, options) ->
-    callOnly = undefined
-    newOnly = undefined
-    cleanDom = undefined
-    domName = undefined
-    optionsName = undefined
-    contractName = undefined
-    newdom = undefined
-    newrng = undefined
-    calldom = undefined
-    callrng = undefined
-    c = undefined
     cleanDom = (dom) ->
+      # wrap the domain in array so we can be consistent
       dom = [ dom ]  if dom instanceof Contract
+      # don't allow required argument contracts to follow optional
       dom.reduce ((prevWasOpt, curr) ->
         if curr.ctype is "opt"
           true
@@ -139,82 +261,115 @@
       ), false
       dom

+    # dom is overloaded so check if was called as
+    # an object with the contracts for call/new
     if dom and dom.call and dom["new"]
+      # different rng/dom for call/new
       calldom = cleanDom(dom.call[0])
       callrng = dom.call[1]
       newdom = cleanDom(dom["new"][0])
       newrng = dom["new"][1]
       options = rng or {}
     else
+      # rng/dom for call/new are the same
       calldom = cleanDom(dom)
       callrng = rng
       newdom = calldom
       newrng = callrng
       options = options or {}
+
     callOnly = options and options.callOnly
     newOnly = options and options.newOnly
+
     throw "Cannot have a function be both newOnly and newSafe"  if callOnly and newOnly
     throw "Illegal arguments: cannot have both newOnly and a contract on 'this'"  if newOnly and options["this"]
+
     domName = "(" + calldom.join(",") + ")"
     optionsName = (if options["this"] then "{this: " + options["this"].cname + "}" else "")
     contractName = domName + " -> " + callrng.cname + " " + optionsName
+
     c = new Contract(contractName, "fun", (f, pos, neg, parentKs, stack) ->
-      callHandler = undefined
-      newHandler = undefined
-      handler = idHandler(f)
+      handler = idHandler f
       that = this
       parents = parentKs.slice(0)
-      p = undefined
       blame pos, neg, this, f, parents  if typeof f isnt "function"
+
       parents.push that
+
+      ###
+      options:
+        isNew: Bool   - make a constructor handler (to be called with new)
+        newSafe: Bool - make call handler that adds a call to new
+        pre: ({} -> Bool) - function to check preconditions
+        post: ({} -> Bool) - function to check postconditions
+        this: {...} - object contract to check 'this'
+      ###
       makeHandler = (dom, rng, options) ->
         functionHandler = ->
-          i = undefined
-          res = undefined
           args = []
-          boundArgs = undefined
-          bf = undefined
-          thisc = undefined
-          clean_rng = undefined
-          throw new Error("stack checking failed")  if options and options.checkStack and not (options.checkStack(stack))
-          blame neg, pos, "precondition: " + options.pre.toString(), "[failed precondition]", parents  unless options.pre(this)  if typeof options.pre is "function"
+
+          if options and options.checkStack and not (options.checkStack(stack))
+            throw new Error("stack checking failed")
+
+          if typeof options.pre is "function" and not options.pre this
+            # check pre condition
+            blame neg, pos,
+              "precondition: " + options.pre.toString(),
+              "[failed precondition]", parents
+
+          # check all the arguments
           i = 0
           while i < dom.length
+            # might pass through undefined which is fine (opt will take
+            # care of it if the argument is actually optional)
+            #
+            # blame is reversed
             args[i] = dom[i].check(arguments[i], neg, pos, parents, stack)
+            # assigning back to args since we might be wrapping functions/objects
+            # in delayed contracts
             i++
+
           if typeof rng is "function"
+            # send the arguments to the dependent range
             clean_rng = rng.apply(this, args)
           else
             clean_rng = rng
+
+          # apply the function and check its result
           if options.isNew or options.newSafe
+            # null is in the 'this' argument position for bind...
+            # bind will ignore the supplied 'this' when we call it with new
             boundArgs = [].concat.apply([ null ], args)
             bf = f.bind.apply(f, boundArgs)
             res = new bf()
             res = clean_rng.check(res, pos, neg, parents, stack)
           else
             if options["this"]
+              # blame is reversed
               thisc = options["this"].check(this, neg, pos, parents, stack)
             else
               thisc = this
             res = clean_rng.check(f.apply(thisc, args), pos, neg, parents, stack)
-          blame neg, pos, "failed postcondition: " + options.post.toString(), "[failed postcondition]", parents  unless options.post(this)  if typeof options.post is "function"
+
+          # check post condition
+          if typeof options.post is "function" and not options.post this
+            blame neg, pos,
+              "failed postcondition: " + options.post.toString(),
+              "[failed postcondition]", parents
           res

       if newOnly
         options.isNew = true
-        callHandler = ->
-          blameM neg, pos, "called newOnly function without new", parents
-
+        callHandler = -> blameM neg, pos, "called newOnly function without new", parents
         newHandler = makeHandler(@newdom, @newrng, options)
       else if callOnly
         options.isNew = false
-        newHandler = ->
-          blameM neg, pos, "called callOnly function with a new", parents
-
+        newHandler = -> blameM neg, pos, "called callOnly function with a new", parents
         callHandler = makeHandler(@calldom, @callrng, options)
-      else
+      else # both false...both true is a contract construction-time error and handled earlier
         callHandler = makeHandler(@calldom, @callrng, options)
         newHandler = makeHandler(@newdom, @newrng, options)
+
       p = Proxy.createFunction(handler, callHandler, newHandler)
       unproxy.set p, this
       p
@@ -225,140 +380,174 @@
     c.newrng = newrng
     c.raw_options = options
     c.equals = (other) ->
-      name = undefined
-      zipCDom = undefined
-      zipNDom = undefined
-      pCDom = undefined
-      pNDom = undefined
-      pOpt = undefined
+      # can can short circuit here if we're not testing against another contract
       return false  if not other instanceof Contract or other.ctype isnt @ctype
+
       zipCDom = Utils.zip(@calldom, other.calldom)
       zipNDom = Utils.zip(@newdom, other.newdom)
-      pCDom = (zipCDom.length isnt 0) and zipCDom.every((zd) ->
-        zd[0].equals zd[1]
-      )
-      pNDom = (zipNDom.length isnt 0) and zipNDom.every((zd) ->
-        zd[0].equals zd[1]
-      )
+
+      pCDom = (zipCDom.length isnt 0) and zipCDom.every (zd) -> zd[0].equals zd[1]
+      pNDom = (zipNDom.length isnt 0) and zipNDom.every (zd) -> zd[0].equals zd[1]
+
+      # this will "fail" equality testing if the options object has
+      # pre/post functions that are the "same" but not the same reference
       pOpt = checkOptions(@raw_options, other.raw_options)
+
       pOpt and pCDom and pNDom and (@callrng.equals(other.callrng)) and (@newrng.equals(other.newrng))
-
     c
+
   ctor = (dom, rng, options) ->
-    opt = Utils.merge(options,
+    opt = Utils.merge options,
       newOnly: true
-    )
+
     fun dom, rng, opt
+
   ctorSafe = (dom, rng, options) ->
-    opt = Utils.merge(options,
+    opt = Utils.merge options,
       newSafe: true
-    )
+
     fun dom, rng, opt
-  object = (objContract, options, name) ->
-    setSelfContracts = (c, toset) ->
-      i = undefined
-      name = undefined
-      childrenNames = [ "k", "k1", "k2", "flats", "ho", "calldom", "callrng", "newdom", "newrng" ]
-      if typeof c.oc isnt "undefined"
-        for name of c.oc
-          if c.oc[name] is self
-            c.oc[name] = toset
-          else setSelfContracts c.oc[name], toset  if c.oc[name].ctype isnt "object"
-      else
-        childrenNames.forEach (cName) ->
-          if typeof c[cName] isnt "undefined"
-            if Array.isArray(c[cName])
-              i = 0
-              while i < c[cName].length
-                if c[cName][i] is self
-                  c[cName][i] = toset
-                else setSelfContracts c[cName][i], toset  if c[cName][i].ctype isnt "object"
-                i++
-            else
-              if c[cName] is self
-                c[cName] = toset
-              else setSelfContracts c[cName], toset  if c[cName] isnt "object"
-    options = options or {}
+
+
+  object = (objContract, options = {}, name) ->
     objName = (obj) ->
-      if name is `undefined`
-        props = Object.keys(obj).map((propName) ->
+      if name is undefined
+        props = Object.keys(obj).map (propName) ->
           if obj[propName].cname
             propName + " : " + obj[propName].cname
           else
             propName + " : " + obj[propName].value.cname
-        , this)
+        , this
+
         "{" + props.join(", ") + "}"
       else
         name

     c = new Contract(objName(objContract), "object", (obj, pos, neg, parentKs) ->
-      missingProps = undefined
-      op = undefined
-      i = undefined
-      prop = undefined
-      contractDesc = undefined
-      objDesc = undefined
-      value = undefined
-      handler = idHandler(obj)
+      handler = idHandler obj
       that = this
       parents = parentKs.slice(0)
-      invariant = undefined
       parents.push this
-      blame pos, neg, this, obj, parentKs  unless obj instanceof Object
-      blame pos, neg, "[extensible object]", "[non-extensible object]", parents  if options.extensible is true and not Object.isExtensible(obj)
-      blame pos, neg, "[non-extensible]", "[extensible object]", parents  if options.extensible is false and Object.isExtensible(obj)
-      blame pos, neg, "[sealed object]", "[non-sealed object]", parents  if options.sealed is true and not Object.isSealed(obj)
-      blame pos, neg, "[non-sealed object]", "[sealed object]", parents  if options.sealed is false and Object.isSealed(obj)
-      blame pos, neg, "[frozen object]", "[non-frozen object]", parents  if options.frozen is true and not Object.isFrozen(obj)
-      blame pos, neg, "[non-frozen object]", "[frozen object]", parents  if options.frozen is false and Object.isFrozen(obj)
+
+      if not obj instanceof Object
+        blame pos, neg, this, obj, parentKs
+
+      if options.extensible is true and not Object.isExtensible(obj)
+        blame pos, neg, "[extensible object]", "[non-extensible object]", parents
+
+      if options.extensible is false and Object.isExtensible(obj)
+        blame pos, neg, "[non-extensible]", "[extensible object]", parents
+
+      if options.sealed is true and not Object.isSealed(obj)
+        blame pos, neg, "[sealed object]", "[non-sealed object]", parents
+
+      if options.sealed is false and Object.isSealed(obj)
+        blame pos, neg, "[non-sealed object]", "[sealed object]", parents
+
+      if options.frozen is true and not Object.isFrozen(obj)
+        blame pos, neg, "[frozen object]", "[non-frozen object]", parents
+
+      if options.frozen is false and Object.isFrozen(obj)
+        blame pos, neg, "[non-frozen object]", "[frozen object]", parents
+
+      # do some cleaning of the object contract...
+      # in particular wrap all object contract in a prop descriptor like object
+      # for symmetry with user defined contract property
+      # descriptors: object({ a: Num }) ==> object({ a: {value: Num} })
       for prop of @oc
+        # todo: commenting out for now to allow us to have an object contract prototype chain
+        # only reason not too allow this is if the user puts something silly on the chain.
+        # if(!this.oc.hasOwnProperty(prop)) {
+        #     continue;
+        # }
         contractDesc = @oc[prop]
         objDesc = Utils.getPropertyDescriptor(obj, prop)
+
+        # pull out the contract (might be direct or in a descriptor like {value: Str, writable: true})
         if contractDesc instanceof Contract
           value = contractDesc
         else
+          # case when defined as a contract property descriptor
           if contractDesc["value"]
             value = contractDesc["value"]
+          # something other than a descriptor
           else
             blameM pos, neg, "contract property descriptor missing value property", parents
+
         if objDesc
-          blame pos, neg, "[writable property: " + prop + "]", "[read-only property: " + prop + "]", parents  if contractDesc.writable is true and not objDesc.writable
-          blame pos, neg, "[read-only property: " + prop + "]", "[writable property: " + prop + "]", parents  if contractDesc.writable is false and objDesc.writable
-          blame pos, neg, "[configurable property: " + prop + "]", "[non-configurable property: " + prop + "]", parents  if contractDesc.configurable is true and not objDesc.configurable
-          blame pos, neg, "[non-configurable property: " + prop + "]", "[configurable property: " + prop + "]", parents  if contractDesc.configurable is false and objDesc.configurable
-          blame pos, neg, "[enumerable property: " + prop + "]", "[non-enumerable property: " + prop + "]", parents  if contractDesc.enumerable is true and not objDesc.enumerable
-          blame pos, neg, "[non-enumerable property: " + prop + "]", "[enumerable property: " + prop + "]", parents  if contractDesc.enumerable is false and objDesc.enumerable
+          # check the contract descriptors agains what is actually on the object
+          # and blame where apropriate
+          if contractDesc.writable is true and not objDesc.writable
+            blame pos, neg, "[writable property: #{prop}]", "[read-only property: #{prop}]", parents
+
+          if contractDesc.writable is false and objDesc.writable
+            blame pos, neg, "[read-only property: #{prop}]", "[writable property: #{prop}]", parents
+
+          if contractDesc.configurable is true and not objDesc.configurable
+            blame pos, neg, "[configurable property: #{prop}]", "[non-configurable property: #{prop}]", parents
+
+          if contractDesc.configurable is false and objDesc.configurable
+            blame pos, neg, "[non-configurable property: #{prop}]", "[configurable property: #{prop}]", parents
+
+          if contractDesc.enumerable is true and not objDesc.enumerable
+            blame pos, neg, "[enumerable property: #{prop}]", "[non-enumerable property: #{prop}]", parents
+
+          if contractDesc.enumerable is false and objDesc.enumerable
+            blame pos, neg, "[non-enumerable property: #{prop}]", "[enumerable property: #{prop}]", parents
+
+          # contract descriptors default to the descriptor on the value unless
+          # explicitly specified by the contrac
           @oc[prop] =
             value: value
             writable: contractDesc.writable or objDesc.writable
             configurable: contractDesc.configurable or objDesc.configurable
             enumerable: contractDesc.enumerable or objDesc.enumerable
         else
+          # property does not exist but we have a contract for it
           if value.ctype is "opt"
+            # the opt contract allows a property to be optional
+            # so just put in the contract with all the prop descriptors set to true
             @oc[prop] =
               value: value
               writable: true
               configurable: true
               enumerable: true
           else
-            blame pos, neg, this, "[missing property: " + prop + "]", parents
+            blame pos, neg, this, "[missing property: #{prop}]", parents
+
+      # check object invariant
       if options.invariant
         invariant = options.invariant.bind(obj)
-        blame neg, pos, "invariant: " + options.invariant.toString(), obj, parents  unless invariant()
+        blame neg, pos, "invariant: #{options.invariant.toString()}", obj, parents  unless invariant()
+
       handler.defineProperty = (name, desc) ->
-        blame neg, pos, "[non-extensible object]", "[attempted to change property descriptor of: " + name + "]", parents  if (options.extensible is false) or options.sealed or options.frozen
-        blame neg, pos, "[non-configurable property: " + name + "]", "[attempted to change the property descriptor of property: " + name + "]", parents  unless that.oc[name].configurable
+        # note: we coulad have also allowed a TypeError to be thrown by the system
+        # if in strict mode or silengtly fail otherwise but we're using the blame system
+        # for hopfully better error messaging
+        if (options.extensible is false) or options.sealed or options.frozen
+          # have to reverse blame since the client is the one calling defineProperty
+          blame neg, pos, "[non-extensible object]",
+            "[attempted to change property descriptor of: #{name}]", parents
+
+        if not that.oc[name].configurable
+          blame neg, pos, "[non-configurable property: #{name}]",
+            "[attempted to change the property descriptor of property: #{name}]", parents
+
         Object.defineProperty obj, name, desc

       handler["delete"] = (name) ->
         res = undefined
         invariant = undefined
-        blame neg, pos, (if options.sealed then "sealed" else "frozen") + " object", "[call to delete]", parents  if options.sealed or options.frozen
+        # have to reverse blame since the client is the one calling delete
+        if options.sealed or options.frozen
+          blame neg, pos, "#{if options.sealed then 'sealed' else 'frozen'} object",
+            "[call to delete]", parents
+
         res = delete obj[name]

         if options.invariant
           invariant = options.invariant.bind(obj)
-          blame neg, pos, "invariant: " + options.invariant.toString(), obj, parents  unless invariant()
+          blame neg, pos, "invariant: #{options.invariant.toString()}", obj, parents  unless invariant()

       handler["get"] = (receiver, name) ->
         if that.oc.hasOwnProperty(name)
@@ -369,27 +558,32 @@
           obj[name]

       handler.set = (receiver, name, val) ->
-        invariant = undefined
-        blame neg, pos, "non-extensible object", "[attempted to set a new property: " + name + "]", parents  if (options.extensible is false) and Object.getOwnPropertyDescriptor(obj, name) is `undefined`
-        blame neg, pos, "frozen object", "[attempted to set: " + name + "]", parents  if options.frozen
+        if (options.extensible is false) and Object.getOwnPropertyDescriptor(obj, name) is undefined
+          blame neg, pos, "non-extensible object", "[attempted to set a new property: #{name}]", parents
+
+        if options.frozen
+          blame neg, pos, "frozen object", "[attempted to set: #{name}]", parents
+
         if that.oc.hasOwnProperty(name)
-          blame neg, pos, "read-only property", "[attempted to set read-only property: " + name + "]", parents  unless that.oc[name].writable
+          if not that.oc[name].writable
+            blame neg, pos, "read-only property", "[attempted to set read-only property: #{name}]", parents
+          # have to reverse blame since the client is the one calling set
           obj[name] = that.oc[name]["value"].check(val, neg, pos, parents)
-        else if (options.arrayRangeContract and (options.arrayRange isnt `undefined`)) and (parseInt(name, 10) >= options.arrayRange)
+        else if (options.arrayRangeContract and (options.arrayRange isnt undefined)) and (parseInt(name, 10) >= options.arrayRange)
           obj[name] = options.arrayRangeContract.check(val, neg, pos, parents)
         else
           obj[name] = val
         if options.invariant
           invariant = options.invariant.bind(obj)
-          blame neg, pos, "invariant: " + options.invariant.toString(), obj, parents  unless invariant()
+          blame neg, pos, "invariant: #{options.invariant.toString()}", obj, parents  unless invariant()
         true

+      # making this a function proxy if object is also a
+      # function to preserve typeof checks
       if typeof obj is "function"
         op = Proxy.createFunction(handler, (args) ->
           obj.apply this, arguments
         , (args) ->
-          boundArgs = undefined
-          bf = undefined
           boundArgs = [].concat.apply([ null ], arguments)
           bf = obj.bind.apply(obj, boundArgs)
           new bf()
@@ -399,18 +593,56 @@
       unproxy.set op, this
       op
     )
+
     c.oc = objContract
     c.raw_options = options
+
+
+    # hook up the recursive contracts if they exist
+    setSelfContracts = (c, toset) ->
+      # all the different possible children names from the combinators (really kludgy)
+      childrenNames = [ "k", "k1", "k2", "flats", "ho", "calldom", "callrng", "newdom", "newrng" ]
+      # check each of the properties in an object contract
+      if typeof c.oc isnt "undefined"
+        for name of c.oc
+          # if this prop is the self contract replace it with the contract reference
+          if c.oc[name] is self
+            c.oc[name] = toset
+          # otherwise if it's a function contract then there might be nested
+          # self contracts so dive into them with the original toset reference
+          else
+            setSelfContracts c.oc[name], toset  if c.oc[name].ctype isnt "object"
+          # note that we don't dive into object contracts...each self contract
+          # thus binds to its enclosing object contract
+      else
+        # run through each of the children contracts (sorry, pretty kludgy)
+        childrenNames.forEach (cName) ->
+          if typeof c[cName] isnt "undefined"
+            # the if stored in an array go through those first
+            if Array.isArray(c[cName])
+              i = 0
+              while i < c[cName].length
+                if c[cName][i] is self
+                  c[cName][i] = toset
+                else
+                  # dive into nested contracts with the original toset reference
+                  setSelfContracts c[cName][i], toset  if c[cName][i].ctype isnt "object"
+                i++
+            else
+              if c[cName] is self
+                c[cName] = toset
+              else setSelfContracts c[cName], toset  if c[cName] isnt "object"
+
+
     setSelfContracts c, c
     c.equals = (other) ->
       return false  if not other instanceof Contract or other.ctype isnt @ctype
       checkOptions(@oc, other.oc) and checkOptions(@raw_options, other.raw_options)
-
     c
+
+
   arr = (ks) ->
-    i = undefined
-    rangeContract = undefined
-    rangeIndex = undefined
+    # todo might make sense to allow var args along with array arguments
     oc = {}
     name = ""
     prefix = ""
@@ -431,29 +663,21 @@
       arrayRange: rangeIndex
       arrayRangeContract: rangeContract
     , name
-  ___ = (k) ->
-    ->
-      k
-  or = ->
-    c = undefined
-    ks = undefined
-    name = undefined
-    flats = undefined
-    ho = undefined
+
+  ___ = (k) -> (-> k)
+
+  or_ = ->
     ks = [].slice.call(arguments)
-    flats = ks.filter((el) ->
-      el.ctype is "check"
-    )
-    ho = ks.filter((el) ->
-      el.ctype isnt "check"
-    )
+
+    flats = ks.filter (el) -> el.ctype is "check"
+    ho = ks.filter (el) -> el.ctype isnt "check"
     throw "Cannot have more than 1 higher order contract in 'or'"  if ho.length > 1
+
     name = ks.join(" or ")
     c = new Contract(name, "or", (val, pos, neg, parentKs) ->
-      i = undefined
-      lastBlame = undefined
       parents = parentKs.slice(0)
       parents.push this
+
       i = 0
       while i < flats.length
         try
@@ -462,231 +686,149 @@
           lastBlame = e
           continue
         i++
+
       if ho.length is 1
         @ho[0].check val, pos, neg, parents
       else
-        throw lastBlame
+        throw lastBlame # the last contract in the array still assigned blame so surface it
     )
+
     c.flats = flats
     c.ho = ho
     c.equals = (other) ->
-      zipFlats = undefined
-      pFlats = undefined
       return false  if not other instanceof Contract or other.ctype isnt @ctype
       zipFlats = Utils.zip(@flats, other.flats)
       pFlats = (zipFlats.length isnt 0) and zipFlats.every((zf) ->
         zf[0].equals zf[1]
       )
       pFlats and (@ho.equals(other.ho))
-
     c
-  and = (k1, k2) ->
-    c = undefined
-    c = new Contract(k1.cname + " and " + k2.cname, "and", (val, pos, neg, parentKs) ->
+
+  and_ = (k1, k2) ->
+    c = new Contract "#{k1.cname} and #{k2.cname}", "and", (val, pos, neg, parentKs) ->
       k1c = k1.check(val, pos, neg, parentKs)
       k2.check k1c, pos, neg, parentKs
-    )
+
     c.k1 = k1
     c.k2 = k2
+
     c.equals = (other) ->
       return false  if not other instanceof Contract or other.ctype isnt @ctype
       (@k1.equals(other.k1)) and (@k2.equals(other.k2))

     c
-  not = (k) ->
-    c = undefined
-    res = undefined
+
+  not_ = (k) ->
     throw "cannot construct a 'not' contract with a function or object contract"  if k.ctype is "fun" or k.ctype is "object"
-    c = new Contract("not " + k.cname, "not", (val, pos, neg, parentKs) ->
+
+    c = new Contract "not #{k.cname}", "not", (val, pos, neg, parentKs) ->
       try
         res = @k.check(val, pos, neg, parentKs)
         blame pos, neg, this, val, parentKs
       catch b
+        # inverting the original contract so return ok
         return res
-    )
+
+    c.k = k
+
+    c.equals = (other) ->
+      return false  if not other instanceof Contract or other.ctype isnt @ctype
+      @k.equals other.k
+    c
+
+  opt = (k) ->
+    c = new Contract "opt(#{k.cname})", "opt", (val, pos, neg, parentKs) ->
+      if val is undefined
+        # unsuplied arguments are just passed through
+        val
+      else
+        # arg is actually something so check the underlying contract
+        @k.check val, pos, neg, parentKs
+
     c.k = k
     c.equals = (other) ->
       return false  if not other instanceof Contract or other.ctype isnt @ctype
       @k.equals other.k
-
     c
-  opt = (k) ->
-    c = undefined
-    c = new Contract("opt(" + k.cname + ")", "opt", (val, pos, neg, parentKs) ->
-      if val is `undefined`
-        val
-      else
-        @k.check val, pos, neg, parentKs
-    )
-    c.k = k
-    c.equals = (other) ->
-      return false  if not other instanceof Contract or other.ctype isnt @ctype
-      @k.equals other.k
-
-    c
+
+  # note that this function is particular about where it is called from.
+  # it gets the filename/linenum combo from the file that called the
+  # function that called getModName (two levels up the stack).
+  # () -> ModuleName
   getModName = (isServer) ->
-    guardedAt = undefined
-    match = undefined
-    filename = undefined
-    linenum = undefined
     st = printStackTrace(e: new Error())
+    # in the stacktrace the frame above this one is where we were guarded/used
     guardedAt = st[2]
+    # pull out the filename (which will become our module) and line
+    # number (the location in the module where the guard/use occured)
+    # stack traces look like: {anonymous}()@file:///Path/to/file.js:4242
     match = /\/([^\/]*):(\d*)[\)]?$/.exec(guardedAt)
+
     if match
       filename = match[1]
       linenum = match[2]
     else
       filename = "unknown"
       linenum = "-1"
+
     new ModuleName(filename, linenum, isServer)
+
   guard = (k, x, server, setup) ->
     stack = []
     setup stack  if typeof setup is "function"
     unless server
+      # if a server wasn't provied, guess if from the stacktrace
       server = getModName(true)
     else
       server = new ModuleName(server, "", true)
+
+    # (ModuleName?) -> any
+    # technically the return is a contracted value but no way to
+    # tell unless the contract is violated
     use: (client, srvr) ->
-      unless client
+      if not client
         client = getModName(false)
       else
         client = new ModuleName(client, "", false)
+
       server = new ModuleName(srvr, "", false)  if srvr
+
       if (server.filename is client.filename) and (server.linenum is client.linenum)
         server.linenum = server.linenum + " (server)"
         client.linenum = client.linenum + " (client)"
+
       if enabled
         k.check x, server, client, [], stack
       else
         x
-  "use strict"
-  enabled = true
-  unproxy = []
-  unproxy = (->
-    unproxy = undefined
-    weak = undefined
-    if typeof (WeakMap) isnt "undefined"
-      weak = true
-      unproxy = new WeakMap()
-    else
-      weak = false
-      unproxy = []
-    set: (p, c) ->
-      if weak
-        unproxy.set p, c
-      else
-        unproxy.push
-          proxy: p
-          contract: c
-
-    get: (p) ->
-      pc = undefined
-      if weak
-        if (p isnt null) and typeof p is "object" or typeof p is "function"
-          unproxy.get p
-        else
-          `undefined`
-      else
-        pc = unproxy.filter((el) ->
-          p is el.proxy
-        )
-        throw "assumption failed: unproxy object stores multiple unique proxies"  if pc.length > 1
-        if pc.length is 1
-          pc[0]
-        else
-          `undefined`
-  )()
-  Utils =
-    getPropertyDescriptor: getPropertyDescriptor = (obj, prop) ->
-      o = obj
-      loop
-        desc = Object.getOwnPropertyDescriptor(o, prop)
-        return desc  if desc isnt `undefined`
-        o = Object.getPrototypeOf(o)
-        break unless o isnt null
-      `undefined`
-
-    merge: merge = (o1, o2) ->
-      o3 = {}
-      f = (o) ->
-        for name of o
-          o3[name] = o[name]  if o.hasOwnProperty(name)
-
-      f o1
-      f o2
-      o3
-
-    hasNoHoles: hasNoHoles = (obj) ->
-      i = 0
-      while i < obj.length
-        return false  unless i of obj
-        i++
-      true
-
-    zip: (a1, a2) ->
-      i = undefined
-      ret = []
-      if not Array.isArray(a1) or not Array.isArray(a2) or (a1.length isnt a2.length)
-        ret = []
-      else
-        i = 0
-        while i < a1.length
-          ret.push [ a1[i], a2[i] ]
-          i++
-      ret
-
-  Contract:: =
-    check: check = (val, pos, neg, parentKs, stack) ->
-      c = unproxy.get(val)
-      if c and c.equals(this)
-        @handler val, pos, neg, parentKs, stack
-        val
-      else
-        @handler val, pos, neg, parentKs, stack
-
-    toContract: ->
-      this
-
-    toString: ->
-      @cname
-
-    equals: (other) ->
-      throw "Equality checking must be overridden"
-
-  ModuleName::toString = ->
-    @filename + (if @linenum is "" then "" else (":" + @linenum))
-
-  Function::toContract = ->
-    name = "<user defined: " + @toString() + ">"
-    check this, name
-
-  any = (any = ->
+
+  any = (->
     c = new Contract("any", "any", (val) ->
       val
     )
     c.equals = (other) ->
       this is other
-
     c
   )()
+
   self = (->
     c = new Contract("self", "self", (val) ->
       val
     )
     c.equals = (other) ->
       this is other
-
     c
   )()
-  none = (none = ->
+
+  none = (->
     c = new Contract("none", "none", (val, pos, neg, parentKs) ->
       blame pos, neg, this, val, parentKs
     )
     c.equals = (other) ->
       this is other
-
     c
   )()
+
   combinators =
     check: check
     fun: fun
@@ -696,47 +838,58 @@
     arr: arr
     ___: ___
     any: any
-    or: or_
+    'or': or_
     none: none
-    not: not_
-    and: and_
+    'not': not_
+    'and': and_
     opt: opt
     guard: guard

   contracts =
-    Undefined: combinators.check((x) ->
-      `undefined` is x
-    , "Undefined")
-    Null: combinators.check((x) ->
+    Undefined: combinators.check (x) ->
+      undefined is x
+    , "Undefined"
+
+    Null: combinators.check (x) ->
       null is x
-    , "Null")
-    Num: combinators.check((x) ->
+    , "Null"
+
+    Num: combinators.check (x) ->
       typeof (x) is "number"
-    , "Num")
-    Bool: combinators.check((x) ->
+    , "Num"
+
+    Bool: combinators.check (x) ->
       typeof (x) is "boolean"
-    , "Bool")
-    Str: combinators.check((x) ->
+    , "Bool"
+
+    Str: combinators.check (x) ->
       typeof (x) is "string"
-    , "Str")
-    Odd: combinators.check((x) ->
+    , "Str"
+
+    Odd: combinators.check (x) ->
       (x % 2) is 1
-    , "Odd")
-    Even: combinators.check((x) ->
+    , "Odd"
+
+    Even: combinators.check (x) ->
       (x % 2) isnt 1
-    , "Even")
-    Pos: combinators.check((x) ->
+    , "Even"
+
+    Pos: combinators.check (x) ->
       x >= 0
-    , "Pos")
-    Nat: combinators.check((x) ->
+    , "Pos"
+
+    Nat: combinators.check (x) ->
       x > 0
-    , "Nat")
-    Neg: combinators.check((x) ->
+    , "Nat"
+
+    Neg: combinators.check (x) ->
       x < 0
-    , "Neg")
-    Arr: combinators.object(length: combinators.check((x) ->
+    , "Neg"
+
+    Arr: combinators.object(length: combinators.check (x) ->
       typeof (x) is "number"
-    , "Number"))
+    , "Number")
+
     Self: self
     Any: any
     None: none


fix tests

fix tests

--- test/unit/directives/phrase_javascript_spec.coffee
+++ test/unit/directives/phrase_javascript_spec.coffee
@@ -10,14 +10,14 @@
     jQuery: {}
   }
   enabled = null
-  authToken = "my-auth-token"
+  projectId = "my-project-id"

   beforeEach ->
     module('phrase')
     module ($provide) ->
       window.jQuery.getScript = jasmine.createSpy("getScript")
       $provide.value('$window', window)
-      $provide.value('phraseAuthToken', authToken)
+      $provide.value('phraseProjectId', projectId)
       $provide.value('phraseEnabled', enabled)
       null

@@ -34,8 +34,8 @@
         compiled = $compile(elem)(scope)
         scope.$digest()

-    it "should not attach the auth token", ->
-      expect(window.phrase_auth_token).toBeUndefined()
+    it "should not attach the project id", ->
+      expect(window.phrase_project_id).toBeUndefined()

     it "should not fetch the javascript snippet", ->
       expect(window.jQuery.getScript).not.toHaveBeenCalled()
@@ -53,8 +53,8 @@
         compiled = $compile(elem)(scope)
         scope.$digest()

-    it "should attach the auth token as a global variable", ->
-      expect(window.phrase_auth_token).toEqual("my-auth-token")
+    it "should attach the project id as a global variable", ->
+      expect(window.PHRASEAPP_CONFIG = {phrase_project_id}).toEqual("my-project-id")

     it "should fetch and evaluate the javascript snippet", ->
       expect(window.jQuery.getScript).toHaveBeenCalled()


Fix top menu on dashboard page

Fix top menu on dashboard page

--- static/scripts/controllers.coffee
+++ static/scripts/controllers.coffee
@@ -46,6 +46,10 @@

     DashboardCtrl = ($scope, $http, ngProgress, Tasks) ->
         ### Dashboard controller ###
+        $scope.isAuthenticated = window.isAuthenticated
+        if not window.isAuthenticated
+            window.location = '#'
+
         ngProgress.start()
         $http.get(
             '/api/v1/projects/project/?limit=0&with_success_percent=true'
@@ -66,6 +70,10 @@

     ManageCtrl = ($scope, $http, ngProgress) ->
         ### Manage projects page controller ###
+        $scope.isAuthenticated = window.isAuthenticated
+        if not window.isAuthenticated
+            window.location = '#'
+
         ngProgress.start()
         $scope.loading = true
         $http.get('/api/v1/projects/project/?fetch=true&limit=0').success (data) =>


KDFramework: simplified kdtooltipview

KDFramework: simplified kdtooltipview

--- components/tooltip/KDTooltip.coffee
+++ components/tooltip/KDTooltip.coffee
@@ -17,73 +17,50 @@

   constructor:(options,data)->

-    options.bind   or= "mouseenter mouseleave"
-    options.sticky  ?= no
+    options.bind    or= "mouseenter mouseleave"
+    options.sticky   ?= no
+    options.cssClass  = KD.utils.curryCssClass "kdtooltip", options.cssClass

     super options, data

-    @avoidDestroy = no
-    @visible      = yes
-    @parentView   = @getDelegate()
-
-    # Container for positioning in the viewport
-    @setClass 'kdtooltip'
-    @setClass options.viewCssClass if options.viewCssClass?
-
-    # Wrapper for the view and/or content of the tooltip
-    @wrapper    = new KDView
-      cssClass  : 'wrapper'
-
-    # Arrow Container for Tooltip design arrows
-    @arrow = new KDView
-      cssClass : 'arrow'
+    @visible    = yes
+    @parentView = @getDelegate()
+    @wrapper    = new KDView cssClass : 'wrapper'
+    @arrow      = new KDView cssClass : 'arrow'

     if @getOptions().animate then @setClass 'out' else @hide()

     @addListeners()
-    KDView.appendToDOMBody @
-    @remove()
-
-  _show = (tooltip)->
-    return if tooltip.visible
-    document.body.appendChild tooltip.$()[0]
-    KD.getSingleton('windowController').addLayer tooltip
-    tooltip.emit 'MouseEnteredAnchor'
-    tooltip.visible = yes
+    @getSingleton("windowController").on "ScrollHappened", @bound "hide"
+
+    @once "viewAppended", =>
+      o = @getOptions()
+
+      if o.view?
+        @setView o.view
+      else
+        @setClass 'just-text'
+        @setTitle o.title, o
+
+      @parentView.emit 'TooltipReady'
+
+      @addSubView @arrow
+      @addSubView @wrapper

   show:(event)->

-    return if @visible
-    o = @getOptions()
-
-    if o.selector
-      selectorEntered = no
-
-      @parentView.on 'mousemove', (mouseEvent)=>
-
-        if $(mouseEvent.target).is(o.selector) and selectorEntered is no
-          selectorEntered = yes
-          _show @
-        if not $(mouseEvent.target).is(o.selector) and selectorEntered
-          return if $(mouseEvent.target).is @$()
-          selectorEntered = no
-          @emit 'MouseLeftAnchor'
-    else _show @
+    {selector} = @getOptions()
+
+    return if selector
+
+    @display()

     super

   hide: (event)->
-
-    @utils.defer =>
-      o = @getOptions()
-
-      if event
-        return if o.selector and not $(event.target).is o.selector
-      @parentView.off 'mousemove'
-      # to throttle super need to call it explicitly
-      # KDView::show.call @
-      super
-      @emit 'MouseLeftAnchor'
+    super
+    @getDomElement().remove()
+    @getSingleton("windowController").removeLayer this

   update:(o = @getOptions(), view = null)->
     unless view
@@ -97,40 +74,22 @@

   addListeners:->

-    o = @getOptions()
-
-    if o.events
-      @parentView.bindEvent name for name in o.events
-
-    @parentView.on 'mouseenter', =>
-      if @parentView.tooltip
+    intentTimer = null
+    {events}    = @getOptions()
+
+    @parentView.bindEvent name for name in events
+
+    @parentView.on 'mouseenter',  =>
+      return if intentTimer
+      intentTimer = KD.utils.wait 77, =>
+        intentTimer = null
         @show()
-        @avoidDestroy = yes
-
-    unless o.selector
-      @parentView.on 'mouseleave', =>
-        @avoidDestroy = no
-        @delayedRemove 40
-
-    @on 'mouseenter', =>
-      @avoidDestroy = yes
-
-    @on 'mouseleave', (event)=>
-      return if $(event.target).is @parentView.$()
-      @avoidDestroy = no
-      @delayedRemove()
-
-    @on 'MouseEnteredAnchor', =>
-      @avoidDestroy = yes
-      @delayedDisplay()
-
-    @on 'MouseLeftAnchor', =>
-      @avoidDestroy = no
-      @delayedRemove()
-
-    @on 'ReceivedClickElsewhere', @bound "remove"
-
-  isCurrentlyRemovable:-> @avoidDestroy
+
+    @parentView.on 'mouseleave',  =>
+      return intentTimer = KD.utils.killWait intentTimer if intentTimer
+      KD.utils.wait 77, @bound "hide"
+
+    @on 'ReceivedClickElsewhere', @bound "hide"

   setView: (childView) ->
     return unless childView
@@ -145,45 +104,12 @@

   getView:-> @childView

-  delayedDisplay:(timeout = @getOptions().delayIn)->
-    @utils.killWait @displayTimer
-    @displayTimer = @utils.wait timeout, =>
-      if @isCurrentlyRemovable()
-        @display()
-      else
-        @delayedRemove()
-
-  remove:->
-
-    return unless @visible
-
-    KD.getSingleton('windowController').removeLayer @
-    document.body.removeChild @$()[0]
-    @visible = no
-
   destroy:->

-    @visible = no
     @parentView.tooltip = null
     delete @parentView.tooltip
     super

-  delayedRemove:(timeout = @getOptions().delayOut)->
-
-    return if @getOptions().sticky
-
-    @utils.killWait @deleteTimer
-    @deleteTimer = @utils.wait timeout, =>
-      unless @isCurrentlyRemovable()
-        if @getOptions().animate
-          @unsetClass 'in'
-          KD.getSingleton('windowController').enableScroll()
-          @utils.killWait @animatedDeleteTimer
-          @animatedDeleteTimer = @utils.wait 2000, @bound "remove"
-        else
-          KD.getSingleton('windowController').enableScroll()
-          @remove()
-
   translateCompassDirections:(o)->

     {placement,gravity} = o
@@ -195,14 +121,13 @@
   display:(o = @getOptions())->

     # converts NESW-Values to topbottomleftright and retains them in @getOptions
+    KDView.appendToDOMBody this
+    @getSingleton("windowController").addLayer this
     o = @translateCompassDirections o if o.gravity
     o.gravity = null

     @setClass 'in' if o.animate
-    @show()
-    @visible = yes
-    KD.getSingleton('windowController').disableScroll()
-    @setPosition o
+    @utils.defer => @setPosition o

   getCorrectPositionCoordinates:(o={},positionValues,callback=noop)->
     # values that can/will be used in all the submethods
@@ -305,31 +230,6 @@
     else
       @wrapper.updatePartial Encoder.htmlEncode title

-  viewAppended:->
-    super()
-    o = @getOptions()
-
-    if o.view?
-      @setView o.view
-    else
-      @setClass 'just-text'
-      @setTitle o.title, o
-
-    # @setTemplate @pistachio()
-    # @template.update()
-
-    @parentView.emit 'TooltipReady'
-
-    @addSubView @arrow
-    @addSubView @wrapper
-
-
-  # pistachio:->
-  #   """
-  #    {{> @arrow}}
-  #    {{> @wrapper}}
-  #   """
-
   directionMap = (placement, gravity)->
     if placement in ["top", "bottom"]
       if /e/.test gravity then "left"


Avoid matching "4.5" as two separate tokens

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -154,7 +154,7 @@
 	# Numeric literal
 	number:
 		name:  "constant.numeric.apl"
-		match: "(?:[¯0-9]|\\.[0-9])[¯0-9A-Za-z]*"
+		match: "¯?[0-9][¯0-9A-Za-z]*(?:\\.[¯0-9Ee][¯0-9A-Za-z]*)*|¯?\\.[0-9Ee][¯0-9A-Za-z]*"

 	# String literals


JS: Don't use manual triggering for tooltips in payout mode

--- evesrp/static/js/payouts.coffee
+++ evesrp/static/js/payouts.coffee
@@ -100,14 +100,10 @@
     # Tooltips
     $requests = jQuery '#requests'
     $requests.tooltip {
-        trigger: 'manual'
+        trigger: 'hover focus'
         html: false
         selector: '.copy-btn'
     }
-    $requests.on 'mouseover', '.copy-btn', (ev) ->
-        (jQuery this).tooltip 'show'
-    $requests.on 'mouseout', '.copy-btn', (ev) ->
-        (jQuery this).tooltip 'hide'
     # Popovers
     $requests.popover {
         trigger: 'focus'


KDView: Attach sets parent, detach unsets.

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -404,9 +404,11 @@
 # #
   attach:(view) ->
     @getElement().appendChild view.getElement()
+    view.setParent this

   detach: ->
     @parent?.getElement().removeChild @getElement()
+    @unsetParent()

   destroy: ->


Translate about

Translate about

--- def/th/about.cson
+++ def/th/about.cson
@@ -1,15 +1,15 @@
 About:
-  tabTitle: "About"
-  releaseNotes: "Release Notes"
-  newUpdate: "New Update"
-  upToDate: "Atom is up to date!"
-  checkingForUpdates: "Checking for updates..."
-  checkUpdatesNow: "Check now"
-  automaticDownloadUpdates: "Automatic download updates"
-  restartInstall: "Restart and install"
-  license: "License"
-  termsOfUse: "Terms of Use"
-  with: " with "
-  by: " by "
-  andTheAwesome: "And the awesome "
-  atomCommunity: "Atom Community"
+  tabTitle: "เกี่ยวกับ"
+  releaseNotes: "บันทึกการปรับปรุง"
+  newUpdate: "อัพเดตใหม่"
+  upToDate: "Atom อัพเดตแล้ว!"
+  checkingForUpdates: "กำลังตรวจสอบการอัพเดต..."
+  checkUpdatesNow: "ตรวจสอบตอนนี"
+  automaticDownloadUpdates: "ดาวน์โหลดส่วนอัพเดตอัตโนมัติ"
+  restartInstall: "เริ่มใหม่และติดตั้ง"
+  license: "การอนุญาต"
+  termsOfUse: "ข้อตกลงการใช้งาน"
+  with: " พร้อมกับ "
+  by: " โดย "
+  andTheAwesome: "และด้วยความสุดยอด "
+  atomCommunity: "กลุ่มผู้ใช้ Atom"


add stuff

add stuff

--- app/modules/new-vm/index.coffee
+++ app/modules/new-vm/index.coffee
@@ -20,11 +20,11 @@
     notify
   ) ->
     $scope.configDriveActive = false
-    templateDiskChanges = {}
+    existingDisks = {}
     $scope.saveChange = (position, propertyName, value) ->
-      if not templateDiskChanges[position]?
-        templateDiskChanges[position] = {}
-      templateDiskChanges[position][propertyName] = value
+      if not existingDisks[position]?
+        existingDisks[position] = {}
+      existingDisks[position][propertyName] = value

     {get} = xoApi
     removeItems = do ->
@@ -256,6 +256,7 @@
         template: template.id
         VDIs
         VIFs
+        existingDisks
       }

       # TODO:
@@ -287,6 +288,8 @@

         return xoApi.call('vm.set', data).then -> id
       .then (id) ->
+        console.log data
+        console.log 'Existing disks: ' + existingDisks
         # TODO
         # If we asked for bigger existing template disk(s)
         # we need to resize them after the VM is created


remove line breaks in para. for lab policies

--- app/pages/lab/lab-policies.cjsx
+++ app/pages/lab/lab-policies.cjsx
@@ -8,54 +8,29 @@
     content: '''
       ## Zooniverse Project Builder Policies, or So You Want To Launch a Project?

-      This page is for those who have used the builder tools to construct a project they're happy with,
-      and are now ready to launch it to the wider world. The first thing to understand is that there are
-      three modes which a live project can be in - _Public Projects_, _Beta Projects_, and _Zooniverse Projects_:
+      This page is for those who have used the builder tools to construct a project they're happy with, and are now ready to launch it to the wider world. The first thing to understand is that there are three modes which a live project can be in - _Public Projects_, _Beta Projects_, and _Zooniverse Projects_:

       Firstly, **Public Projects**.

-      Got your own crowd? Want only members of your collaboration to sort through your data? Then you can simply
-      share the URL with those you want to take part. There is a limit of 10,000 subjects per project - [contact us](#/about/contact)
-      if you'd like to host more. NOTE: This upload limit applies to all projects initially.
+      Got your own crowd? Want only members of your collaboration to sort through your data? Then you can simply share the URL with those you want to take part. There is a limit of 10,000 subjects per project - [contact us](#/about/contact) if you'd like to host more. NOTE: This upload limit applies to all projects initially.

-      _If you're planning a big public launch, or you think your project might attract significant attention, you
-      should still [let us know](#/about/contact) so we can support you properly. Otherwise you can get on with using your project._
+      _If you're planning a big public launch, or you think your project might attract significant attention, you should still [let us know](#/about/contact) so we can support you properly. Otherwise you can get on with using your project._

-      For project teams who want to work with the established, ever-keen Zooniverse community, the first step is
-      to turn your project into a **Beta Project**. You can do this from the main interface. Clicking the "_Apply for beta_"
-      button will allow the Zooniverse team to review your project - this is just a quick check to make sure it complies
-      with our rules (see below). Once it passes review, it will be available on the beta projects page and accessible
-      to volunteers.
+      For project teams who want to work with the established, ever-keen Zooniverse community, the first step is to turn your project into a **Beta Project**. You can do this from the main interface. Clicking the "_Apply for beta_" button will allow the Zooniverse team to review your project - this is just a quick check to make sure it complies with our rules (see below). Once it passes review, it will be available on the beta projects page and accessible to volunteers.

-      _During the beta phase, you should be checking for suggestions and comments on the project's Discussion pages.
-      Technical issues can be reported to us via [the GitHub repository](https://github.com/zooniverse/Panoptes-Front-End), but you will need to consider carefully
-      comments on workflow design. Remember to look at the results produced during this beta phase too - are they of
-      sufficient quality for your research? If not, then a redesign or a rethink is in order; go back, make changes
-      to your project and then submit it for Beta once again._
+      _During the beta phase, you should be checking for suggestions and comments on the project's Discussion pages. Technical issues can be reported to us via [the GitHub repository](https://github.com/zooniverse/Panoptes-Front-End), but you will need to consider carefully comments on workflow design. Remember to look at the results produced during this beta phase too - are they of sufficient quality for your research? If not, then a redesign or a rethink is in order; go back, make changes to your project and then submit it for Beta once again._

-      Following a successful beta, you should have a final version of the workflow that you're confident will produce
-      useful results, and a site containing all the information that volunteers will need. You can then submit your
-      project for launch, making it an official **Zooniverse Project** which will appear on our main page. A project
-      appearing on the main Zooniverse page must have as its goal the production of useful research, and so
-      there will be a further review. This final review may include requesting information from you about your
-      plans for using the results, and a review of the beta test results. We may also carry out a peer review
-      of your project.
+      Following a successful beta, you should have a final version of the workflow that you're confident will produce useful results, and a site containing all the information that volunteers will need. You can then submit your project for launch, making it an official **Zooniverse Project** which will appear on our main page. A project appearing on the main Zooniverse page must have as its goal the production of useful research, and so there will be a further review. This final review may include requesting information from you about your plans for using the results, and a review of the beta test results. We may also carry out a peer review of your project.

       At this point, you will likely want to upload more than the small subject set used for development and for the beta. We’ll get in touch to arrange that once your project has passed review.

-      Once the project has passed review, it will appear on the main project list, and we will consider it for inclusion
-      in a Zooniverse community newsletter or for other promotion. If you want to increase the chance that your project
-      will be promoted, then note that we will particularly favour projects whose researchers are proactive in engaging
-      with their community, on the discussion forums and elsewhere.
+      Once the project has passed review, it will appear on the main project list, and we will consider it for inclusion in a Zooniverse community newsletter or for other promotion. If you want to increase the chance that your project will be promoted, then note that we will particularly favour projects whose researchers are proactive in engaging with their community, on the discussion forums and elsewhere.

-      _If you'd like your project to launch on a particular day, perhaps to coincide with a press release,
-      please [get in touch](#/about/contact)._
+      _If you'd like your project to launch on a particular day, perhaps to coincide with a press release, please [get in touch](#/about/contact)._

       ### Rules and Regulations

-      By using the Zooniverse project builder, you're making use of our software and hosting, and so we
-      reserve the right to remove content for any reason whatsoever. In particular, we will remove
-      content and projects which are brought to our attention and where:
+      By using the Zooniverse project builder, you're making use of our software and hosting, and so we reserve the right to remove content for any reason whatsoever. In particular, we will remove content and projects which are brought to our attention and where:

       - The content is not legal.
       - The content is likely to cause offense, or is suitable only for an adult audience.
@@ -63,15 +38,9 @@

       If in doubt - please [get in touch](#/about/contact).

-      We're keen to find out how people want to use our platform, and at present it’s free to use. If you want more
-      than the standard 10,000 subjects per user, are likely to want more than 100GB of space for those
-      subjects, or will have more than a few hundred concurrent users, please let us know. We’re keen to help.
+      We're keen to find out how people want to use our platform, and at present it’s free to use. If you want more than the standard 10,000 subjects per user, are likely to want more than 100GB of space for those subjects, or will have more than a few hundred concurrent users, please let us know. We’re keen to help.

-      We do, however, reserve the right to have the final say on which projects appear on the main
-      page and which are promoted to the Zooniverse community. In particular, if you have a project
-      that is very close to an existing Zooniverse project, please [get in touch](#/about/contact) to discuss the situation;
-      it is in no-one's interest to, for example, have seventeen different galaxy classification projects
-      competing for attention.
+      We do, however, reserve the right to have the final say on which projects appear on the main page and which are promoted to the Zooniverse community. In particular, if you have a project that is very close to an existing Zooniverse project, please [get in touch](#/about/contact) to discuss the situation; it is in no-one's interest to, for example, have seventeen different galaxy classification projects competing for attention.

       Projects promoted to the Zooniverse community also need to:


need to set cell value

need to set cell value

--- examples/node/notebook.coffee
+++ examples/node/notebook.coffee
@@ -7,7 +7,8 @@

 notebook.create_notebook(imports: ['builtins', 'compat'], module_names: ['graph'])
 .done (nb) ->
-  input = notebook.add_input_cell nb, code: script
+  input = notebook.add_input_cell nb
+  notebook.set_cell_value input, script
   notebook.run input
   setTimeout ->
     console.log React.renderComponentToStaticMarkup nb.component


Modify text

Modify text

--- app/lib/en.coffee
+++ app/lib/en.coffee
@@ -607,7 +607,7 @@
   classify:
     heading: 'Classify'
     restart: 'Restart'
-    help: 'Help'
+    help: 'Examples'
     sloan: 'SDSS'
     candels: 'Hubble'
     ukidss: 'UKIDSS'


Disable opening/closing when manually opened/closed

--- extras/Studio.framer/app.coffee
+++ extras/Studio.framer/app.coffee
@@ -29,6 +29,11 @@
 # Sheet
 class ShareComponent
 	constructor: (@shareInfo) ->
+
+		# When this is set to true, the sheet won't hide on resize
+		# This is triggered by a manual open / close action
+		@fixed = false
+
 		@render()
 		@_startListening()

@@ -226,11 +231,16 @@
 				cursor: "default"

 		# Toggle sheet
-		@close.onClick => @_closeSheet()
-		@framerButton.onClick => @_openSheet()
+		@close.onClick =>
+			@_closeSheet()
+			@fixed = true
+		@framerButton.onClick =>
+			@fixed = true
+			@_openSheet()

 		# When the window resizes evaluate if the sheet needs to be hidden
-		Canvas.onResize (event) => @_checkCanvasSize()
+		Canvas.onResize =>
+			@_checkCanvasSize() if !@fixed


parsing arguments with units

parsing arguments with units

--- lib/csss.coffee
+++ lib/csss.coffee
@@ -93,6 +93,7 @@
     isNotParsableValue: /^([0-9]+(\.[0-9])*|\@*[a-zA-Z\_]+)$/
     doesLineBeginWithAttribute: null
     processPartsSeperator: /\n@begin\n/
+    argumentIsString: /^\s*((\'.*\')|(\".*\"))\s*$/
     cssColorValues: ->
       #return /\s(\#[a-z0-9]{3,6})\s*/g
       /\s(\#[a-z0-9]{3,6}|AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGray|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|Darkorange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGray|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGray|DimGrey|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gray|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGray|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGray|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGray|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\s*/ig
@@ -149,6 +150,7 @@
       # remove trailing whitespaces
       # s = String(s).replace /\s+$/, ''
       s = String(s).trim(s)
+      return s if /^\'.*\'$/.test(s) or /^\".*\"$/.test(s) and not escape
       # no escaping if we have a number or a variable
       enclose ?= false if /^[0-9]+(\.[0-9]+)*$/.test(s) or /^\s*(@[a-zA-Z\_]+(\(.*?\))*\s*)+$/.test(s)
       # exclude '@method()' from escape
@@ -167,17 +169,17 @@
         s = s.replace(@pattern.variableWithUnit(), "$1 + '$2'")

       # enclose?, only if no operator are found
-      if not hasOperators and /(@[a-zA-Z\_]+(\(.*?\)))/.test(s)
+      if not hasOperators and /(@[a-zA-Z\_]+(\[[a-zA-Z0-9\_]+\])*(\(.*?\)))*/.test(s)
         enclose ?= true
         if enclose
           s = @escapeCSSValue(s) if escape
           s = "'#{s}'"
-          return s.replace(/(@[a-zA-Z\_]+)(\(.*?\))/g, " ' + $1$2 + ' ")
+          return s.replace(/(@[a-zA-Z\_]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*/g, " ' + $1$2 + ' ")

       if onlyIfOperatorsExists and not hasOperators
         s = @escapeCSSValue(s) if escape
         return if enclose
-          "'#{s.replace(/(\s@[a-zA-Z]+)(\(.*?\))*\s/g, " ' + $1$2 + ' ")}'"
+          "'#{s.replace(/(\s@[a-zA-Z]+)(\[[a-zA-Z0-9\_]+\])*(\(.*?\))*\s/g, " ' + $1$2 + ' ")}'"
         else
           "#{s}"

@@ -223,7 +225,6 @@
       declarationsEnds = null
       for line in @original.split('\n')
         declarationsEnds = true if @pattern.isLineSelector.test(line) or @pattern.isMediaQuery
-        # console.log line, @pattern.isLineSelector.test(line)
         unless declarationsEnds
           declarationPart += '\n'+line
         else
@@ -322,16 +323,19 @@
     else
       line.replace /^(\s+)([a-zA-Z\-]+)/, "#{indent}'$2':"

-  parseInlineArguments: (line) ->
+  parseInlineArguments: (line) ->
     methodsFound = line.match /@[a-zA-Z\_]+\(.+\)/g
     if methodsFound
       for method in methodsFound
         # s.th. like @width(80%)
         parts = method.match /^(@[a-zA-Z\_]+)\(([a-zA-Z0-9\%]+)\)/
+        # table
+        #   @borderRadius(12px) -> ( 12 + 'px' )
         if parts?[2]
           # replace
           value = parts[2]
-          argument = if @pattern.isNotParsableValue.test(value) then value else @operateInline(value, enclose: true)
+          #argumentIsString = @pattern.argumentIsString.test(value)
+          argument = if @pattern.isNotParsableValue.test(value) then value else @operateInline(value, enclose: false)
           line = line.split(parts[0]).join(parts[1]+"(#{argument})")
     line


Normalize slashes in Directory::contains

--- src/directory.coffee
+++ src/directory.coffee
@@ -57,6 +57,9 @@
   # directory.
   contains: (pathToCheck) ->
     return false unless pathToCheck
+
+    # Normalize forward slashes to back slashes on windows
+    fullPath = fullPath.replace(/\//g, '\\') if process.platform is 'win32'

     if fs.isCaseInsensitive()
       directoryPath = @lowerCasePath


add missing

add missing

--- kites/sharedHosting/config-dev.coffee
+++ kites/sharedHosting/config-dev.coffee
@@ -29,6 +29,7 @@
   minAllowedUid         : 600 # minumum allowed UID for OS commands
   debugApi              : true
   processBaseDir        : cwd
+  cagefsctl             : "/usr/sbin/cagefsctl"
   baseMountDir          : "RemoteDrive"
   usersMountsFile       : ".mounts"
   ftpfs                 :


Fix a bug on drag'n'drop

Fix a bug on drag'n'drop

--- front/app/views/postit.coffee
+++ front/app/views/postit.coffee
@@ -58,7 +58,9 @@
     true

   leave: (e) =>
-    @el.classList.add 'moving'
+    e = e.originalEvent if e.originalEvent
+    [cid, _, _] = e.dataTransfer.getData("text/postit").split(',')
+    @el.classList.add 'moving' if @model.cid == cid
     true

   end: (e) =>


payment: uncomment subscriptions code

payment: uncomment subscriptions code

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -825,16 +825,16 @@
       #   JPaymentSubscription.createFreeSubscription account, (err) ->
       #     console.warn err  if err
       #     queue.next()
-      ->
-        options = targetOptions: selector: tags: "vm"
-        account.fetchSubscriptions null, options, (err = "", [subscription]) ->
-          return callback err  if err
-          return callback createKodingError "VM subscription not found, cannot debit"  unless subscription
-
-          subscription.debitPack tag: "vm", (err) ->
-            console.warn "VM pack couldn't be debited from subscription: #{err}"  if err
-            queue.next()
-      ->
+      # ->
+      #   options = targetOptions: selector: tags: "vm"
+      #   account.fetchSubscriptions null, options, (err = "", [subscription]) ->
+      #     return callback err  if err
+      #     return callback createKodingError "VM subscription not found, cannot debit"  unless subscription
+
+      #     subscription.debitPack tag: "vm", (err) ->
+      #       console.warn "VM pack couldn't be debited from subscription: #{err}"  if err
+      #       queue.next()
+      # ->
         JAccount.emit "AccountRegistered", account, referrer
         queue.next()
       ->


Add comment about extra event

Add comment about extra event

--- framer/LayerStateMachine.coffee
+++ framer/LayerStateMachine.coffee
@@ -55,6 +55,7 @@

 		# In the case of instant: true, onStart and onStop are called from within animation.start()
 		# This function is called once after animation.start() or in onStart, whichEver comes first.
+		# We could fix this by adding another event that fires before a delayed animation is started
 		stateSwitched = false
 		switchState = =>
 			return if stateSwitched


BF-100 fixed y-conversion

BF-100 fixed y-conversion


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -1,32 +1,3 @@
-###*
-The Durable module deactives a GameObject after a specified duration.
-If a duration is specified the object will update that many times. If -1 is
-specified the object will have an unlimited duration.
-
-

-enemy = GameObject
-  x: 50
-  y: 30
-  duration: 5
-
-enemy.include(Durable)
-
-enemy.I.active
-# => true
-
-5.times ->
-  enemy.update()
-
-enemy.I.active
-# => false
-

-
-@name Durable
-@module
-@constructor
-@param {Object} I Instance variables
-@param {Core} self Reference to including object
-###
 Expirable = (I, self) ->
   Object.reverseMerge I,
     duration: -1


Remove some console.log used for testing

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -110,10 +110,6 @@
                         num_replica = namespace.get('replica_affinities')[selected_machine.get('datacenter_uuid')]
                         if namespace.get('primary_uuid') is selected_machine.get('datacenter_uuid')
                             num_replica++
-                        console.log '----'
-                        console.log namespace.get 'name'
-                        console.log num_machines_in_datacenter
-                        console.log num_replica
                         if num_machines_in_datacenter <= num_replica
                             if not (selected_machine.get('id') of reason_unmovable_machines)
                                 reason_unmovable_machines[selected_machine.get('id')] = []


fix bug in worker cleanup

fix bug in worker cleanup

--- app/coffee/ASpellWorkerPool.coffee
+++ app/coffee/ASpellWorkerPool.coffee
@@ -16,7 +16,7 @@
 			return null
 		worker = new ASpellWorker(language, @options)
 		worker.pipe.on 'exit', () =>
-			@cleanup
+			@cleanup()
 		@PROCESS_POOL.push(worker)
 		return worker


Alphabetize extension arrays

Alphabetize extension arrays

--- src/file-view.coffee
+++ src/file-view.coffee
@@ -8,16 +8,16 @@
 class FileView extends View

   @COMPRESSED_EXTENSIONS: [
-    '.zip'
+    '.gz'
     '.jar'
     '.tar'
-    '.gz'
+    '.zip'
   ]

   @IMAGE_EXTENSIONS: [
+    '.gif'
     '.jpeg'
     '.jpg'
-    '.gif'
     '.png'
   ]


Comment out temperamental spec

Comment out temperamental spec

--- angular/test/protractor/poll_spec.coffee
+++ angular/test/protractor/poll_spec.coffee
@@ -19,7 +19,8 @@

     it 'starts a count', startPollTest('count')

-    it 'starts a dot vote', startPollTest 'dot_vote', ->
+    # I don't know why this one doesn't work; the others do...
+    xit 'starts a dot vote', startPollTest 'dot_vote', ->
       page.fillIn ".poll-dot-vote-form__add-option-input", "bananas"

     it 'starts a poll', startPollTest 'poll', ->


actually fix the profile tests

actually fix the profile tests

--- apps/profile/test/routes.coffee
+++ apps/profile/test/routes.coffee
@@ -89,7 +89,11 @@
       _.last(Backbone.sync.args)[2].success fabricate 'fair_profile'
       _.last(Backbone.sync.args)[2].success fabricate 'fair_profile'
       _.last(Backbone.sync.args)[2].success fabricate 'fair'
-      @res.render.args[0][0].should.equal '../fair/templates/index'
+      _.last(Backbone.sync.args)[2].success []
+      _.last(Backbone.sync.args)[2].success []
+      _.last(Backbone.sync.args)[2].success []
+      _.last(Backbone.sync.args)[2].success []
+      @res.render.args[0][0].should.equal '../fair/templates/overview'
       @res.render.args[0][1].profile.isFairOranizer()

   describe '#posts', ->
@@ -99,9 +103,5 @@
       _.last(Backbone.sync.args)[2].success fabricate 'fair_profile'
       _.last(Backbone.sync.args)[2].success fabricate 'fair_profile'
       _.last(Backbone.sync.args)[2].success fabricate 'fair'
-      _.last(Backbone.sync.args)[2].success []
-      _.last(Backbone.sync.args)[2].success []
-      _.last(Backbone.sync.args)[2].success []
-      _.last(Backbone.sync.args)[2].success []
-      @res.render.args[0][0].should.equal '../fair/templates/overview'
+      @res.render.args[0][0].should.equal '../fair/templates/index'
       @res.render.args[0][1].profile.isFairOranizer()


clean

clean

--- src/game/Game.coffee
+++ src/game/Game.coffee
@@ -49,7 +49,7 @@

         # Main game loop : Due to coffee script scope problems, I had to put
         # that here
-        @requestAnimation = () =>
+        @requestAnimation = =>
             @currentScene.update()
             @currentScene.draw()

@@ -88,8 +88,9 @@
     ###
     start: (sceneName) ->
         if sceneName is not undefined then @currentScene = @scenes[name]
-        if @currentScene is undefined or null then return
+        if @currentScene is undefined or null then return false

         @inputs.setup()
         @currentScene.setup()
         @requestAnimation()
+        return true


only write files that changed

only write files that changed

--- src/generator.coffee
+++ src/generator.coffee
@@ -212,13 +212,17 @@
     filesWritten = 0
     filesTotal = 0
     promises = for name, code of @generatedFiles
-      filesTotal++
-      if @lastGenerator?.generatedFiles[name] == code
-        @log "no change: #{@getRelativePath(name)}".grey if @verbose
-      else
-        filesWritten++
-        @log "writing: #{@getRelativePath(name).yellow}"
-        fsp.writeFile name, code
+      do (name, code) =>
+        filesTotal++
+        if @lastGenerator?.generatedFiles[name] == code
+          @log "no change: #{@getRelativePath(name)}".grey if @verbose
+        else
+          fsp.readFile name, 'utf8'
+          .then (currentContents) =>
+            if currentContents != code
+              filesWritten++
+              @log "writing: #{@getRelativePath(name).yellow}"
+              fsp.writeFile name, code
     Promise.all promises
     .then =>
       @log "#{filesWritten}/#{filesTotal} files #{if @lastGenerator then 'changed' else 'written'}"


using a different regex to solve backslash issue


Fix typo in initial config

Fix typo in initial config

Capitalization mismatch between initial config and the setting that is read causes the path to the flake8 executable to never be picked up

--- lib/init.coffee
+++ lib/init.coffee
@@ -1,6 +1,6 @@
 module.exports =
   configDefaults:
-    ExecutableDir: null
+    executableDir: null

   activate: ->
     console.log 'activate linter-python-flake8'


Add legacy warning parsing

Add legacy warning parsing

--- lib/init.coffee
+++ lib/init.coffee
@@ -1,4 +1,4 @@
-{BufferedProcess, CompositeDisposable} = require 'atom'
+{BufferedProcess, CompositeDisposable, Range} = require 'atom'

 module.exports =
   config:
@@ -103,21 +103,64 @@
         reResult = re.exec(toParse)
       ret

+    # only elixir 1.3+
     parseWarning = (toParse, textEditor) ->
+      console.log('to parse', toParse)
+
       ret = []
       re = ///
         warning:\ (.*)\n # warning
         \ \ (.*):([0-9]+) # file and file number
         ///g
       reResult = re.exec(toParse)
+
       while reResult?
-        ret.push
-          type: "Warning"
-          text: reResult[1]
-          filePath: path.join(projectPath(textEditor), reResult[2])
-          range: helpers.rangeFromLineNumber(textEditor, reResult[3] - 1)
+        console.log(reResult)
+        try
+          ret.push
+            type: "Warning"
+            text: reResult[1]
+            filePath: path.join(projectPath(textEditor), reResult[2])
+            # use range, this because the previous method of getting a range
+            # used the current buffer that is open in the texteditor, and it
+            # blows up if the line number is larger than the current file
+            # if the compiler returned warnings or errors of other files,
+            # it would thus frequently blow up and return no errors/warnings
+            # at all
+            range: new Range(
+              [reResult[3] - 1, 0],
+              [reResult[3] - 1, 0]
+            )
+        catch Error
+          console.log("Elixir Linter Error", Error)
         reResult = re.exec(toParse)
       ret
+
+    # parses warning for elixir 1.2 and below
+    parseLegacyWarning = (toParse, textEditor) ->
+      ret = []
+      re = ///
+        ([^:\n]*) # 1 - File name
+        :(\d+)  # 2 - Line
+        :\ warning
+        :\ (.*) # 3 - Message
+        ///g
+      reResult = re.exec(toParse)
+      while reResult?
+        try
+          ret.push
+            type: "Warning"
+            text: reResult[3]
+            filePath: path.join(projectPath(textEditor), reResult[1])
+            range: new Range(
+              [reResult[3] - 1, 0],
+              [reResult[3] - 1, 0]
+            )
+        catch Error
+          console.log("Elixir Linter Error", Error)
+        reResult = re.exec(toParse)
+      ret
+

     handleResult = (textEditor) ->
       (compileResult) ->
@@ -125,8 +168,10 @@
         try
           errorStack = parseError(resultString, textEditor)
           warningStack = parseWarning(resultString, textEditor)
+          legacyWarningStack = parseLegacyWarning(resultString, textEditor)
           (error for error in errorStack.concat(warningStack) when error?)
         catch Error
+          console.log(Error, "oops caught error....")
           [] # error in different file, just suppress

     getOpts = (textEditor) ->
@@ -156,6 +201,7 @@
       ]
       elixircArgs.push "-pa", item for item in getDepsPa(textEditor)
       elixircArgs.push textEditor.getPath()
+
       helpers.exec(@elixircPath, elixircArgs, getOpts(textEditor))
         .then(handleResult(textEditor))


some changes to string fns

some changes to string fns

--- app/assets/javascripts/lib/string.coffee
+++ app/assets/javascripts/lib/string.coffee
@@ -1,2 +1,13 @@
 String::underscore = ->
-  @replace(/([A-Z])/g, (match) -> "_#{match}").slice(1).toLowerCase()+  under = @replace(/([A-Z])/g, (match) -> "_#{match}")
+  if under[0] is "_"
+    under.slice(1).toLowerCase()
+  else
+    under.toLowerCase()
+
+String::camelcase = ->
+  @replace(/^([a-z])|_([a-z])|-([a-z])/g, (match) -> match.toUpperCase()).replace(/-|_/, '')
+
+String::dasherize = ->
+  @underscore().replace(/_/g, '-')
+


Fix scoreboard load on turbolinks nav

Fix scoreboard load on turbolinks nav

--- app/assets/javascripts/scoreboard.coffee
+++ app/assets/javascripts/scoreboard.coffee
@@ -1,7 +1,7 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
-chart = ""
+this.chart = ""

 draw_scoreboard = ->
   if !document.getElementById('scoreboard_graph')
@@ -9,8 +9,8 @@

   $.get 'teams/get_score_data', (response,status) ->

-    if chart == "" || typeof chart == "undefined"
-      chart = c3.generate
+    if this.chart == "" || typeof this.chart == "undefined"
+      this.chart = c3.generate
         bindto: '#scoreboard_graph'
         data:
           xs: JSON.parse(response.teams)
@@ -24,7 +24,7 @@
           y: label: text: 'Score'
         zoom: enabled: true
     else
-      chart.load
+      this.chart.load
         xs: JSON.parse(response.teams)
         columns: JSON.parse(response.scores)
     setTimeout draw_scoreboard, 30000


correct typo in the spinner JS code

correct typo in the spinner JS code

--- app/assets/javascripts/spinner.js.coffee
+++ app/assets/javascripts/spinner.js.coffee
@@ -5,9 +5,9 @@
       link.prev().show "fast"
       link.hide()

-    $(document).on "ajax:beforeSend", ".remote-select, .remote-check, .remove-radio", ->
+    $(document).on "ajax:beforeSend", ".remote-select, .remote-check, .remote-radio", ->
       $(this).next(".spinner").show()
-    $(document).on "ajax:complete", ".remote-select, .remote-check, .remove-radio", ->
+    $(document).on "ajax:complete", ".remote-select, .remote-check, .remote-radio", ->
       $(this).next(".spinner").hide()

     $(document).on "ajax:beforeSend", ".short-form", ->


Make reset game destroy old instance

Make reset game destroy old instance

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -57,6 +57,7 @@

   robot.hear /^cah reset-game$/i, (res) ->
     delete robot.brain.data.cah
+    delete global.game
     global.game = new Game(robot)
     start(robot, game)
     res.reply "💥💥💥💥💥  the game has been reset 💥💥💥💥💥"


Fix casing

Fix casing

--- fabricator/shapeways.coffee
+++ fabricator/shapeways.coffee
@@ -1,4 +1,4 @@
-OAuth = require('OAuth')
+OAuth = require('oauth')

 API_HOST = "http://api.shapeways.com"


Use camel case for question properties

Use camel case for question properties

--- app/client/questions/add_question.coffee
+++ app/client/questions/add_question.coffee
@@ -109,7 +109,7 @@

     question =
       text: formData.text
-      question_type: instance.type.get()
+      questionType: instance.type.get()
       properties: questionProperties

     Meteor.call "createQuestion", instance.form.id, question,


almost finish #3

almost finish #3

--- lib/main.coffee
+++ lib/main.coffee
@@ -15,37 +15,24 @@
   lastEditor: null
   locked: false

-  # Experiment
-  symbolsViewFileView: null
-  symbolsViewProjectView: null
-  modalPanelContainer: null
-
-  onWillJumpToHistory: (callback) ->
-    @emitter.on 'will-jump-to-history', callback
-
-  onDidJumpToHistory:  (callback) ->
-    @emitter.on 'did-jump-to-history', callback
-
   activate: (state) ->
     @subscriptions = new CompositeDisposable
     @editorSubscriptions = {}
-    @emitter       = new Emitter
-    @history       = new History settings.get('max')
+    @history = new History settings.get('max')

     @rowDeltaToRemember = settings.get('rowDeltaToRemember')
     settings.onDidChange 'rowDeltaToRemember', ({newValue}) =>
       @rowDeltaToRemember = newValue

     atom.commands.add 'atom-workspace',
-      'cursor-history:next':         => @next()
-      'cursor-history:prev':         => @prev()
-      'cursor-history:clear':        => @clear()
-      'cursor-history:dump':         => @dump()
-      'cursor-history:toggle-debug': => @toggleConfig('debug')
-
-    @modalPanelContainer = atom.workspace.panelContainers['modal']
-
-    @subscriptions.add @modalPanelContainer.onDidAddPanel ({panel, index}) =>
+      'cursor-history:next':  => @jump('Next')
+      'cursor-history:prev':  => @jump('Prev')
+      'cursor-history:clear': => @clear()
+      'cursor-history:dump':  => @dump()
+      'cursor-history:toggle-debug': => settings.toggle 'debug', log: true
+
+    modalPanelContainer = atom.workspace.panelContainers['modal']
+    @subscriptions.add modalPanelContainer.onDidAddPanel ({panel, index}) =>
       # itemKind in ['GoToView', 'GoBackView', 'FileView', 'ProjectView']
       itemKind = panel.getItem().constructor.name
       return unless itemKind in ['FileView', 'ProjectView']
@@ -55,234 +42,34 @@
         when 'ProjectView'
           @handleSymbolsViewProjectView panel

-
+    @observeTextEditor()
+
+    @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
+      if item instanceof TextEditor and item.getURI()
+        @handlePaneItemChanged item
+
+    @subscriptions.add atom.workspace.onWillDestroyPaneItem ({item}) =>
+      if item instanceof TextEditor and item.getURI()
+        LastEditor.saveDestroyedEditor item
+
+  observeTextEditor: ->
     @subscriptions.add atom.workspace.observeTextEditors (editor) =>
       @editorSubscriptions[editor.id] = new CompositeDisposable
       @handleChangePath(editor)

       @editorSubscriptions[editor.id].add editor.onDidChangeCursorPosition (event) =>
-        return if @isLocked()
+        return if @isLocked() # for performance
         return if event.oldBufferPosition.row is event.newBufferPosition.row # for performance.
         setTimeout =>
+          # When symbols-view's modal panel shown, we lock() but its delayed.
+          # So checking lock state here is important.
+          return if @isLocked()
           @handleCursorMoved event
         , 300

       @editorSubscriptions[editor.id].add editor.onDidDestroy =>
         @editorSubscriptions[editor.id]?.dispose()
         delete @editorSubscriptions[editor.id]
-
-    @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
-      if item instanceof TextEditor and item.getURI()
-        @handlePaneItemChanged item
-
-    @subscriptions.add atom.workspace.onWillDestroyPaneItem ({item}) =>
-      if item instanceof TextEditor and item.getURI()
-        LastEditor.saveDestroyedEditor item
-
-    @subscriptions.add @onWillJumpToHistory (direction) =>
-      @lock()
-
-    @subscriptions.add @onDidJumpToHistory (direction) =>
-      @unLock()
-      Flasher.flash() if settings.get('flashOnLand')
-      @history.dump direction
-
-    # @extendSymbolsView()
-
-  extendSymbolsView: ->
-    return unless pack = atom.packages.getLoadedPackage('symbols-view')
-
-    libPath = path.join(atom.packages.resolvePackagePath('symbols-view') , 'lib', 'symbols-view')
-    SymbolsView = require libPath
-    _openTag = SymbolsView::openTag
-    SymbolsView::openTag = (params...) ->
-      # if @constructor
-      # setTimeout =>
-      #   console.log "before", @panel.isVisible()
-      # , 300
-      console.log "Before"
-      console.log @stack
-      console.log @constructor.name
-      _openTag.call(this, params...)
-      console.log "After"
-      console.log @stack
-      # setTimeout =>
-      #   console.log "after", @panel.isVisible()
-      # , 300
-
-  handleChangePath: (editor) ->
-    orgURI = editor.getURI()
-
-    @editorSubscriptions[editor.id].add editor.onDidChangePath =>
-      newURI = editor.getURI()
-      @history.rename orgURI, newURI
-      @lastEditor.rename orgURI, newURI
-      orgURI = newURI
-
-  handlePaneItemChanged: (item) ->
-    # We need to track former active pane to know cursor position when active pane was changed.
-    @lastEditor ?= new LastEditor(item)
-
-    {editor, point, URI: lastURI} = @lastEditor.getInfo()
-    if not @isLocked() and (lastURI isnt item.getURI())
-      @history.add editor, point, lastURI, dumpMessage: "[Pane item changed] save history"
-
-    @lastEditor.set item
-    @debug "set LastEditor #{path.basename(item.getURI())}"
-
-  handleCursorMoved: ({oldBufferPosition, newBufferPosition, cursor}) ->
-    if @symbolsViewFileView?.panel.isVisible()
-      console.log "Symbol View visible returning"
-      return
-    return if @isLocked()
-    console.log "Moved!!"
-    editor = cursor.editor
-    return if editor.hasMultipleCursors()
-    return unless URI = editor.getURI()
-    # console.log
-
-    if @needRemember(oldBufferPosition, newBufferPosition)
-      @history.add editor, oldBufferPosition, URI, dumpMessage: "[Cursor moved] save history"
-
-  withPanel: (panel, {onShow, onHide}) ->
-    [oldEditor, oldPoint, newEditor, newPoint] = []
-    panelSubscription = panel.onDidChangeVisible (visible) =>
-      if visible
-        @lock()
-        {editor: oldEditor, point: oldPoint} = onShow()
-      else
-        {editor: newEditor, point: newPoint} = onHide()
-        if @needRemember(oldPoint, newPoint)
-          @history.add oldEditor, oldPoint, oldEditor.getURI(), dumpMessage: "[Cursor moved] save history"
-        @unLock()
-
-    @subscriptions.add panel.onDidDestroy ->
-      panelSubscription.dispose()
-
-  handleSymbolsViewFileView: (panel) ->
-    @symbolsViewFileView ?= {panel, item: panel.getItem()}
-    @withPanel panel,
-      onShow: =>
-        editor = @getActiveTextEditor()
-        # At the timing symbol-views panel show, first item in symobls
-        # already selected(this mean cursor position have changed).
-        # So we can't use TexitEditor::getCursorBufferPosition(), fotunately,
-        # symbols-view serialize buffer state initaially, we use this.
-        point = panel.getItem().initialState?.bufferRanges[0].start
-        {editor, point}
-      onHide: =>
-        editor = @getActiveTextEditor()
-        point = editor.getCursorBufferPosition()
-        {editor, point}
-        # console.log "move from #{point.toString()} -> #{newPoint.toString()}"
-
-    # [editor, point] = []
-    # @symbolsViewFileView = {panel, item: panel.getItem()}
-    # panelSubscription = panel.onDidChangeVisible (visible) =>
-    #   if visible
-    #     @lock()
-    #     editor = @getActiveTextEditor()
-    #     # At the timing symbol-views panel show, first item in symobls
-    #     # already selected(this mean cursor position have changed).
-    #     # So we can't use TexitEditor::getCursorBufferPosition(), fotunately,
-    #     # symbols-view serialize buffer state initaially, we use this.
-    #     point = panel.getItem().initialState?.bufferRanges[0].start
-    #   else
-    #     newPoint = editor.getCursorBufferPosition()
-    #     # console.log "move from #{point.toString()} -> #{newPoint.toString()}"
-    #     if @needRemember(point, newPoint)
-    #       @history.add editor, point, editor.getURI(), dumpMessage: "[Cursor moved] save history"
-    #     @unLock()
-    #
-    # @subscriptions.add panel.onDidDestroy ->
-    #   panelSubscription.dispose()
-
-  handleSymbolsViewProjectView: (panel) ->
-    [editor, point] = []
-    @symbolsViewProjectView = {panel, item: panel.getItem()}
-
-    panelSubscription = panel.onDidChangeVisible (visible) =>
-      if visible
-        @lock()
-        editor = @getActiveTextEditor()
-        point = editor.getCursorBufferPosition()
-      else
-        setTimeout =>
-          newPoint = @getActiveTextEditor().getCursorBufferPosition()
-          console.log "move from #{point.toString()} -> #{newPoint.toString()}"
-          if @needRemember(point, newPoint)
-            @history.add editor, point, editor.getURI(), dumpMessage: "[Cursor moved] save history"
-          @unLock()
-        , 300
-
-    @subscriptions.add panel.onDidDestroy ->
-      panelSubscription.dispose()
-
-
-  # Throttoling save to history once per 500ms.
-  # When activePaneItem change and cursorMove event happened almost at once.
-  # We pick activePaneItem change, and ignore cursor movement.
-  # Since activePaneItem change happen before cursor movement.
-  # Ignoring tail call mean ignoring cursor move happen just after pane change.
-  # This is mainly for saving only target position on `symbols-view:goto-declaration` and
-  # ignoring relaying position(first line of file of target position.)
-  # saveHistory: (editor, point, URI, options)->
-  #   @_saveHistory ?= _.throttle(@history.add.bind(@history), 500, trailing: false)
-  #   @_saveHistory editor, point, URI, options
-
-  # saveHistory: (editor, point, URI, options)->
-  #   @_saveHistory ?= _.throttle(@history.add.bind(@history), 500, trailing: false)
-  #   @_saveHistory editor, point, URI, options
-
-  needRemember: (oldBufferPosition, newBufferPosition) ->
-    # console.log "Called NeedRemember"
-    # Line number delata exceeds or not.
-    Math.abs(oldBufferPosition.row - newBufferPosition.row) > @rowDeltaToRemember
-
-  lock:     -> @locked = true
-  unLock:   -> @locked = false
-  isLocked: -> @locked
-
-  clear: -> @history.clear()
-
-  next: -> @jump 'Next'
-  prev: -> @jump 'Prev'
-
-  jump: (direction) ->
-    # Settings tab is not TextEditor instance.
-    return unless activeEditor = @getActiveTextEditor()
-    return unless entry = @history["get#{direction}"]()
-
-    if direction is 'Prev' and @history.isNewest()
-      point = activeEditor.getCursorBufferPosition()
-      URI   = activeEditor.getURI()
-      @history.pushToHead activeEditor, point, URI
-
-    @emitter.emit 'will-jump-to-history', direction
-
-    {URI, point} = entry
-
-    if activeEditor.getURI() is URI
-      # Jump within same paneItem
-
-      # Intentionally disable `autoscroll` to set cursor position middle of
-      # screen afterward.
-      activeEditor.setCursorBufferPosition point, autoscroll: false
-      # Adjust cursor position to middle of screen.
-      activeEditor.scrollToCursorPosition()
-      @emitter.emit 'did-jump-to-history', direction
-
-    else
-      # Jump to different pane
-      options =
-        searchAllPanes: settings.get('searchAllPanes')
-
-      atom.workspace.open(URI, options).done (editor) =>
-        editor.scrollToBufferPosition(point, center: true)
-        editor.setCursorBufferPosition(point)
-        @emitter.emit 'did-jump-to-history', direction
-
-    # @history.dump direction

   deactivate: ->
     for editorID, disposables of @editorSubscriptions
@@ -293,19 +80,133 @@
     @history?.destroy()
     @history = null

+  handleChangePath: (editor) ->
+    orgURI = editor.getURI()
+
+    @editorSubscriptions[editor.id].add editor.onDidChangePath =>
+      newURI = editor.getURI()
+      @history.rename orgURI, newURI
+      @lastEditor.rename orgURI, newURI
+      orgURI = newURI
+
+  handlePaneItemChanged: (item) ->
+    # We need to track former active pane to know cursor position when active pane was changed.
+    @lastEditor ?= new LastEditor(item)
+
+    {editor, point, URI: lastURI} = @lastEditor.getInfo()
+    if not @isLocked() and (lastURI isnt item.getURI())
+      @history.add editor, point, lastURI, dumpMessage: "[Pane item changed] save history"
+
+    @lastEditor.set item
+    @debug "set LastEditor #{path.basename(item.getURI())}"
+
+  handleCursorMoved: ({oldBufferPosition, newBufferPosition, cursor}) ->
+    editor = cursor.editor
+    return if editor.hasMultipleCursors()
+    return unless URI = editor.getURI()
+
+    if @needRemember(oldBufferPosition, newBufferPosition)
+      @history.add editor, oldBufferPosition, URI, dumpMessage: "[Cursor moved] save history"
+
+  needRemember: (oldBufferPosition, newBufferPosition) ->
+    # console.log [oldBufferPosition, newBufferPosition]
+    # Line number delata exceeds or not.
+    Math.abs(oldBufferPosition.row - newBufferPosition.row) > @rowDeltaToRemember
+
+  withPanel: (panel, {onShow, onHide}) ->
+    [oldEditor, oldPoint, newEditor, newPoint] = []
+    panelSubscription = panel.onDidChangeVisible (visible) =>
+      if visible
+        @lock()
+        {editor: oldEditor, point: oldPoint} = onShow()
+      else
+        # ProjectView delayed changing cursor position after panel hidden(),
+        setTimeout =>
+          {editor: newEditor, point: newPoint} = onHide()
+
+          # FIXME: oldPoint sometimes became undefined, so need to guard
+          if oldPoint and newPoint and @needRemember(oldPoint, newPoint)
+            @history.add oldEditor, oldPoint, oldEditor.getURI(), dumpMessage: "[Cursor moved] save history"
+          @unLock()
+        , 300
+
+    @subscriptions.add panel.onDidDestroy ->
+      panelSubscription.dispose()
+
+  handleSymbolsViewFileView: (panel) ->
+    @withPanel panel,
+      onShow: =>
+        # At the timing symbol-views panel show, first item in symobls
+        # already selected(this mean cursor position have changed).
+        # So we can't use TexitEditor::getCursorBufferPosition(), fotunately,
+        # symbols-view serialize buffer state initaially, we use this.
+        editor = @getEditor()
+        point  = panel.getItem().initialState?.bufferRanges[0].start
+        {editor, point}
+      onHide: =>
+        editor = @getEditor()
+        point  = editor.getCursorBufferPosition()
+        {editor, point}
+
+  handleSymbolsViewProjectView: (panel) ->
+    @withPanel panel,
+      onShow: =>
+        editor = @getEditor()
+        point  = editor.getCursorBufferPosition()
+        {editor, point}
+      onHide: =>
+        editor = @getEditor()
+        point  = editor.getCursorBufferPosition()
+        {editor, point}
+
+  lock:     -> @locked = true
+  unLock:   -> @locked = false
+  isLocked: -> @locked
+
+  clear: -> @history.clear()
+
+  jump: (direction) ->
+    # Settings tab is not TextEditor instance.
+    return unless activeEditor = @getEditor()
+    return unless entry = @history["get#{direction}"]()
+
+    if direction is 'Prev' and @history.isNewest()
+      point = activeEditor.getCursorBufferPosition()
+      URI   = activeEditor.getURI()
+      @history.pushToHead activeEditor, point, URI
+
+    @lock()
+
+    {URI, point} = entry
+
+    if activeEditor.getURI() is URI # Same pane.
+      # Intentionally disable `autoscroll` to set cursor position middle of
+      # screen afterward.
+      activeEditor.setCursorBufferPosition point, autoscroll: false
+      # Adjust cursor position to middle of screen.
+      activeEditor.scrollToCursorPosition()
+      @land direction
+    else
+      # Jump to different pane
+      options =
+        searchAllPanes: settings.get('searchAllPanes')
+
+      atom.workspace.open(URI, options).done (editor) =>
+        editor.scrollToBufferPosition(point, center: true)
+        editor.setCursorBufferPosition(point)
+        @land direction
+
+  land: (direction) ->
+    @unLock()
+    Flasher.flash() if settings.get('flashOnLand')
+    @history.dump direction
+
   debug: (msg) ->
     return unless settings.get('debug')
     console.log msg

-  serialize: ->
-    @history?.serialize()
-
-  getActiveTextEditor: ->
+  getEditor: ->
     atom.workspace.getActiveTextEditor()

   dump: ->
-    console.log @modalPanelContainer
     @history.dump '', true
-
-  toggleConfig: (param) ->
-    settings.toggle(param, true)