OX-460 fixed particle size for new trees

--- app/assets/javascripts/oxalis/geometries/skeleton.coffee
+++ app/assets/javascripts/oxalis/geometries/skeleton.coffee
@@ -120,7 +120,7 @@
     @nodesBuffer.push(new ResizableBuffer(3))

     @routes.push(new THREE.Line(routeGeometry, new THREE.LineBasicMaterial({color: treeColor, linewidth: 1}), THREE.LinePieces))
-    @nodes.push(new THREE.ParticleSystem(routeGeometryNodes, new THREE.ParticleBasicMaterial({color: treeColor, size: 5, sizeAttenuation : false})))
+    @nodes.push(new THREE.ParticleSystem(routeGeometryNodes, new THREE.ParticleBasicMaterial({color: treeColor, size: @model.route.getParticleSize(), sizeAttenuation : false})))
     @ids.push(treeId)

     @setActiveNode()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -1,6 +1,6 @@
 ###*
 The Controllable module adds simple movement
-when up, down, left, right ar
+when up, down, left, right are

   # create a player and include ClampBounds


resetvhost

resetvhost

--- config/dev.coffee
+++ config/dev.coffee
@@ -83,6 +83,6 @@
       have a vhost associated with this repository. Generally
       speaking, your first name is a good choice.
       """.replace /\n/g, ' '
-    uri         : 'http://zb.koding.com:3008/addVhost'
+    uri         : 'http://zb.koding.com:3008/resetVhost'
     webPort     : 3008
   pidFile           : '/tmp/koding.server.pid'


Minor clean-up to zip support

Minor clean-up to zip support

--- scripts/albums.coffee
+++ scripts/albums.coffee
@@ -9,10 +9,12 @@

     document.getElementById('album-download').addEventListener('click', (e) ->
         e.preventDefault()
+        e.target.classList.add('hidden')
         downloadAlbum()
     , false)
 , false)

+zipComplete = false
 downloadAlbum = () ->
     API.zipAlbum(window.filename, (result) ->
         if result.error?
@@ -22,6 +24,9 @@
             downloadFinishedZip()
         else if result.status == 'success'
             showDownloadModal()
+            window.onbeforeunload = ->
+                if not zipComplete
+                    return "If you leave this page, you won't get your zip file."
             window.setTimeout(pollZip, 1000)
         else
             alert('An error occured with your request.')
@@ -32,6 +37,7 @@
     iframe.src = "/download/#{window.filename}.zip"
     iframe.className = 'hidden'
     document.body.appendChild(iframe)
+    zipComplete = true

 pollZip = () ->
     API.zipAlbum(window.filename, (result) ->


Fix applyMarkdown, remove javascript manually

--- core/utils.coffee
+++ core/utils.coffee
@@ -126,23 +126,23 @@
         then hljs.highlight(lang,text).value
         else text

-    text = marked Encoder.htmlDecode text
+    text = Encoder.htmlDecode marked text

     sanitizeText = $(text)

     # Proxify images
-
     sanitizeText.find("img").each (i,element) =>
       src = element.getAttribute 'src'
       element.setAttribute "src", src?.replace /.*/, @proxifyUrl

     # Give all outbound links a target blank
-    sanitizeText.find("a").each (i,element) =>
+    sanitizeText.find("a").each (i, element) =>
+      href = element.getAttribute "href"
+      element.setAttribute "href", href.replace /^\s*javascript/i, 'JS_REMOVED'
       unless /^(#!)/.test $(element).attr("href")
         $(element).attr("target", "_blank")

     text = $("<div />").append(sanitizeText.clone()).remove().html() # workaround for .html()
-

   applyLineBreaks: (text)->
     return null unless text


getting all issues tested

getting all issues tested

--- test/issues.coffee
+++ test/issues.coffee
@@ -51,4 +51,94 @@
             assert.equal called, 2
             assert.equal open.length, 1
             assert.equal closed.length, 0
+            done.call null
+
+    'both not empty': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            cb null, [ { number: 1 } ]
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.ifError err
+            assert.equal called, 2
+            assert.equal open.length, 1
+            assert.equal closed.length, 1
+            done.call null
+
+    '99 results on a page': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            cb null, ( { number: i } for i in [ 0...99 ] )
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.ifError err
+            assert.equal called, 2
+            assert.equal open.length, 99
+            assert.equal closed.length, 99
+            done.call null
+
+    '100 results on a page': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            assert opts.page in [ 1, 2 ]
+            cb null, if opts.page is 1 then ( { number: i } for i in [ 0...100 ] ) else []
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.ifError err
+            assert.equal called, 4
+            assert.equal open.length, 100
+            assert.equal closed.length, 100
+            done.call null
+
+    '101 total results': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            assert opts.page in [ 1, 2 ]
+            cb null, if opts.page is 1
+                ( { number: i } for i in [ 0...100 ] )
+            else
+                [ { number: 100 } ]
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.ifError err
+            assert.equal called, 4
+            assert.equal open.length, 101
+            assert.equal closed.length, 101
+            assert.deepEqual open[100], { number: 100 }
+            assert.deepEqual closed[100], { number: 100 }
+            done.call null
+
+    '201 total results': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            assert opts.page in [ 1, 2, 3 ]
+            cb null, if opts.page in [ 1, 2 ]
+                ( { number: i } for i in [ (h = 100 * (opts.page - 1))...h + 100 ] )
+            else
+                [ { number: 200 } ]
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.ifError err
+            assert.equal called, 6
+            assert.equal open.length, 201
+            assert.equal closed.length, 201
+            for i in [ open, closed ]
+                for j in [ 100, 200 ]
+                    assert.deepEqual i[j], { number: j }
+            done.call null
+
+    'get all when not found': (done) ->
+        called = 0
+        req.all_issues = (opts, cb) ->
+            called += 1
+            cb null, { 'message': 'Not Found' }
+
+        issues.get_all {}, (err, [ open, closed ]) ->
+            assert.equal err, 'Not Found'
+            assert.equal called, 1
             done.call null


Add some delay when completing a task

Add some delay when completing a task

Closes https://github.com/gjtorikian/markdowntutorial.com/issues/45

--- js/lessons.coffee
+++ js/lessons.coffee
@@ -30,7 +30,7 @@
   $(el).bind('keyup', (e) ->
     previewMd(pos, editor)

-    $('.renderpad a').each (idx, el) ->
+    $('.renderpad a').each (idx, el) ->
       # In the link lesson, I don't want people to deal with typing http://
       href = $(el).attr('href').match(/www\..+/)
       href = "http://#{href}"
@@ -40,7 +40,9 @@
       if environment == "development"
         console.log "Did it for #{pos}"

-      showNextSection(el)
+      setTimeout (->
+        showNextSection(el)
+      ), 500
   )

 previewMd = (pos, editor) ->
@@ -53,10 +55,10 @@
   if (nextSection?)
     nextStepId = nextSection.data("step")
     currStepId = $(currentPad).closest(".displayed").data("step") ? -1
-
+
     # Don't display the panel unless it is the next one
-    return unless currStepId + 1 == nextStepId
-
+    return unless currStepId + 1 == nextStepId
+
     nextSection.fadeIn('slow')
     $('body').scrollTo(nextSection, ->
       nextSection.removeClass("toBeRevealed").addClass("displayed")
@@ -75,9 +77,9 @@
   # We need the second to last path segment because Jekyll makes pretty URLs by
   # creating directories named after pages with a single index.html file in each
   lessonNumber = pathSegments[pathSegments.length - 2]
-
+
   $(".toBeRevealed").each (idx, el) -> $(this).data("step", idx)
-
+
   $(".scratchpad").each (idx, el) ->
     setupAce(lessonNumber, idx, el)


Adding more snippets

Adding more snippets

--- grammars/gherkin.cson
+++ grammars/gherkin.cson
@@ -39,6 +39,11 @@
   }
 ]
 'repository':
+  'iridium_comment_markers':
+    'captures':
+      '0':
+        'name': 'keyword.language.iridium.marker'
+    'match': '^\\s*#IMPORT:'
   'comments':
     'captures':
       '0':
@@ -54,11 +59,6 @@
         'name': 'source'
       }
     ]
-  'iridium_comment_markers':
-    'captures':
-      '1':
-        'name': 'keyword.language.iridium.marker'
-    'match': '^\\s*#IMPORT:'
   'feature_keyword':
     'captures':
       '1':


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_clear.coffee
+++ src/engine_clear.coffee
@@ -24,7 +24,7 @@
     if I.clear
       I.canvas.clear()
     else if I.background
-      I.background.fill(canvas, 0, 0, )
+      I.background.fill(canvas, 0, 0, App.width, App.height)
     else if I.backgroundColor
       I.canvas.fill(I.backgroundColor)


Rename isFile() to isFileSync()

Rename isFile() to isFileSync()

--- lib/archive-edit-session.coffee
+++ lib/archive-edit-session.coffee
@@ -14,7 +14,7 @@
       new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)

   @deserialize: ({path}={}) ->
-    if fsUtils.isFile(path)
+    if fsUtils.isFileSync(path)
       new ArchiveEditSession(path)
     else
       console.warn "Could not build edit session for path '#{path}' because that file no longer exists"


Fixes reverse brace/bracket matching Fixes #116

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -54,11 +54,11 @@
     return if @editor.isFoldedAtCursorRow()
     return if @isCursorOnCommentOrString()

-    {position, currentPair, matchingPair} = @findCurrentPair(@matchManager.pairedCharacters)
+    {position, currentPair, matchingPair} = @findCurrentPair(false)
     if position
       matchPosition = @findMatchingEndPair(position, currentPair, matchingPair)
     else
-      {position, currentPair, matchingPair} = @findCurrentPair(@matchManager.pairedCharactersInverse)
+      {position, currentPair, matchingPair} = @findCurrentPair(true)
       if position
         matchPosition = @findMatchingStartPair(position, matchingPair, currentPair)

@@ -82,11 +82,11 @@

       #check if the character to the left is part of a pair
       if @matchManager.pairedCharacters.hasOwnProperty(text) or @matchManager.pairedCharactersInverse.hasOwnProperty(text)
-        {position, currentPair, matchingPair} = @findCurrentPair(@matchManager.pairedCharacters)
+        {position, currentPair, matchingPair} = @findCurrentPair(false)
         if position
           matchPosition = @findMatchingEndPair(position, currentPair, matchingPair)
         else
-          {position, currentPair, matchingPair} = @findCurrentPair(@matchManager.pairedCharactersInverse)
+          {position, currentPair, matchingPair} = @findCurrentPair(true)
           if position
             matchPosition = @findMatchingStartPair(position, matchingPair, currentPair)

@@ -162,11 +162,19 @@
     @editor.decorateMarker(marker, type: 'highlight', class: 'bracket-matcher', deprecatedRegionClass: 'bracket-matcher')
     marker

-  findCurrentPair: (matches) ->
+  findCurrentPair: (isInverse) ->
     position = @editor.getCursorBufferPosition()
+    if isInverse
+      matches = @matchManager.pairedCharactersInverse
+      position = position.traverse([0, -1])
+    else
+      matches = @matchManager.pairedCharacters
     currentPair = @editor.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
     unless matches[currentPair]
-      position = position.traverse([0, -1])
+      if isInverse
+        position = position.traverse([0, 1])
+      else
+        position = position.traverse([0, -1])
       currentPair = @editor.getTextInRange(Range.fromPointWithDelta(position, 0, 1))
     if matchingPair = matches[currentPair]
       {position, currentPair, matchingPair}


Try adding viewers in same group.

Try adding viewers in same group.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -86,6 +86,6 @@
         robot.http("https://tmi.twitch.tv/group/user/masonest/chatters")
             .get() (err, res, body) ->
                 chat = JSON.parse(body)
-                chatters = if chat.chatters.moderators then "Current chatters: #{chat.chatters.moderators}." else "Whoops, try again."
+                chatters = if chat.chatters.moderators, chat.chatters.viewers then "Current chatters: #{chat.chatters.moderators} #{chat.chatters.viewers}." else "Whoops, try again."
                 msg.send "#{chatters}"


Create slack mini-client

Create slack mini-client

--- scripts/delete.coffee
+++ scripts/delete.coffee
@@ -1,34 +1,43 @@
 _ = require 'lodash'
-Q = require('q')
+Q = require 'q'
+util = require 'util'

-loadRecentMessages = (http, token, channel, robotId, count) ->
-  count = count || 50
-  url = "https://slack.com/api/channels.history?" +
-    "token=#{token}&channel=#{channel}&count=#{count}"
-  deferred = Q.defer()
-  http(url).get() (err, res, body) ->
-    if err
-      deferred.reject "I don't know what I've said lately: #{err}"
-    else
-      messages = JSON.parse(body).messages
-      myMessages = _(messages)
-        .filter((msg) -> msg.user is robotId)
-        .sortBy('ts')
-        .reverse()
-        .value()
-      deferred.resolve(myMessages)
-  return deferred.promise
+class Client
+  constructor: (robot, msg) ->
+    @http = robot.http
+    @token = msg.robot.adapter.client.token
+    @channel = msg.message.rawMessage.channel
+    @robotId = msg.robot.adapter.client.self.id
+    @userId = msg.message.user.id

-deleteMessage = (http, token, channel, message) ->
-  url = "https://slack.com/api/chat.delete?" +
-    "token=#{token}&channel=#{channel}&ts=#{message.ts}"
-  deferred = Q.defer()
-  http(url).get() (err, res, body) ->
-    if err
-      deferred.reject "I couldn't delete my message: #{err}"
-    else
-      deferred.resolve()
-  return deferred.promise
+  loadRecentMessages: (count) ->
+    count = count || 50
+    url = "https://slack.com/api/channels.history?" +
+      "token=#{@token}&channel=#{@channel}&count=#{count}"
+    deferred = Q.defer()
+    @http(url).get() (err, res, body) =>
+      if err
+        deferred.reject "I don't know what I've said lately: #{err}"
+      else
+        messages = JSON.parse(body).messages
+        myMessages = _(messages)
+          .filter((msg) => msg.user is @robotId)
+          .sortBy('ts')
+          .reverse()
+          .value()
+        deferred.resolve(myMessages)
+    return deferred.promise
+
+  deleteMessage: (message) ->
+    url = "https://slack.com/api/chat.delete?" +
+      "token=#{@token}&channel=#{@channel}&ts=#{message.ts}"
+    deferred = Q.defer()
+    @http(url).get() (err, res, body) =>
+      if err
+        deferred.reject "I couldn't delete my message: #{err}"
+      else
+        deferred.resolve()
+    return deferred.promise

 selectMessage = (messages, age) ->
   deferred = Q.defer()
@@ -42,15 +51,12 @@

   robot.respond /delete(?:\s+(\d+)\s+ago)?/, (msg) ->
     age = (msg.match[1] || 1) - 1
-    token = msg.robot.adapter.client.token
-    channel = msg.message.rawMessage.channel
-    robotId = msg.robot.adapter.client.self.id
-    you = msg.message.user.id
-    # console.log {age: age, token: token, channel: channel, robotId: robotId, you: you}
+    client = new Client robot, msg
+    console.log util.inspect client

-    loadRecentMessages(robot.http, token, channel, robotId)
+    client.loadRecentMessages()
       .then((messages) -> selectMessage messages, age)
-      .then((message) -> deleteMessage robot.http, token, channel, message)
+      .then((message) -> client.deleteMessage message)
       .then(() -> msg.reply "Whoops, sorry about that!")
       .catch((err) -> msg.reply err)
       .done()


update option will read with domain and function

--- src/event-object.coffee
+++ src/event-object.coffee
@@ -19,11 +19,12 @@
       wihtoutLast=true if func?
       {obj,lastDomain} = @___.createDomainIfNotExist(domain,wihtoutLast)
       return obj.eb(lastDomain,func,option)
-    else if option? #only if options are set
+    else
+      console.log "no route!",sortArgs if eb?.debug
+
+    if option? #only if options are set
       console.log "eb#3" if eb?.debug
       @___.setOption option
-    else
-      console.log "no route!",sortArgs if eb?.debug

     return @

@@ -65,9 +66,11 @@
     return @obj

   setOption: (options) ->
+    console.log "options"
     # console.log @_functions if eb?.debug
     for key,opt of options
-      if key=='thisArg'
+      if key=='thisArg' and opt instanceof Object
+        console.log "set this arg to ",@obj
         @obj.thisArg = opt
       else if key == 'onReady' and opt instanceof Boolean #TODO: del?
         @onReady = opt


Fix a bug in WebSocketServer

Fix a bug in WebSocketServer

verify receiver connection when closing it.

Signed-off-by: Manson Li <f2a598d7717cc6757bef28b84a9ab02e5efc6048@matchstick.tv>

--- lib/comm/WebSocketServer.coffee
+++ lib/comm/WebSocketServer.coffee
@@ -22,7 +22,8 @@
         @txconn = {}

     close: ->
-        @rxconn.close() # senders connection close soon
+        @_closeSenders()
+        @rxconn?.close() # senders connection close soon

     setReceiverConn: (rxconn) ->
         @rxconn = rxconn


add まだある

add まだある

--- scripts/misawa.coffee
+++ scripts/misawa.coffee
@@ -8,7 +8,7 @@
 #   None
 #
 # Commands:
-#   hubot 俺の分,ゲザー,申し訳,休む,嫌な予感,俺は土下座,いいのか,かます,有給 - get ミサワ ネタ
+#   hubot 俺の分,ゲザー,申し訳,休む,嫌な予感,俺は土下座,いいのか,かます,有給,まだある - get ミサワ ネタ

 module.exports = (robot) ->
   robot.respond /俺の分$/, (msg) ->
@@ -38,3 +38,6 @@

   robot.respond /有給$/, (msg) ->
     msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/e/a/ea14c261.gif"
+
+  robot.respond /まだある$/, (msg) ->
+    msg.send "http://livedoor.4.blogimg.jp/jigokuno_misawa/imgs/7/3/73de70ed.gif"


Donkeybot goes to games, not chillen

Donkeybot goes to games, not chillen

--- scripts/mumble.coffee
+++ scripts/mumble.coffee
@@ -95,7 +95,7 @@
   _connectAsync: Promise.promisify mumble.connect

   _ready: (cli) ->
-    cli.user.moveToChannel 'Chillen'
+    cli.user.moveToChannel 'Games'

   _userMove: (user) ->
     if user.channel.name == 'Games' and user.name != @name


Concat people arrays.

Concat people arrays.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -86,9 +86,10 @@
         .get() (err, res, body) ->
             chat = JSON.parse(body)
             people = [chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers]
+            merged = merged.concat.apply(merged, people)

             setInterval(->
-                for username in people
+                for username in merged
                     points[username] += 5
                     return
             , 10000)


Trying that again.

Trying that again.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -131,10 +131,10 @@
             # Also only returns active users at the moment
             # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]

-            output1 = [robot.brain.data.points]
-            output2 = [robot.brain.data.points.usernames]
-            output3 = [robot.brain.data.points.points]
-            output4 = [robot.brain.data.points.usernames.points]
+            output1 = [robot.brain.get('data.points')]
+            output2 = [robot.brain.get('data.points.usernames')]
+            output3 = [robot.brain.get('data.points.points')]
+            output4 = [robot.brain.get('data.points.usernames.points')]

             outputs = ["Output 1: #{output1}, Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}"]


Will need to Google this next.

Will need to Google this next.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -90,5 +90,5 @@

             timerMethod = ->
                 for username in people
-                    points[username] =+ 5
+                    points =+ 5
                     return


Try robot.brain.get for data.

Try robot.brain.get for data.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -111,10 +111,13 @@
             pointcount = msg.match[1]

             # Currently only seems to get results from in-chat users at time of command
-            score = ["#{robot.brain.people.username} has #{robot.brain.people.points[username]}"]
+            savednames = robot.brain.get "people.username"
+            savedpoints = robot.brain.get "people.username.points"
+
+            score = ["#{savednames} has #{savedpoints[savednames]}"]

             topscore = score.sort((a, b) ->
-                b.robot.brain.people.points[username] - a.robot.brain.people.points[username]
+                b.savedpoints[savednames] - a.savedpoints[savednames]
             ).slice(0, pointcount)

             msg.send "The top #{pointcount} users with the most points are: #{topscore}"


cursorNode not exists for out-of-screen cursor

--- lib/cursor-style-manager.coffee
+++ lib/cursor-style-manager.coffee
@@ -47,11 +47,14 @@
     cursorStyle = @getCursorStyle(cursor, true)
     # [NOTE] Using non-public API
     cursorNode = @editorElement.component.linesComponent.cursorsComponent.cursorNodesById[cursor.id]
-    cursorNode.style.setProperty('top', cursorStyle.top)
-    cursorNode.style.setProperty('left', cursorStyle.left)
-    new Disposable ->
-      cursorNode.style.removeProperty('top')
-      cursorNode.style.removeProperty('left')
+    if cursorNode
+      cursorNode.style.setProperty('top', cursorStyle.top)
+      cursorNode.style.setProperty('left', cursorStyle.left)
+      new Disposable ->
+        cursorNode.style?.removeProperty('top')
+        cursorNode.style?.removeProperty('left')
+    else
+      new Disposable

   updateCursorStyleNew: ->
     # We must dispose previous style modification for non-visual-mode


senpai adana comleate!

senpai adana comleate!

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -4,46 +4,88 @@
 # Commands:
 #   hubot よろしくお願いします！ - hubot に挨拶しよう！
 #   hubot 誰知ってるんですか? - hubot が知ってる人を教えてくれるよ！
-#   hubot {name} は後輩です。よろしくお願いします! - hubot が知ってる人を教えてくれるよ！
-#   hubot {アダ名} は {nickname} のアダ名です  - hubot いアダ名を教えるよ！
-#   hubot {アダ名} は {nickname} のアダ名じゃないです  - hubot にアダ名が間違いだったことをを教えるよ！
+#   hubot 後輩の {nickname} - hubot に面通ししよう！
+#   hubot {nickname} のアダ名何ですか?  - ものしりな hubot にアダ名を教えiてもらおう！
+#   hubot {アダ名} は {nickname} のアダ名  - hubot にアダ名を教えるよ！
+#   hubot {アダ名} は {nickname} のアダ名じゃない  - hubot にアダ名が間違いだったことをを教えるよ！

-aisatsu = [
+aisatsu = [# {{{
   'よろしくな'
   'まいど'
   '元気か？'
-]
+]# }}}

-nandeTameguchi = [
+nandeTameguchi = [# {{{
   'つか、何でタメ口なん？'
   '口のききかたに気をつけや'
-]
+]# }}}

+# {{{ existsUser
 existsUser = (robot, msg, name) ->
   users = robot.brain.data.usersInfo ||= {}
   for u, v of users
     if u is name
       return true
   return false
+# }}}
+
+# {{{ SenpaiStorage
+setSenpaiStorage = (robot, msg, key, val) ->
+  storage = robot.brain.data.senpaiStorage ||= {}
+  storage[key] = val
+#  msg.send "storage #{key}, #{val}"
+  robot.brain.data.senpaiStorage = storage
+
+getSenpaiStorage = (robot, msg, key) ->
+  storage = robot.brain.data.senpaiStorage ||= {}
+  storage[key]
+# }}}
+
+# {{{ UserInfo
+setUserInfo = (robot, msg, name, key, val) ->
+  return null unless existsUser robot, msg, name
+  usersInfo = robot.brain.data.usersInfo ||= {}
+  usersInfo[name] ||= {}
+  usersInfo[name][key] = val
+#  msg.send "usersInfo #{name}: #{key}, #{val}"
+  robot.brain.data.usersInfo = usersInfo
+
+getUserInfo = (robot, msg, name, key) ->
+  return null unless existsUser robot, msg, name
+  usersInfo = robot.brain.data.usersInfo ||= {}
+  usersInfo[name] ||= {}
+  usersInfo[name][key]
+# }}}
+
+# {{{ Keigo
+REGEXP_KEIGO = '(です|デス|desu|ます|マス|masu|っす|ッス)か?[\?？!！]?'

 isKeigo = (robot, msg) ->
-  msg.message.match('(です|デス|desu|ます|マス|masu|っす|ッス)か?[\?？!！]?') isnt null
+  msg.message.match(REGEXP_KEIGO) isnt null
+
+trimKeigo = (str) ->
+  str.replace new RegExp('' + REGEXP_KEIGO + '$'), ''

 checkKeigo = (robot, msg) =>
   msg.send msg.random nandeTameguchi unless isKeigo robot, msg
+# }}}

 module.exports = (robot) ->
-  robot.brain.on 'loaded', =>
+  robot.brain.on 'loaded', =># {{{
+    robot.brain.data.senpaiStorage ||= {}
     robot.brain.data.usersInfo ||= {}
+# }}}

-  robot.respond /(よろしく)/, (msg) ->
+  robot.respond /よろしく/, (msg) -># {{{
     unless existsUser robot, msg, msg.message.user.name
       msg.send "#{msg.message.user.name} こいつ誰？ > all"
       checkKeigo robot, msg
       return
     msg.send msg.random aisatsu
+    checkKeigo robot, msg
+# }}}

-  robot.respond /(誰知ってるん(です)?か?[\?？]?)/, (msg) ->
+  robot.respond /誰知って/, (msg) -># {{{
     users = robot.brain.data.usersInfo ||= {}
     ret = []
     for u, v of users
@@ -51,33 +93,101 @@
     ret = ret.join('だろ, ')
     msg.send "えーっと #{ret} かな"
     checkKeigo robot, msg
+# }}}

-  robot.respond /([^ ]+) は ([^ ]+) のアダ名/, (msg) ->
-    return if msg.message.match('じゃない')
-    nick = msg.match[1]
-    name = msg.match[2]
-    unless existsUser robot, msg, name
-      msg.send "#{name} とか知らねーよ!"
+  robot.respond /後輩の ([^ ]+)/, (msg) -># {{{
+    name = msg.match[1]
+    name = trimKeigo name
+    if existsUser robot, msg, name
+      msg.send "知ってるしw"
+      checkKeigo robot, msg
+      return
+    robot.brain.data.usersInfo[name] = {}
+    msg.send "#{name} か。よろしくな"
+# }}}
+
+  robot.respond /([^ ]+) のアダ名何/, (msg) -># {{{
+    realname = msg.match[1]
+    unless existsUser robot, msg, realname
+      msg.send "#{realname} って誰？"
       checkKeigo robot, msg
       return

-    usersInfo = robot.brain.data.usersInfo["#{name}"] ||= []
-    if nick in usersInfo
-      msg.send "知ってるしw"
+    nicknames = getUserInfo robot, msg, realname, 'NICKNAMES'
+    nicknames ||= []
+    unless nicknames.length
+      msg.send "#{realname} は、特にアダ名無いよ。悲しいな"
       checkKeigo robot, msg
       return

-    usersInfo.push nick
-    msg.send "#{msg.match[1]} は #{msg.match[2]} のことか。#{robot.name} 覚えた!"
+    ret = []
+    for u, v of nicknames
+      ret.push v
+    ret = ret.join('とか, ')
+    msg.send "えーっと #{realname} は  #{ret} って呼ばれてるよ"
+    checkKeigo robot, msg

-  robot.respond /([^ ]+) は ([^ ]+) のアダ名(じゃない)/, (msg) ->
-    nick = msg.match[1]
-    name = msg.match[2]
-    unless existsUser robot, msg, name
-      msg.send "そもそも #{name} って誰?"
+# }}}
+
+  robot.respond /([^ ]+) は ([^ ]+) のアダ名/, (msg) -># {{{
+    return if msg.message.match('じゃない')
+    nickname = msg.match[1]
+    realname = msg.match[2]
+    unless existsUser robot, msg, realname
+      msg.send "#{realname} って誰？"
+      checkKeigo robot, msg
+      return
+
+    gNicknames = getSenpaiStorage robot, msg, 'NICKNAMES'
+    gNicknames ||= {}
+    nicknames = getUserInfo robot, msg, realname, 'NICKNAMES'
+    nicknames ||= []
+
+    gRealname = gNicknames[nickname]
+    if gRealname is realname
+#      nicknames.push nickname
+#      setUserInfo robot, msg, realname, 'NICKNAMES', nicknames
+      msg.send '知ってるしw'
+      checkKeigo robot, msg
+      return
+    else if gRealname? and gRealname isnt realname
+      msg.send "それ #{gRealname} のアダ名やろ？"
+      checkKeigo robot, msg
+      return
+
+    gNicknames[nickname] = realname
+    setSenpaiStorage robot, msg, 'NICKNAMES', gNicknames
+    nicknames.push nickname
+    setUserInfo robot, msg, realname, 'NICKNAMES', nicknames
+    msg.send 'ok. 了解'
+    checkKeigo robot, msg
+# }}}
+
+  robot.respond /([^ ]+) は ([^ ]+) のアダ名(じゃない)/, (msg) -># {{{
+    nickname = msg.match[1]
+    realname = msg.match[2]
+    unless existsUser robot, msg, realname
+      msg.send "そもそも #{realname} って誰?"
+      checkKeigo robot, msg
+      return
+
+    gNicknames = getSenpaiStorage robot, msg, 'NICKNAMES'
+    gNicknames ||= {}
+    nicknames = getUserInfo robot, msg, realname, 'NICKNAMES'
+    nicknames ||= []
+
+    unless nickname in nicknames
+      msg.send "#{realname} は #{nickname} なんて呼ばれてないで"
       checkKeigo robot, msg
       return

     msg.send "え、まじで。だれやねん、嘘教えたやつ。"
-    users = robot.brain.data.usersInfo
-    delete users[name]
+    gn = gNicknames[nickname]
+
+    gNicknames[nickname] = null
+    setSenpaiStorage robot, msg, 'NICKNAMES', gNicknames
+    newNicknames = (item for item in nicknames when item isnt nickname)
+    msg.send "nick #{newNicknames}"
+    setUserInfo robot, msg, realname, 'NICKNAMES', newNicknames
+    checkKeigo robot, msg
+# }}}


add props.subject for forum in toolbar

add props.subject for forum in toolbar

--- app/assets/javascripts/components/subject-set-toolbar.cjsx
+++ app/assets/javascripts/components/subject-set-toolbar.cjsx
@@ -67,7 +67,7 @@

-            <ForumSubjectWidget subject_set={@props.subject_set} project={@props.project} />
+            <ForumSubjectWidget subject_set={@props.subject_set} project={@props.project} subject={@props.subject}/>


fix hide show logic it was backwards

fix hide show logic it was backwards

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -1,12 +1,12 @@
 $ ->
-
+
   #scan page and find all objects with data rules
   findDataRules = ->
     $("[data-rule]").each ->
       rule = $.parseJSON($(this).attr("data-rule")).rule
       $(rule.conditions).each ->
         bindConditions(rule.hidden, rule.question_id, this.question_id, this.operator, this.answer)
-
+
   #bind conditions to question
   bindConditions = (hidden, ancestor_id, question_id, operator, answer) ->
     $("[data-question-id=" + question_id + "]").on "change", ->
@@ -36,14 +36,14 @@
         else
           hideShowQuestions(hidden, logic_result, ancestor_id)
         i++
-
+
   #hide or show questions
   hideShowQuestions = (hidden, logic_result, ancestor_id) ->
     selection = $("[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]").closest(".form-entry-item-container")
-    if hidden
+    if hidden
+      if logic_result then selection.addClass("conditional-logic-hidden") else selection.removeClass("conditional-logic-hidden")
+    else
       if logic_result then selection.removeClass("conditional-logic-hidden") else selection.addClass("conditional-logic-hidden")
-    else
-      if logic_result then selection.addClass("conditional-logic-hidden") else selection.removeClass("conditional-logic-hidden")
-
+
   #call findDataRules on document ready
   findDataRules()


Remove old scrollbar measuring code.

Remove old scrollbar measuring code.

--- app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
+++ app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
@@ -1,22 +1,3 @@
-scrollbarWidth = 0
-FactlinkJailRoot.loaded_promise.then ->
-  # Create the measurement nod; see http://davidwalsh.name/detect-scrollbar-width
-  scrollDiv = document.createElement("div");
-  $(scrollDiv).css(
-    width: "100px"
-    height: "100px"
-    overflow: "scroll"
-    position: "absolute"
-    top: "-9999px"
-  )
-  document.body.appendChild(scrollDiv)
-
-  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
-
-  document.body.removeChild(scrollDiv)
-  console.log 'scrollbar width: ', scrollbarWidth
-
-
 # To check for scrollbars on the window,  use the slightly unusual window.innerHeight
 # rather than  document.documentElement.clientHeight so it works in css compat mode.
 window_has_scrollbar = -> window.innerHeight < document.documentElement.scrollHeight


Handle unit filter urls.

Handle unit filter urls.

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -24,6 +24,7 @@
 ALLOWED_URLS = [
     /^\/$/
     /^\/unit\/\d+\/?$/,
+    /^\/unit\/\?[a-z0-9,=&]+\/?$/,
     /^\/service\/\d+\/?$/,
     /^\/search\/$/,
     /^\/address\/[^\/]+\/[^\/]+$/
@@ -78,10 +79,13 @@
     res.render 'embed.jade', vars

 handleUnit = (req, res, next) ->
+    if req.query.services?
+        requestHandler req, res, next
+        return
     pattern = /^\/(\d+)\/?$/
     r = req.path.match pattern
     if not r or r.length < 2
-        res.redirect config.urlPrefix
+        res.redirect config.url_prefix
         return

     unitId = r[1]


checking status code

checking status code

--- scripts/trello.coffee
+++ scripts/trello.coffee
@@ -67,6 +67,10 @@
     unless process.env.TRELLO_TOKEN?
       msg.send "Please specify the Trello token in TRELLO_TOKEN"
       return
+    msg.http(url)
+      .get() (err, res, body) ->
+        if res.statusCode isnt '200'
+          return
     jsdom = require 'jsdom'
     jsdom.env(
       html: msg.match[0]


check if editor

check if editor

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -83,14 +83,21 @@

   registerEvents: ->
     @disposables.push atom.workspace.onDidOpen (e) =>
-      grammar = e.item.getGrammar().name
-      if grammar in @grammars
+      if isGrammarInGrammars(e.item)
         @startServer()
     @disposables.push atom.workspace.onDidChangeActivePaneItem =>
       @setCurrentProvider()

+  isGrammarInGrammars: (editor) ->
+    grammar = editor.getGrammar().name
+    if grammar in @grammars
+      return true
+    else
+      return false
+
   setCurrentProvider: ->
     editor = atom.workspace.getActiveEditor()
+    return unless editor
     for provider, idx in @providers
       if provider.editor.id is editor.id
         @currentProviderIdx = idx


Added config option for jQuery version.

Added config option for jQuery version.

Default value is '1'

--- lib/bookmarklet.coffee
+++ lib/bookmarklet.coffee
@@ -8,6 +8,7 @@

   configDefaults:
     includeJquery: false
+    jqueryVersion: '1'

   activate: (state) ->
     @bookmarkletView = new BookmarkletView(state.bookmarkletViewState)
@@ -15,12 +16,15 @@
     @linkSuffix = '\">Click Me</a>'
     @header = 'javascript:(function(){'
     @footer = '})();'
-    @jQueryURL = '//ajax.googleapis.com/ajax/libs/jquery/1/jquery.js'
+    @jqueryFile = '/jquery.min.js'
+    @jqueryURL = '//ajax.googleapis.com/ajax/libs/jquery/'
     atom.workspaceView.command "bookmarklet:create-javaScript", => @createJS()
     atom.workspaceView.command "bookmarklet:create-link", => @createLink()

     atom.config.observe 'bookmarklet.includeJquery', callNow:true, (value) =>
       @includeJquery = value
+    atom.config.observe 'bookmarklet.jqueryVersion', callNow:true, (value) =>
+      @jqueryVersion = value

   deactivate: ->
     @bookmarkletView.destroy()
@@ -40,11 +44,14 @@
     if grammar.name is 'JavaScript'
       content = editor.getText()

+
       if @includeJquery
+        jqueryToUse = @jqueryURL + @jqueryVersion + @jqueryFile
+
         code = 'var __hasjq = function () {' + content + '};' +
           'if (window.jQuery) __hasjq(); else {' +
           'var s = document.createElement("script");' +
-          's.src = "' + @jQueryURL + '";' +
+          's.src = "' + jqueryToUse + '";' +
           's.onload = __hasjq;' +
           'document.body.appendChild(s);}';
       else


fix typo

fix typo

--- snippets/fa-css.cson
+++ snippets/fa-css.cson
@@ -27,8 +27,8 @@
 		prefix: "fa-ambulance"
 		body: "content: '\\\\f0f9';"
 	"content: '\\f2a3';":
-    prefix: "american-sign-language-interpreting"
-    body: "content: '\\\\f2a3';"
+	    prefix: "fa-american-sign-language-interpreting"
+	    body: "content: '\\\\f2a3';"
 	"content: '\\f13d';":
 		prefix: "fa-anchor"
 		body: "content: '\\\\f13d';"
@@ -120,11 +120,11 @@
 		prefix: "fa-arrows-v"
 		body: "content: '\\\\f07d';"
 	"content: '\\f2a3';":
-    prefix: "asl-interpreting"
-    body: "content: '\\\\f2a3';"
+	    prefix: "fa-asl-interpreting"
+	    body: "content: '\\\\f2a3';"
 	"content: '\\f2a2';":
-    prefix: "assistive-listening-systems"
-    body: "content: '\\\\f2a2';"
+	    prefix: "fa-assistive-listening-systems"
+	    body: "content: '\\\\f2a2';"
 	"content: '\\f069';":
 		prefix: "fa-asterisk"
 		body: "content: '\\\\f069';"
@@ -132,8 +132,8 @@
 		prefix: "fa-at"
 		body: "content: '\\\\f1fa';"
 	"content: '\\f29e';":
-    prefix: "audio-description"
-    body: "content: '\\\\f29e';"
+	    prefix: "fa-audio-description"
+	    body: "content: '\\\\f29e';"
 	"content: '\\f1b9';":
 		prefix: "fa-automobile"
 		body: "content: '\\\\f1b9';"


Remove unused requires

Remove unused requires

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -1,7 +1,5 @@
-fs = require 'fs'
 fsUtils = require 'fs-utils'
 $ = require 'jquery'
-_ = require 'underscore'
 less = require 'less'
 WindowEventHandler = require 'window-event-handler'
 require 'jquery-extensions'


tiebreak by descending score

tiebreak by descending score

--- models/person.coffee
+++ models/person.coffee
@@ -199,6 +199,7 @@
   sort = []
   # sort by minimum vote count for your type
   sort.push ['voteCounts.' + @role, 1] if @judge or @contestant
+  sort.push ['scores.overall', -1]
   sort.push ['updatedAt', -1]

   Team = mongoose.model 'Team'


Change .editor to .miniEditor in command panel specs

--- spec/extensions/command-panel-spec.coffee
+++ spec/extensions/command-panel-spec.coffee
@@ -15,25 +15,25 @@
       rootView.attachToDom()
       expect(rootView.find('.command-panel')).not.toExist()
       expect(rootView.activeEditor().isFocused).toBeTruthy()
-      expect(commandPanel.editor.isFocused).toBeFalsy()
+      expect(commandPanel.miniEditor.isFocused).toBeFalsy()

       rootView.trigger 'command-panel:toggle'
       expect(rootView.find('.command-panel').view()).toBe commandPanel
-      expect(commandPanel.editor.isFocused).toBeTruthy()
+      expect(commandPanel.miniEditor.isFocused).toBeTruthy()
       # this is currently assigned dynamically since our css scheme lacks variables
-      expect(commandPanel.prompt.css('font')).toBe commandPanel.editor.css('font')
-      commandPanel.editor.insertText 's/war/peace/g'
+      expect(commandPanel.prompt.css('font')).toBe commandPanel.miniEditor.css('font')
+      commandPanel.miniEditor.insertText 's/war/peace/g'

       rootView.trigger 'command-panel:toggle'
       expect(rootView.find('.command-panel')).not.toExist()
       expect(rootView.activeEditor().isFocused).toBeTruthy()
-      expect(commandPanel.editor.isFocused).toBeFalsy()
+      expect(commandPanel.miniEditor.isFocused).toBeFalsy()

       rootView.trigger 'command-panel:toggle'
       expect(rootView.find('.command-panel').view()).toBe commandPanel
-      expect(commandPanel.editor.isFocused).toBeTruthy()
-      expect(commandPanel.editor.buffer.getText()).toBe ''
-      expect(commandPanel.editor.getCursorScreenPosition()).toEqual [0, 0]
+      expect(commandPanel.miniEditor.isFocused).toBeTruthy()
+      expect(commandPanel.miniEditor.buffer.getText()).toBe ''
+      expect(commandPanel.miniEditor.getCursorScreenPosition()).toEqual [0, 0]

   describe "when command-panel:repeat-relative-address is triggered on the root view", ->
     it "calls .repeatRelativeAddress on the command interpreter with the active editor", ->
@@ -49,36 +49,37 @@
       commandInterpreter = commandPanel.commandInterpreter
       expect(commandInterpreter.lastRelativeAddress).toBeUndefined()
       rootView.trigger 'command-panel:set-selection-as-regex-address'
-      expect(commandInterpreter.lastRelativeAddress.regex.toString()).toEqual "/\\(items\\)/"
+      expect(commandInterpreter.lastRelativeAddress.subcommands.length).toBe 1
+      expect(commandInterpreter.lastRelativeAddress.subcommands[0].regex.toString()).toEqual "/\\(items\\)/"

   describe "when command-panel:find-in-file is triggered on an editor", ->
     it "pre-populates command panel's editor with /", ->
       rootView.activeEditor().trigger "command-panel:find-in-file"
       expect(commandPanel.parent).not.toBeEmpty()
-      expect(commandPanel.editor.getText()).toBe "/"
+      expect(commandPanel.miniEditor.getText()).toBe "/"

   describe "when esc is pressed in the command panel", ->
     it "closes the command panel", ->
       rootView.trigger 'command-panel:toggle'
       expect(rootView.find('.command-panel').view()).toBe commandPanel
-      commandPanel.editor.trigger keydownEvent('escape')
+      commandPanel.miniEditor.trigger keydownEvent('escape')
       expect(rootView.find('.command-panel')).not.toExist()

   describe "when return is pressed on the panel's editor", ->
     it "calls execute", ->
       spyOn(commandPanel, 'execute')
       rootView.trigger 'command-panel:toggle'
-      commandPanel.editor.insertText 's/hate/love/g'
-      commandPanel.editor.trigger keydownEvent('enter')
+      commandPanel.miniEditor.insertText 's/hate/love/g'
+      commandPanel.miniEditor.trigger keydownEvent('enter')

       expect(commandPanel.execute).toHaveBeenCalled()

     describe "if the command is malformed", ->
       it "adds and removes an error class to the command panel and does not close it", ->
         rootView.trigger 'command-panel:toggle'
-        commandPanel.editor.insertText 'garbage-command!!'
+        commandPanel.miniEditor.insertText 'garbage-command!!'

-        commandPanel.editor.trigger keydownEvent('enter')
+        commandPanel.miniEditor.trigger keydownEvent('enter')
         expect(commandPanel.parent()).toExist()
         expect(commandPanel).toHaveClass 'error'

@@ -93,23 +94,23 @@

       rootView.trigger 'command-panel:toggle'

-      commandPanel.editor.trigger 'move-up'
-      expect(commandPanel.editor.getText()).toBe 's/twinkies/wheatgrass/g'
-      commandPanel.editor.trigger 'move-up'
-      expect(commandPanel.editor.getText()).toBe 's/war/peace/g'
-      commandPanel.editor.trigger 'move-up'
-      expect(commandPanel.editor.getText()).toBe 's/war/peace/g'
-      commandPanel.editor.trigger 'move-down'
-      expect(commandPanel.editor.getText()).toBe 's/twinkies/wheatgrass/g'
-      commandPanel.editor.trigger 'move-down'
-      expect(commandPanel.editor.getText()).toBe ''
+      commandPanel.miniEditor.trigger 'move-up'
+      expect(commandPanel.miniEditor.getText()).toBe 's/twinkies/wheatgrass/g'
+      commandPanel.miniEditor.trigger 'move-up'
+      expect(commandPanel.miniEditor.getText()).toBe 's/war/peace/g'
+      commandPanel.miniEditor.trigger 'move-up'
+      expect(commandPanel.miniEditor.getText()).toBe 's/war/peace/g'
+      commandPanel.miniEditor.trigger 'move-down'
+      expect(commandPanel.miniEditor.getText()).toBe 's/twinkies/wheatgrass/g'
+      commandPanel.miniEditor.trigger 'move-down'
+      expect(commandPanel.miniEditor.getText()).toBe ''

   describe ".execute()", ->
     it "executes the command and closes the command panel", ->
       rootView.activeEditor().setText("i hate love")
       rootView.activeEditor().getSelection().setBufferRange [[0,0], [0,Infinity]]
       rootView.trigger 'command-panel:toggle'
-      commandPanel.editor.insertText 's/hate/love/'
+      commandPanel.miniEditor.insertText 's/hate/love/'
       commandPanel.execute()
       expect(rootView.activeEditor().getText()).toBe "i love love"
       expect(rootView.find('.command-panel')).not.toExist()


Add method for optionally-fatal deprecation warnings

--- src/lib/Platform.coffee
+++ src/lib/Platform.coffee
@@ -7,3 +7,12 @@
   if typeof process isnt 'undefined' and process.execPath and process.execPath.match /node|iojs/
     return false
   true
+
+exports.warnDeprecated = (message) ->
+  if exports.isBrowser()
+    throw new Error message if window.NOFLO_FATAL_DEPRECATED
+    console.warn message
+    return
+  if process.env.NOFLO_FATAL_DEPRECATED
+    throw new Error message
+  console.warn message


Fix js scoping for coder contributions page

--- app/assets/javascripts/repositories.js.coffee
+++ app/assets/javascripts/repositories.js.coffee
@@ -2,7 +2,7 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-$('body.repositories.index').ready ->
+$('body.repositories.index, body.coders.show').ready ->
     table = $('#top-repos').DataTable
         order: [[2, 'desc']]
         columnDefs: [


Initialize credentials util. Refs #4.

Initialize credentials util. Refs #4.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -36,6 +36,7 @@

 process.on 'SIGUSR2', -> logger.reopenFileStreams()

+credentialsConfig = ProjectCredentialsConfig.create()
 options =
   config: credentials.enrichCredentials
     project_key: argv.projectKey


Cleanup

Cleanup

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -2,11 +2,11 @@
 Q = require 'q'
 _ = require 'underscore'
 path = require 'path'
+{ProjectCredentialsConfig} = require 'sphere-node-utils'
 package_json = require '../package.json'
 Logger = require './logger'
 StockImport = require './stockimport'
 SftpHelper = require './sftp'
-{ProjectCredentialsConfig} = require 'sphere-node-utils'

 argv = require('optimist')
   .usage('Usage: $0 --projectKey [key] --clientId [id] --clientSecret [secret] --file [file] --logDir [dir] --logLevel [level]')
@@ -36,9 +36,48 @@

 process.on 'SIGUSR2', -> logger.reopenFileStreams()

+importFn = (fileName) ->
+  throw new Error 'You must provide a file to be processed' unless fileName
+  d = Q.defer()
+  logger.info "About to process file #{fileName}"
+  mode = stockimport.getMode fileName
+  fs.read fileName
+  .then (content) ->
+    logger.info 'File read, running import'
+    stockimport.run(content, mode)
+    .then (result) ->
+      logger.info stockimport.sumResult(result)
+      d.resolve(fileName)
+    .fail (e) ->
+      logger.error e, "Oops, something went wrong when processing file #{fileName}"
+      d.reject 1
+  .fail (e) ->
+    logger.error e, "Cannot read file #{fileName}"
+    d.reject 2
+  d.promise
+
+processFn = (files, fn) ->
+  throw new Error 'Please provide a function to process the files' unless _.isFunction fn
+  d = Q.defer()
+  _process = (tick) ->
+    logger.info tick, 'Current tick'
+    if tick >= files.length
+      logger.info 'No more files, resolving...'
+      d.resolve()
+    else
+      file = files[tick]
+      fn(file)
+      .then ->
+        logger.info "Finishing processing file #{file}"
+        sftpHelper.finish(file)
+      .then ->
+        _process(tick + 1)
+      .fail (error) -> d.reject error
+      .done()
+  _process(0)
+  d.promise
+
 credentialsConfig = ProjectCredentialsConfig.create()
-.fail (err) ->
-  logger.error e, "Problems on getting client credentials from config files."
 .then (credentials) ->
   options =
     config: credentials.enrichCredentials
@@ -51,26 +90,6 @@
       logger: logger

   stockimport = new StockImport options
-
-  importFn = (fileName) ->
-    throw new Error 'You must provide a file to be processed' unless fileName
-    d = Q.defer()
-    logger.info "About to process file #{fileName}"
-    mode = stockimport.getMode fileName
-    fs.read fileName
-    .then (content) ->
-      logger.info 'File read, running import'
-      stockimport.run(content, mode)
-      .then (result) ->
-        logger.info stockimport.sumResult(result)
-        d.resolve(fileName)
-      .fail (e) ->
-        logger.error e, "Oops, something went wrong when processing file #{fileName}"
-        d.reject 1
-    .fail (e) ->
-      logger.error e, "Cannot read file #{fileName}"
-      d.reject 2
-    d.promise

   file = argv.file

@@ -91,27 +110,6 @@
       targetFolder: argv.sftpTarget
       logger: logger

-    processFn = (files, fn) ->
-      throw new Error 'Please provide a function to process the files' unless _.isFunction fn
-      d = Q.defer()
-      _process = (tick) ->
-        logger.info tick, 'Current tick'
-        if tick >= files.length
-          logger.info 'No more files, resolving...'
-          d.resolve()
-        else
-          file = files[tick]
-          fn(file)
-          .then ->
-            logger.info "Finishing processing file #{file}"
-            sftpHelper.finish(file)
-          .then ->
-            _process(tick + 1)
-          .fail (error) -> d.reject error
-          .done()
-      _process(0)
-      d.promise
-
     sftpHelper.download(TMP_PATH)
     .then (files) ->
       logger.info files, "Processing #{files.length} files..."
@@ -124,3 +122,8 @@
     .fail (error) ->
       logger.error error, 'Oops, something went wrong!'
       process.exit(1)
+    .done()
+.fail (err) ->
+  logger.error e, "Problems on getting client credentials from config files."
+  process.exit(1)
+.done()


Ensure component can work with logrotate.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -16,6 +16,9 @@
     { level: 'error', stream: process.stderr }
     { level: argv.logLevel, path: "#{argv.logDir}/sphere-stock-sync-#{argv.projectKey}.log" }
   ]
+
+process.on 'SIGUSR2', ->
+  logger.reopenFileStreams()

 options =
   baseConfig:


(feature) finish adding defaultState to command-line options.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -125,7 +125,7 @@
       .option '--publish', 'When given, all changes will be published immediately'
       .option '--updatesOnly', "Won't create any new products, only updates existing"
       .option '--dryRun', 'Will list all action that would be triggered, but will not POST them to SPHERE.IO'
-      .option '--defaultState', "When given, specifies the key of the state to assign imported products to, if they don't have one"
+      .option '--defaultState [stateKey]', "When given, specifies the key of the state to assign imported products to, if they don't have one"
       .option '-m, --matchBy [value]', 'Product attribute name which will be used to match products. Possible values: id, slug, sku, <custom_attribute_name>. Default: id. Localized attribute types are not supported for <custom_attribute_name> option', 'id'
       .option '-e, --encoding [encoding]', 'Encoding used when reading data from input file | default: utf8', 'utf8'
       .usage '--projectKey <project-key> --clientId <client-id> --clientSecret <client-secret> --csv <file>'
@@ -154,6 +154,7 @@
             httpConfig:
               host: program.sphereHost
               enableRetry: true
+            defaultState: opts.defaultState
           options.authConfig.host = program.sphereAuthHost
           options.continueOnProblems = opts.continueOnProblems or false

@@ -172,6 +173,7 @@
           importer.allowRemovalOfVariants = opts.allowRemovalOfVariants
           importer.publishProducts = opts.publish
           importer.updatesOnly = true if opts.updatesOnly
+          importer.defaultState = opts.defaultState
           importer.dryRun = true if opts.dryRun
           importer.matchBy = opts.matchBy


Fix indentation

Fix indentation

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -429,14 +429,15 @@
             else null

     lintUnary: (token) ->
-      if token[1] is 'new'
-        expectedIdentifier = @peek(1)
-        # The callStart is generated if your parameters are on the next line
-        # but is missing if there are no params and no parens.
-        expectedCallStart  = @peek(2)
-        if expectedIdentifier?[0] is 'IDENTIFIER' and
-          expectedCallStart?[0] isnt 'CALL_START'
-            @createLexError('empty_constructor_needs_parens')
+        if token[1] is 'new'
+            expectedIdentifier = @peek(1)
+            # The callStart is generated if your parameters are on the next line
+            # but is missing if there are no params and no parens.
+            expectedCallStart  = @peek(2)
+            if expectedIdentifier?[0] is 'IDENTIFIER' and
+            expectedCallStart?[0] isnt 'CALL_START'
+
+                @createLexError('empty_constructor_needs_parens')

     # Lint the given array token.
     lintArray : (token) ->


Update check new lineNumber object

Update check new lineNumber object

--- src/coffeelint.coffee
+++ src/coffeelint.coffee
@@ -402,7 +402,7 @@
     lintToken : (token) ->
         [type, value, lineNumber] = token

-        lineNumber = lineNumber["first_line"] if lineNumber["first_line"]?
+        lineNumber = lineNumber.first_line if lineNumber?.first_line?
         @tokensByLine[lineNumber] ?= []
         @tokensByLine[lineNumber].push(token)
         # CoffeeScript loses line numbers of interpolations and multi-line


basic drag to text working

basic drag to text working

--- src/controller.coffee
+++ src/controller.coffee
@@ -1188,7 +1188,20 @@
   hook 'mouseup', 1, (point, event, state) ->
     # We will consume this event iff we dropped it successfully
     # in the root tree.
-    if @draggingBlock? and @lastHighlight?
+    if not @draggingBlock?
+      return
+
+    if not @currentlyUsingBlocks
+      # TODO handle multiline
+      text = ''
+      cur = @draggingBlock.start
+      while cur.next
+        cur = cur.next
+        if cur.value
+          text += cur.value
+      @aceEditor.onTextInput text
+    else if  @lastHighlight?
+
       if @inTree @draggingBlock
         # Since we removed this from the tree,
         # we will need to log an undo operation


Allow colors in line marking

Allow colors in line marking

--- src/controller.coffee
+++ src/controller.coffee
@@ -2750,14 +2750,14 @@
   hook 'populate', 0, ->
     @markedLines = {}

-  Editor::markLine = (line) ->
+  Editor::markLine = (line, style) ->
     block = @tree.getBlockOnLine line

     if block?
       path = @markedLines[line] = @view.getViewNodeFor(block).path.clone()

       path.style.fillColor = null
-      path.style.strokeColor = '#FFF'
+      path.style.strokeColor = style.color
       path.style.lineWidth = 2
       path.noclip = true; path.bevel = false


refactor: remove old code

refactor: remove old code

--- src/core/field.coffee
+++ src/core/field.coffee
@@ -1,34 +1,8 @@
-#{Tessellation} = require "./hyperbolic_tessellation.coffee"
-{unity, newNode, showNode, node2array} = require "./vondyck_chain.coffee"
+{unity, newNode} = require "./vondyck_chain.coffee"
 {NodeHashMap} = require "./chain_map.coffee"
-#{RewriteRuleset, knuthBendix} = require "./knuth_bendix.coffee"

 #High-level utils for working with hyperbolic cellular fields

-
-
-exports.neighborsSum = neighborsSum = (cells, getNeighbors, plus=((x,y)->x+y), plusInitial=0)->
-  sums = new NodeHashMap
-  cells.forItems (cell, value)->
-    for neighbor in getNeighbors cell
-      sums.putAccumulate neighbor, value, plus, plusInitial
-    #don't forget the cell itself! It must also present, with zero (initial) neighbor sum
-    if sums.get(cell) is null
-      sums.put(cell, plusInitial)
-  return sums
-
-exports.evaluateTotalisticAutomaton = evaluateTotalisticAutomaton = (cells, getNeighborhood, nextStateFunc, plus, plusInitial)->
-  newCells = new NodeHashMap
-  sums = neighborsSum cells, getNeighborhood, plus, plusInitial
-  sums.forItems (cell, neighSum)->
-    cellState = cells.get(cell) ? 0
-    nextState = nextStateFunc cellState, neighSum
-    if nextState isnt 0
-      newCells.put cell, nextState
-  return newCells
-
-
-
 exports.extractClusterAt = extractClusterAt = (cells, tiling, chain) ->
   #use cycle instead of recursion in order to avoid possible stack overflow.
   #Clusters may be big.


Defer requires until the cache is populated

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -1,7 +1,8 @@
 Module = require 'module'
 path = require 'path'
-fs = require 'fs-plus'
-semver = require 'semver'
+
+fs = null     # Defer so the cache is used
+semver = null # Defer so the cache is used

 nativeModules = process.binding('natives')

@@ -15,6 +16,10 @@
   registered: false
   resourcePath: null

+requireDependencies = ->
+  fs ?= require 'fs-plus'
+  semver ?= require 'semver'
+
 loadDependencies = (modulePath, rootPath, rootMetadata, moduleCache) ->
   for childPath in fs.listSync(path.join(modulePath, 'node_modules'))
     continue if path.basename(childPath) is '.bin'
@@ -172,6 +177,8 @@
     foundPath

 exports.create = (modulePath) ->
+  requireDependencies()
+
   modulePath = fs.realpathSync(modulePath)
   metadataPath = path.join(modulePath, 'package.json')
   metadata = JSON.parse(fs.readFileSync(metadataPath))
@@ -201,6 +208,7 @@
   cache.registered = true
   cache.resourcePath = resourcePath
   registerBuiltins(devMode)
+  requireDependencies()

   return


Support for arrays

Support for arrays

--- lib/pg-hoff-results-view.coffee
+++ lib/pg-hoff-results-view.coffee
@@ -84,14 +84,23 @@

     createTd: (text, typeName) ->
         td = document.createElement('td')
-        td.textContent = text
         try
-            td.textContent = Type[typeName].format(text) if Type[typeName]?.format and atom.config.get('pg-hoff.formatColumns')
-            td.setAttribute 'title', text
+            td.textContent = @cellText(text, typeName)
         catch err
-            console.error 'Could not format as ' + Type[typeName].name, text
+            console.error 'Could not format as ' + typeName, text
+        td.setAttribute 'title', text
+        return td

-        return td
+    cellText: (data, typeName) ->
+        typeName = typeName.slice(0, -2) if typeName.match /\[\]$/
+        if data?.constructor == Array
+            ct = (e) => if e is null then 'NULL' else @cellText e, typeName
+            elements = (ct e for e in data)
+            '[' + elements.join(', ') + ']'
+        else if Type[typeName]?.format and atom.config.get('pg-hoff.formatColumns')
+            Type[typeName].format(data)
+        else
+            data

     update: (resultsets) ->
         @resultsets = resultsets


Refactor indent

Refactor indent

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -17,10 +17,11 @@
     basename = path.basename(filePath)
     command = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
     args = ['-a', filePath]
-    exit = (code) -> if (code == 0)
-      atom.notifications.addSuccess("#{command} -a #{basename}")
-    else
-      atom.notifications.addError("Failer #{command} -a #{basename}")
+    exit = (code) ->
+       if (code == 0)
+         atom.notifications.addSuccess("#{command} -a #{basename}")
+       else
+         atom.notifications.addError("Failer #{command} -a #{basename}")
     process = new BufferedProcess({command, args, exit})
     process


Bump version for dev.

Bump version for dev.

--- lib/shared/lib/spendflow.coffee
+++ lib/shared/lib/spendflow.coffee
@@ -1,4 +1,4 @@
-@SPENDFLOW_VERSION = '1.7.15'
+@SPENDFLOW_VERSION = '1.7.15.dev'

 @spendflowPrecision = 2


fix webdriver tests

fix webdriver tests

--- test/wd/e2e.coffee
+++ test/wd/e2e.coffee
@@ -143,7 +143,7 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      ops: [{ insert: text, attributes: { size: '32px' } }]
+      ops: [{ attributes: { size: '32px' }, insert: text }]
     }
     expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
   )
@@ -164,8 +164,6 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      startLength: 1530
-      endLength: 1530
       ops: [
         { retain: 10 }
         { retain: 20, attributes: { bold: true } }
@@ -189,8 +187,6 @@
     )
     browser.switchTo().defaultContent()
     expectedDelta = {
-      startLength: 1530
-      endLength: 1530
       ops: [
         { retain: 9 }
         { retain: 1, attributes: { align: 'center' } }


Fix protractor test

Fix protractor test

Enter key new calls getSelection which calls checkUpdate so we cannot
get entire test change as one delta

--- test/wd/e2e.coffee
+++ test/wd/e2e.coffee
@@ -45,23 +45,39 @@
   )

   it('enter', ->
+    editor.sendKeys(protractor.Key.RETURN)
+    expectedDelta = { ops: [{ retain: 10 }, { insert: '\n' }] }
+    browser.switchTo().defaultContent()
+    expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
+    browser.switchTo().frame('quill-1')
+
+    editor.sendKeys(protractor.Key.RETURN)
+    expectedDelta = { ops: [{ retain: 11 }, { insert: '\n' }] }
+    browser.switchTo().defaultContent()
+    expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
+    browser.switchTo().frame('quill-1')
+
     text = 'Chapter 1. Loomings.'
-    editor.sendKeys(protractor.Key.RETURN, protractor.Key.RETURN, text, protractor.Key.RETURN)
-    updateEditor()
+    editor.sendKeys(text)
+    updateEditor(false)
+    # The previous newline inserts was assumed to be appended since the insertion character matches
+    # the last character of the document. There is no such ambiguity here so the number of retains
+    # is the same as the last delta
+    expectedDelta = { ops: [{ retain: 11 }, { insert: "#{text}" }] }
+    expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
+    browser.switchTo().frame('quill-1')
+
+    editor.sendKeys(protractor.Key.RETURN)
+    expectedDelta = { ops: [{ retain: 32 }, { insert: '\n' }] }
+    browser.switchTo().defaultContent()
+    expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
+    browser.switchTo().frame('quill-1')
     expect(editor.getInnerHtml().then(cleanLines)).toEqual([
       '
The Whale
'
       '

'
       "
#{text}
"
       '

'
     ].join(''))
-    browser.switchTo().defaultContent()
-    expectedDelta = {
-      ops: [
-        { retain: 10 }
-        { insert: "\n#{text}\n\n" }
-      ]
-    }
-    expect(deltaOutput.getText()).toEqual(JSON.stringify(expectedDelta))
   )

   it('tab', ->


Pass PreviouslyFocusedElement to callback function

--- lib/tool-bar-button-view.coffee
+++ lib/tool-bar-button-view.coffee
@@ -24,7 +24,7 @@
         if typeof options.callback is 'string'
           atom.commands.dispatch @getPreviouslyFocusedElement(), options.callback
         else
-          options.callback(options.data)
+          options.callback(options.data, @getPreviouslyFocusedElement())

     @on 'mouseover', =>
       @storeFocusedElement()


dynamic templates aved to locals as an array

--- lib/utils/compile_helper.coffee
+++ lib/utils/compile_helper.coffee
@@ -22,13 +22,13 @@
     front_matter_string = @file_contents.match(yaml_matcher)

     if front_matter_string
-      @template_name = "#{path.basename(@file, path.extname(@file))}"
-      options.locals.site = {}
-      # this should add to an array
-      options.locals.site[@template_name] = {}
+      # set up variables
+      options.locals.site = []
+      @dynamic_locals = {}
+
       front_matter = js_yaml.safeLoad(front_matter_string[1], { filename: @file })
-
-      options.locals.site[@template_name][k] = v for k,v of front_matter
+      @dynamic_locals[k] = v for k,v of front_matter
+
       @layout = front_matter.layout if _.pluck(front_matter, 'layout')
       @file_contents = @file_contents.replace yaml_matcher, ''

@@ -49,8 +49,10 @@
     for key, value of extra
       options.locals[key] = value

-    if @template_name and extra? and extra.yield?
-      options.locals.site[@template_name].content = extra.yield
+    # add compiled content to locals for dynamic templates
+    if @dynamic_locals and extra? and extra.yield?
+      @dynamic_locals.content = extra.yield.trim()
+      options.locals.site.push(@dynamic_locals)

     return options.locals

@@ -58,6 +60,7 @@
     write_content = @compress(write_content) if options.compress
     fs.writeFileSync @export_path, write_content
     global.options.debug.log "compiled #{path.basename(@file)}"
+    console.log options.locals.site

   compress: (write_content) ->
     require('../utils/compressor')(write_content, @target_extension)


add user to server item model

add user to server item model

--- SingularityUI/app/models/ServerItem.coffee
+++ SingularityUI/app/models/ServerItem.coffee
@@ -30,6 +30,7 @@
                 item.uptime = moment() - item.firstSeenAt
         if item.currentState?
             item.state = item.currentState.state
+            item.user = item.currentState.user
         item

     remove: =>


option class support

option class support

--- jquery-select7.coffee
+++ jquery-select7.coffee
@@ -1,9 +1,9 @@
 ###
 @name jquery-select7
-@version 0.2.11
+@version 0.2.12
 @author Se7enSky studio <info@se7ensky.com>
 ###
-###! jquery-select7 0.2.11 http://github.com/Se7enSky/jquery-select7 ###
+###! jquery-select7 0.2.12 http://github.com/Se7enSky/jquery-select7 ###

 plugin = ($) ->

@@ -19,6 +19,7 @@
 			data.title = trim $(option).text()
 			data.value = $(option).attr("value") or trim $(option).text()
 			data.disabled = yes if $(option).attr "disabled"
+			data.class = $(option).attr "class"
 			data
 		)
 	readSelectedIndexFromSelect = (el) ->
@@ -101,7 +102,7 @@
 			for option, i in @options
 				continue if option.isPlaceholder
 				continue if i is @selectedIndex
-				$option = $ """

"""
+				$option = $ """

"""
 				$option.text option.title
 				$option.addClass "select7__option_disabled" if option.disabled
 				$option.prepend """""" if option.icon


Really fix base

Really fix base

--- serve-sketches.coffee
+++ serve-sketches.coffee
@@ -120,7 +120,7 @@
   getSketches(SKETCH_DIR).then (sketches) ->
     list = sketches.filter(filter)
     res.send header(req.app.locals.templateVars) + '
' + list.map((filename) ->
-      url = req.app.base + '/image/' + encodeURIComponent(filename)
+      url = req.app.locals.base + '/image/' + encodeURIComponent(filename)
       return '
View - ' + linkTags(filename) + '
'
     ).join('') + '
' + footer(req.app.locals.templateVars)
 exports.showTextList = showTextList


DRY test aesthetics

DRY test aesthetics

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -4,213 +4,172 @@
 else
   parser = require 'vfl-compiler'

-describe 'VFL compiler', ->
+parse = (source, expect) ->
+  result = null
+  describe source, ->
+    it 'should do something', ->
+      result = parser.parse source
+      chai.expect(result).to.be.an 'array'
+    it 'should match expected', ->
+      chai.expect(result).to.eql expect
+
+describe 'Compiler', ->
+
   it 'should provide a parse method', ->
     chai.expect(parser.parse).to.be.a 'function'
-
-
+
   # Basics
   # --------------------------------------------------

   describe '/* Basics */', ->
-
-    describe '@horizontal [#b1][#b2]; // simple connection ', ->
-      source = """
-              @horizontal [#b1][#b2];
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @horizontal [#b1][#b2]; // simple connection
+          """
+        ,
           [
-            'ccss'
-            "#b1[right] == #b2[left]"
+            [
+              'ccss'
+              "#b1[right] == #b2[left]"
+            ]
           ]
-        ]
-
-      it 'should be able to produce a result', ->
-        result = parser.parse source
-        chai.expect(result).to.be.an 'array'
-
-      it '/ the result should match the expectation', ->
-        chai.expect(result).to.eql expect
-
-    describe '@vertical [#b1]-[#b2]-[#b3]-[#b4]-[#b5]; // connection chain with implicit hgaps ', ->
-      source = """
-              @vertical [#b1]-[#b2]-[#b3]-[#b4]-[#b5];
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @vertical [#b1]-[#b2]-[#b3]-[#b4]-[#b5]; // connection chain with implicit hgaps
+          """
+        ,
           [
-            'ccss'
-            "#b1[bottom] + [vgap] == #b2[top]"
-            "#b2[bottom] + [vgap] == #b3[top]"
-            "#b3[bottom] + [vgap] == #b4[top]"
-            "#b4[bottom] + [vgap] == #b5[top]"
+            [
+              'ccss'
+              "#b1[bottom] + [vgap] == #b2[top]"
+              "#b2[bottom] + [vgap] == #b3[top]"
+              "#b3[bottom] + [vgap] == #b4[top]"
+              "#b4[bottom] + [vgap] == #b5[top]"
+            ]
           ]
-        ]
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-
-    describe '@horizontal [#b1]-100-[#b2]-8-[#b3]; // explicit gaps ', ->
-      source = """
-              @horizontal [#b1]-100-[#b2]-8-[#b3];
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @horizontal [#b1]-100-[#b2]-8-[#b3];
+          """
+        ,
           [
-            'ccss'
-            "#b1[right] + 100 == #b2[left]"
-            "#b2[right] + 8 == #b3[left]"
+            [
+              'ccss'
+              "#b1[right] + 100 == #b2[left]"
+              "#b2[right] + 8 == #b3[left]"
+            ]
           ]
-        ]
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-
-
+
   # Containment
   # --------------------------------------------------

   describe '/* Containment */', ->

-    describe '@vertical |[#sub]| in(#parent); // flush with super view', ->
-      source = """
-              @vertical |[#sub]| in(#parent);
-      """
-      result = null
-      expect =
-        [
+    parse """
+            @vertical |[#sub]| in(#parent); // flush with super view
+          """
+        ,
           [
-            'ccss'
-            '#parent[top] == #sub[top]'
-            '#sub[bottom] == #parent[bottom]'
+            [
+              'ccss'
+              '#parent[top] == #sub[top]'
+              '#sub[bottom] == #parent[bottom]'
+            ]
           ]
-        ]
-
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-
-    describe '@horizontal |-[#sub1]-[#sub2]-| in(#parent); // super view with standard gaps', ->
-      source = """
-              @horizontal |-[#sub1]-[#sub2]-| in(#parent);
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @horizontal |-[#sub1]-[#sub2]-| in(#parent); // super view with standard gaps
+          """
+        ,
           [
-            'ccss'
-            '#parent[left] + [hgap] == #sub1[left]'
-            '#sub1[right] + [hgap] == #sub2[left]'
-            '#sub2[right] + [hgap] == #parent[right]'
+            [
+              'ccss'
+              '#parent[left] + [hgap] == #sub1[left]'
+              '#sub1[right] + [hgap] == #sub2[left]'
+              '#sub2[right] + [hgap] == #parent[right]'
+            ]
           ]
-        ]
-
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-
-    describe '@horizontal |-1-[#sub]-2-| in(#parent); // super view with explicit gaps', ->
-      source = """
-              @horizontal |-1-[#sub]-2-| in(#parent);
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @horizontal |-1-[#sub]-2-| in(#parent); // super view with explicit gaps
+          """
+        ,
           [
-            'ccss'
-            '#parent[left] + 1 == #sub[left]'
-            '#sub[right] + 2 == #parent[right]'
+            [
+              'ccss'
+              '#parent[left] + 1 == #sub[left]'
+              '#sub[right] + 2 == #parent[right]'
+            ]
           ]
-        ]
-
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-

   # Cushions
   # --------------------------------------------------

   describe '/* Cushions */', ->

-    describe '@horizontal [#b1]~[#b2]; // simple cushion ', ->
-      source = """
-              @horizontal [#b1]~[#b2];
-      """
-      result = null
-      expect =
-        [
+    parse """
+            @horizontal [#b1]~[#b2]; // simple cushion
+          """
+        ,
           [
-            'ccss'
-            "#b1[right] <= #b2[left]"
+            [
+              'ccss'
+              "#b1[right] <= #b2[left]"
+            ]
           ]
-        ]
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect

-    describe '@horizontal [#b1]~-~[#b2]~100~[#b3]; // cushions w/ gaps ', ->
-      source = """
-              @horizontal [#b1]~-~[#b2]~100~[#b3];
-      """
-      result = null
-      expect =
-        [
+    parse """
+            @horizontal [#b1]~-~[#b2]~100~[#b3]; // cushions w/ gaps
+          """
+        ,
           [
-            'ccss'
-            "#b1[right] + [hgap] <= #b2[left]"
-            "#b2[right] + 100 <= #b3[left]"
+            [
+              'ccss'
+              "#b1[right] + [hgap] <= #b2[left]"
+              "#b2[right] + 100 <= #b3[left]"
+            ]
           ]
-        ]
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
-
-    describe '@horizontal |~[#sub]~2~| in(#parent); // super view with cushions', ->
-      source = """
-              @horizontal |~[#sub]~2~| in(#parent);
-      """
-      result = null
-      expect =
-        [
+
+    parse """
+            @horizontal |~[#sub]~2~| in(#parent); // super view with cushions
+          """
+        ,
           [
-            'ccss'
-            '#parent[left] <= #sub[left]'
-            '#sub[right] + 2 <= #parent[right]'
+            [
+              'ccss'
+              '#parent[left] <= #sub[left]'
+              '#sub[right] + 2 <= #parent[right]'
+            ]
           ]
-        ]
-
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect

-  # Constraint Predicates
+  # Predicates
   # --------------------------------------------------

   describe '/* Predicates */', ->

-    describe '@horizontal [#sub(==100)]; // single predicate', ->
-      source = """
-              @horizontal [#sub(==100)];
-      """
-      result = null
-      expect =
-        [
+    parse """
+            @horizontal [#sub(==100)]; // single predicate
+          """
+        ,
           [
-            'ccss'
-            '#parent[left] <= #sub[left]'
-            '#sub[right] + 2 <= #parent[right]'
+            [
+              'ccss'
+              '#sub[width] == 100'
+            ]
           ]
-        ]
-
-      it '/ the result should match the expectation', ->
-        result = parser.parse source
-        chai.expect(result).to.eql expect
+
+    parse """
+            @horizontal [#sub(==100)]; // single predicate
+          """
+        ,
+          [
+            [
+              'ccss'
+              '#sub[width] == 100'
+            ]
+          ]
+


cleanup indents

cleanup indents

--- js/outer/about.coffee
+++ js/outer/about.coffee
@@ -9,7 +9,7 @@
       photo: "paul"
       visible: true
       bio: ["You might not know it by looking at him, but Paul is a wicked card shark. When he's not making his poker buddies cry, he seeks out mogul-laced ski trails to hurl himself down, and within the first five minutes of meeting him he'll probably tell you the story about how he mostly landed a backflip on his skis. Lest his ego get too big, though, we can always remind him of the time he failed at a YCombinator startup, and he'll go bury his emotions in the sweet sweet taste of artisan chocolate--or any chocolate, really. ",
-            "Paul's been interviewed by the Wall Street Journal, and one of his proudest achievements was giving a Google Tech Talk on compilers and programming languages. He wrote phc, an open source PHP compiler, then did his PhD on compilers and static analysis in Dublin. Before working on the Firefox Javascript engine here in the Bay Area, he wrote stefon, an asset pipeline for Clojure. It pains Paul to see so much time wasted due to inadequate developer tools, so he approaches Continuous Integration from the perspective of developer productivity. He loves to focus on building the Circle product, but, as he'll tell you while he's walloping you at table tennis, he also has to take care of the business side of things, too. Then, while you're distracted by his work talk, he'll hit you with the smash that nearly got him booted from the San Francisco Table Tennis league, and you'll be done for."]
+        "Paul's been interviewed by the Wall Street Journal, and one of his proudest achievements was giving a Google Tech Talk on compilers and programming languages. He wrote phc, an open source PHP compiler, then did his PhD on compilers and static analysis in Dublin. Before working on the Firefox Javascript engine here in the Bay Area, he wrote stefon, an asset pipeline for Clojure. It pains Paul to see so much time wasted due to inadequate developer tools, so he approaches Continuous Integration from the perspective of developer productivity. He loves to focus on building the Circle product, but, as he'll tell you while he's walloping you at table tennis, he also has to take care of the business side of things, too. Then, while you're distracted by his work talk, he'll hit you with the smash that nearly got him booted from the San Francisco Table Tennis league, and you'll be done for."]
     ,
       name: "Allen Rohner"
       photo: "allen"
@@ -19,7 +19,7 @@
       twitter: "arohner"
       email: "allen@circleci.com"
       bio: ["Allen loves Ultimate Frisbee: he's played in the snow, on the beach, on Thanksgiving Day, New Year's Eve, and New Year's Day, and he regularly plays in 110 degree August heat. It helps take his mind off being rejected from YCombinator 4 different times, though he did get a book from Alexis Ohanian as a consolation prize.  His future plans include the tacocopter, the burrito bomber, and the mosquito laser. When Allen's not coding, he spends his time making his own pizza dough and brewing his own beer. He'd make his own whiskey, too, if he wanted to wait 10 years for a drink.",
-            "Back in the day, Allen started work on his own self-hosting, native code lisp, and then the clouds parted and he discovered Clojure. Since then, he's become a Clojure contributor, and has commits in clojure.core, contrib, lein, ring, compojure, noir, and about a dozen more libraries. He finished SICP, and wrote Scriptjure, a Clojure to Javascript compiler, 2 years before ClojureScript. Other devs used his work as a basis for Stevedore, which he's happy to say is used in production every day at Circle. In addition to co-founding Circle as a means to bring Continuous Deployment to the masses, he looks forward to building tools that help debug test failures by anticipating common problems. He currently deploys six times a day, and more on weekends."]
+        "Back in the day, Allen started work on his own self-hosting, native code lisp, and then the clouds parted and he discovered Clojure. Since then, he's become a Clojure contributor, and has commits in clojure.core, contrib, lein, ring, compojure, noir, and about a dozen more libraries. He finished SICP, and wrote Scriptjure, a Clojure to Javascript compiler, 2 years before ClojureScript. Other devs used his work as a basis for Stevedore, which he's happy to say is used in production every day at Circle. In addition to co-founding Circle as a means to bring Continuous Deployment to the masses, he looks forward to building tools that help debug test failures by anticipating common problems. He currently deploys six times a day, and more on weekends."]
     ,
       name: "David Lowe"
       visible: true
@@ -29,7 +29,7 @@
       email: "dlowe@circleci.com"
       photo: "dlowe"
       bio: ["David is responsible for all of the bugs. He wrote his first buggy code in BASIC with a pencil and paper, and he's been getting better at it ever since. He keeps a 1/4 acre garden and eats almost entirely home-grown veggies during the growing season.  Helping to balance out the numerous times that he's lost at chess, David has won the IOCCC on 5 separate occasions. In a past life, he wrote MTA software which at one time was sending about 1% of the internet's email.",
-            "Besides being invited to speak at the Frozen Perl Conference, OIT's CSET Department, and SOU's CS Department, David co-founded the SF perl user group, the southern Oregon geek group, and the Rogue hack lab hackerspace. He drank the automated testing kool-aid years ago, and after introducing and championing (and constantly fiddling with) continuous integration tools at his last N jobs, he came to Circle, and is particularly keen on building and scaling things which would be impossible to justify for any single small company where CI isn't what they do. He splits his time between introducing new bugs, telling kids to get off his lawn, and baking pies. His current running total is roughly 1500 delicious pastry concoctions."]
+        "Besides being invited to speak at the Frozen Perl Conference, OIT's CSET Department, and SOU's CS Department, David co-founded the SF perl user group, the southern Oregon geek group, and the Rogue hack lab hackerspace. He drank the automated testing kool-aid years ago, and after introducing and championing (and constantly fiddling with) continuous integration tools at his last N jobs, he came to Circle, and is particularly keen on building and scaling things which would be impossible to justify for any single small company where CI isn't what they do. He splits his time between introducing new bugs, telling kids to get off his lawn, and baking pies. His current running total is roughly 1500 delicious pastry concoctions."]
     ,
       name: "Jenneviere Villegas"
       photo: "jenn"
@@ -39,7 +39,7 @@
       twitter: "jenneviere"
       email: "jenneviere@circleci.com"
       bio: ["Jenneviere moved to the Bay Area in 2010 after spending a few months there, singing and dancing, as the lead in a rock opera. She then worked as an extra on the movie Twixt, and when she realized that Mr. Coppola wasn't going to cast her as the lead in his next film, she began drowning her sorrows in hoppy IPAs and the soothing click of knitting needles. She took the black and works the Gate at That Thing In The Desert, and has only written a single line of code, ever.",
-            "Drawing from her many years' experience in her previous position as the Customer Amazement Specialist, Operations Manager, Returns Siren, and Retail Store Maven for a certain large utility kilt manufacturer, Jenneviere brings her skills as a professional pottymouth, dabbler in inappropriate and snarky humor, and cat wrangling to the team at Circle, and spends most of her time trying to keep everyone well-groomed and hairball free."]
+        "Drawing from her many years' experience in her previous position as the Customer Amazement Specialist, Operations Manager, Returns Siren, and Retail Store Maven for a certain large utility kilt manufacturer, Jenneviere brings her skills as a professional pottymouth, dabbler in inappropriate and snarky humor, and cat wrangling to the team at Circle, and spends most of her time trying to keep everyone well-groomed and hairball free."]
     ,

       name: "Daniel Woelfel"
@@ -50,7 +50,7 @@
       twitter: "DanielWoelfel"
       email: "daniel@circleci.com"
       bio: ["Despite all our coercions, promises, bribes, and outright threats for nearly a year, it took us putting Daniel in a time-out and forcing him to type things about himself to get a bio out of him. He doesn't really have any hobbies: if he's doing something, it's usually either reading romantic fiction (not to be confused with romance novels!), coding, or binge-watching episodes of :tv-show-he-just-discovered-that-is-so-awesome-he-can't-put-it-down. One of his first programming projects was an app written in Python that creates an exact replica of an image using only html tables. It's been almost 2 years since he last touched the thing, and it's still cranking out tables. He's only been rejected from YCombinator 3 times, which is empirically below the threshold of consolation prizes.",
-            "Daniel has a bachelors degree in Math from Texas A&M. He has written an app to graph Kickstarter projects, and was a customer (the 155th!) before he joined the Circle team. He looks forward to helping increase the 'wow, I can do something cool with this!' feeling that users get when they use Circle, and has been tinkering with a few things recently to make that possible--a native websockets library so that our output feels more like you're directly connected to a terminal and hooking in something like jquery.terminal so that you actually can be directly connected to a terminal. He currently helps feed his movie habit by helping organize weekly movie nights at the office. Next up: Fight Club."]
+        "Daniel has a bachelors degree in Math from Texas A&M. He has written an app to graph Kickstarter projects, and was a customer (the 155th!) before he joined the Circle team. He looks forward to helping increase the 'wow, I can do something cool with this!' feeling that users get when they use Circle, and has been tinkering with a few things recently to make that possible--a native websockets library so that our output feels more like you're directly connected to a terminal and hooking in something like jquery.terminal so that you actually can be directly connected to a terminal. He currently helps feed his movie habit by helping organize weekly movie nights at the office. Next up: Fight Club."]
     ,
       name: "Mahmood Ali"
       photo: "mahmood"
@@ -60,7 +60,7 @@
       twitter: "notnoop"
       email: "mahmood@circleci.com"
       bio: ["Mahmood loves car camping, which might explain why he also likes moving as much as he does. He and his family have moved 6 times in the last 6 years, though his baby boy has been completely lazy and hasn't offered to carry a single box yet. In between relocation to new abodes, Mahmood spends the nicer Boston weather exploring bike trails and learning computer languages. He's determined that his son will know Java before he can talk.",
-            "During his time at MIT, Mahmood spoke at a few Java research conferences including Devoxx, and has pushed code to Java 8--and Java 7--compilers. He's been a speaker at OOPSLA/Splash, and is active in the open-source community. He enjoys being reminded of his in-production projects, like java-apns, every time he gets support tickets. He acknowledges his own fallibility, and aims to help the Circle team continue to bring reliable test automation to all developers. His Achilles Heel is his pair of permanently underachieving glasses."]
+        "During his time at MIT, Mahmood spoke at a few Java research conferences including Devoxx, and has pushed code to Java 8--and Java 7--compilers. He's been a speaker at OOPSLA/Splash, and is active in the open-source community. He enjoys being reminded of his in-production projects, like java-apns, every time he gets support tickets. He acknowledges his own fallibility, and aims to help the Circle team continue to bring reliable test automation to all developers. His Achilles Heel is his pair of permanently underachieving glasses."]
     ,

       name: "Gordon Syme"
@@ -71,7 +71,7 @@
       twitter: "gordon_syme"
       email: "gordon@circleci.com"
       bio: ["Gordon has been racing dinghies right through the winter off the coast of Ireland for the past 16-odd years, and as his oft-showcased cabinet of prize coffee mugs will tell you, he's damn good at it. When he's not busy bludgeoning a piano with his ham-fisted fingers, he spends his free time rolling down mountains on a bicycle… sometimes even the right way up. In his down time, he's a board game shark, though you won't catch him trying to buy Boardwalk or get out of jail free.",
-            "Gordon joins the team after spending significant time at Amazon building tools to monitor the entire network (he built their DWDM monitoring from the ground up). He also built a JVM bytecode recompiler to enable running applications on a clustered VM without needing re-programming effort, and has grand plans for how he's going to make waves at Circle. We're pretty sure they include company Power Grid tournaments."]
+        "Gordon joins the team after spending significant time at Amazon building tools to monitor the entire network (he built their DWDM monitoring from the ground up). He also built a JVM bytecode recompiler to enable running applications on a clustered VM without needing re-programming effort, and has grand plans for how he's going to make waves at Circle. We're pretty sure they include company Power Grid tournaments."]
     ,

       name: "Danny King"
@@ -82,5 +82,5 @@
       twitter: "dannykingme"
       email: "danny@circleci.com"
       bio: ["Danny (not to be confused with Daniel) has batted a .500 baseball season with zero strikeouts and once took 2nd place at a poker tournament at the Mirage in Vegas. When he was 15 he, along with two of his paintball teammates, won a pickup paintball game against the three top-rated players from the number one team in the world, though he reckons this is only arguably impressive.  In the world of virtual sports, Danny has scored over a million points in Tony Hawk's Pro Skater, and has racked up massive karma points by designing a website--pro bono--for a non-profit organization that helps provide activity bags to hospitalized children. He was selected for a reality TV show on Fox but he turned it down (they're evil).",
-            "Danny finished high school at the young age of 16.  Two years after that, he cashed his first design freelance check. His past clients include Verizon, Lego, The Tribune, and Tyson Foods, and he has four individual 5-star rated projects in the Age of Empires design community. Not only does he design, but he's learned a bit of code, too, so he can build the things he designs, and his past projects include writing a tool that auto-hides Google Map's UI with simple CSS. He comes to Circle bursting with fresh ideas (like the new logo!) and can be found manipulating pixels with his hood securely in the Up position."]
+        "Danny finished high school at the young age of 16.  Two years after that, he cashed his first design freelance check. His past clients include Verizon, Lego, The Tribune, and Tyson Foods, and he has four individual 5-star rated projects in the Age of Empires design community. Not only does he design, but he's learned a bit of code, too, so he can build the things he designs, and his past projects include writing a tool that auto-hides Google Map's UI with simple CSS. He comes to Circle bursting with fresh ideas (like the new logo!) and can be found manipulating pixels with his hood securely in the Up position."]
     ]


Track 'after' by reference

Track 'after' by reference

--- src/dockerfile.coffee
+++ src/dockerfile.coffee
@@ -37,17 +37,17 @@
       command.doAfter(@)
       return @
     command.doAfter = (after) =>
-      index = @commands.indexOf(after)
-      throw new Error("Add/Override this command to Dockerfile first") if index == -1
-      command.after = index
+      throw new Error("Add/Override this command to Dockerfile first") if @commands.indexOf(after) == -1
+      command.after = after
       return command
-    command.after = -1
     return command
   override: (command) ->
     unless command.overrides()
       throw new Error("This command does not override")
     for c, i in @commands
       if c.keyword() == command.keyword()
+        for dep in @commands
+          dep.after = command if dep.after == c
         command.after = c.after
         @commands[i] = command
         return command
@@ -55,8 +55,6 @@
   toString: ->
     output = ""
     commands = clone(@commands)
-    # At first, replace indices with references
-    commands.forEach((c) -> if c.after == -1 then c.after = undefined else c.after = commands[c.after])
     # Make indepdendent commands dependent
     makeDependent = (c) ->
       for cmd in commands


Add map of labels for statistical values

Currently, the only mapping is "Distinct count" --> "Unique values"

--- src/coffee/cilantro/ui/field/stats.coffee
+++ src/coffee/cilantro/ui/field/stats.coffee
@@ -15,8 +15,16 @@
     class FieldStatValue extends Marionette.ItemView
         tagName: 'li'

-        template: (data) ->
-            "#{ data.label }#{ data.value }"
+        # This is a map of data labels to display labels. For example, when
+        # displaying data with a label of 'Distinct count', it will be
+        # rendered as 'Unique values' when this field stat value is displayed
+        # in the page. If a data label is not in this map, then the data
+        # label itself will be used.
+        labelMap:
+            'Distinct count': 'Unique values'
+
+        template: (data) =>
+            "#{ @labelMap[data.label] or data.label }#{ data.value }"

     class FieldStatsValues extends Marionette.CollectionView


store agent url, so we can stop an instance even when we haven't received any events or inspect data.

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -75,6 +75,7 @@
     Instances.upsert {name: instance}, $set:
       startedBy: user._id
       images: (service.image for serviceName, service of dockerCompose)
+      agent: url: agentUrl
       app:
         name: app
         version: version


refactoring, moving script-gen to agent

refactoring, moving script-gen to agent

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -49,23 +49,16 @@
         dir: dir
         startScript: Scripts.bash.start app, version, instance, options, parameters
         stopScript: Scripts.bash.stop app, version, instance, options, parameters
+        app:
+          name: app
+          version: version
+          definition: YAML.safeLoad (findAppDef app, version).def
+        instance:
+          name: instance
+          options: options
+          #parameters: parameters we do not send params to the agent, this is a feature of the dashboard

-<<<<<<< Updated upstream
     console.log "Sending a POST request to '#{agentUrl}' to start '#{instance}'."
-=======
-    appInfo =
-      app:
-        name: app
-        version: version
-      definition: YAML.safeLoad (findAppDef app, version).def
-      parameters: parameters
-      options: options
-    callOpts.data.appInfo = appInfo
-
-    console.log 'APPINFO', appInfo
-
-    console.log "Sending request to #{options.agentUrl}"
->>>>>>> Stashed changes

     HTTP.post "#{agentUrl}/app/install-and-run?access_token=#{Settings.get('agentAuthToken')}", callOpts, (err, result) ->
       throw new Meteor.Error err if err
@@ -102,7 +95,7 @@
       user = Meteor.user()
     else
       user = userId: null, username: 'API'
-
+
     Instances.upsert {name: instanceName}, $set:
       'meta.stoppedBy':
         userId: user._id


:new: Associate views with linter

:new: Associate views with linter

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -1,6 +1,7 @@
 Path = require 'path'
 {CompositeDisposable, Emitter} = require 'atom'
 LinterView = require './linter-view-manager'
+LinterViews = require './linter-views'
 Bubble = require './bubble'
 Panel = require './panel'
 PanelView = require './views/panel'
@@ -15,6 +16,7 @@

     @emitter = new Emitter
     @view = new LinterView this
+    @views = new LinterViews this
     @bottom = new Bottom this
     @statusBar = null
     @messagesProject = new Map
@@ -76,5 +78,6 @@
     @eachLinter (linter) ->
       linter.subscriptions.dispose()
     @panelModal.destroy()
+    @views.deactivate()

 module.exports = Linter


initial site lines for crosshair tool

initial site lines for crosshair tool

--- bokehjs/src/coffee/tool/crosshair_tool.coffee
+++ bokehjs/src/coffee/tool/crosshair_tool.coffee
@@ -27,6 +27,32 @@
       SetBasepoint: "_set_base_point"
     }

+    render: () ->
+      if not @active
+        return
+
+      ctx = @plot_view.ctx
+
+      cw = @plot_view.view_state.get('canvas_width')
+      ch = @plot_view.view_state.get('canvas_height')
+
+      line_width = 1
+
+      ctx.save()
+
+      ctx.strokeStyle = 'red'
+      ctx.globalAlpha = 0.7
+      ctx.lineWidth   = line_width
+      ctx.setLineDash([])
+
+      ctx.beginPath()
+      ctx.moveTo(0,@y)
+      ctx.lineTo(cw,@y)
+      console.log(@x,@y)
+      ctx.moveTo(@x, 0)
+      ctx.lineTo(@x, ch)
+      ctx.stroke()
+      ctx.restore()

     mouse_coords: (e, x, y) ->
       return [x, y]
@@ -35,6 +61,7 @@
       if @active
         return
       @active = true
+      #[@x,@y] = [5,5]
       @popup = $(
         '''<div class="resize_popup pull-right"
           style="border-radius: 10px; background-color: lightgrey; padding:3px 8px; font-size: 14px;
@@ -45,9 +72,9 @@
       cw = @plot_view.view_state.get('outer_width')
       @popup.text("x: 0 y:0")

-      @request_render()
-      @plot_view.$el.css("cursor", "crosshair")
-      @plot_view.request_render()
+      #@request_render()
+      #@plot_view.$el.css("cursor", "crosshair")
+      #@plot_view.request_render()
       return null

     _deactivate: (e) ->
@@ -70,13 +97,13 @@
       ydiff = y - @y
       [@x, @y] = [x, y]

-      ch = @plot_view.view_state.get('outer_height')
-      cw = @plot_view.view_state.get('outer_width')

       data_x = sprintf("%.4f", @plot_view.xmapper.map_from_target(x))
       data_y = sprintf("%.4f", @plot_view.ymapper.map_from_target(y))
       @popup.text("x: #{data_x} y: #{data_y}")
-
+      @request_render()
+      @plot_view.request_render()
+      @plot_view.unpause(true)
       return null

   class CrosshairTool extends Tool.Model


Paginator is not implicitly hidden #377

Paginator is not implicitly hidden #377

--- wzk/ui/grid/Paginator.coffee
+++ wzk/ui/grid/Paginator.coffee
@@ -142,7 +142,7 @@
       @dom.replaceNode newClone, oldClone
       newClones.push newClone
     @clones = newClones
-    @show true
+    @show(true)

   ###*
     @protected
@@ -172,7 +172,7 @@

     @renderer.decorate @, el
     @setElementInternal el
-    @showInternal false
+    @showInternal(false)

   ###*
     @protected
@@ -269,13 +269,15 @@
     @param {boolean=} force default is false
   ###
   show: (visible, force = false) ->
-    return if not force and @canHide()
-    @showInternal visible
+    if not force and @canHide()
+      @showInternal(false)
+    else
+      @showInternal(visible)

   ###*
     @protected
     @param {boolean} visible
   ###
   showInternal: (visible) ->
-    visibility = if visible then 'visible' else 'hidden'
-    goog.style.setStyle(el, 'visibility', visibility) for el in [@getElement()].concat @clones
+    func = if visible then goog.dom.classes.remove else goog.dom.classes.add
+    func(el, 'empty') for el in [@getElement()].concat(@clones)


add vim style ctags key bindings

add vim style ctags key bindings

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -130,6 +130,9 @@
   's': 'vim-mode:substitute'
   '.': 'vim-mode:repeat'

+  'ctrl-]': 'symbols-view:go-to-declaration'
+  'ctrl-T': 'symbols-view:return-from-declaration'
+
   '" a': 'vim-mode:register-prefix'
   '" b': 'vim-mode:register-prefix'
   '" c': 'vim-mode:register-prefix'