Reorder grammar rules logically

Reorder grammar rules logically

--- grammar.coffee
+++ grammar.coffee
@@ -22,24 +22,41 @@
   rules:
     program: -> repeat(@statement)

+    #
+    # Statements
+    #
+
     statement: -> choice(
       @expression_statement,
+      @var_declaration,
+      @statement_block,
+
       @if_statement,
       @switch_statement,
       @for_statement,
       @for_in_statement,
       @while_statement,
       @do_statement,
+      @try_statement,
+
       @break_statement,
-      @statement_block,
       @return_statement,
       @throw_statement,
-      @try_statement,
       @delete_statement,
-      @var_declaration)
+    )

     expression_statement: -> seq(
       err(@expression), terminator())
+
+    var_declaration: -> seq(
+      keyword("var"),
+      commaSep1(err(choice(
+        @identifier,
+        @var_assignment))),
+      terminator())
+
+    statement_block: -> seq(
+      "{", err(repeat(@statement)), "}")

     if_statement: -> seq(
       keyword("if"),
@@ -54,21 +71,6 @@
       keyword("switch"),
       "(", @expression, ")",
       "{", repeat(choice(@case, @default)), "}")
-
-    case: -> seq(
-      keyword("case"),
-      @expression,
-      ":",
-      repeat(@statement))
-
-    default: -> seq(
-      keyword("default"),
-      ":",
-      repeat(@statement))
-
-    break_statement: -> seq(
-      keyword("break"),
-      terminator())

     for_statement: -> seq(
       keyword("for"),
@@ -103,34 +105,46 @@
       keyword("while"),
       @_paren_expression)

-    return_statement: -> seq(
-      keyword("return"),
-      optional(@expression),
-      terminator())
-
-    delete_statement: -> seq(
-      keyword("delete"),
-      choice(@member_access, @subscript_access),
-      terminator())
-
-    var_declaration: -> seq(
-      keyword("var"),
-      commaSep1(err(choice(
-        @identifier,
-        @var_assignment))),
-      terminator())
-
-    var_assignment: -> seq(
-      @identifier,
-      "=",
-      @expression)
-
     try_statement: -> seq(
       keyword("try"),
       @statement_block,
       optional(@catch),
       optional(@finally))

+    break_statement: -> seq(
+      keyword("break"),
+      terminator())
+
+    return_statement: -> seq(
+      keyword("return"),
+      optional(@expression),
+      terminator())
+
+    throw_statement: -> seq(
+      keyword("throw"),
+      @expression,
+      terminator())
+
+    delete_statement: -> seq(
+      keyword("delete"),
+      choice(@member_access, @subscript_access),
+      terminator())
+
+    #
+    # Statement components
+    #
+
+    case: -> seq(
+      keyword("case"),
+      @expression,
+      ":",
+      repeat(@statement))
+
+    default: -> seq(
+      keyword("default"),
+      ":",
+      repeat(@statement))
+
     catch: -> seq(
       keyword("catch"),
       optional(seq("(", @identifier, ")"))
@@ -140,12 +154,37 @@
       keyword("finally"),
       @statement_block)

-    throw_statement: -> seq(
-      keyword("throw"),
-      @expression,
-      terminator())
+    var_assignment: -> seq(
+      @identifier,
+      "=",
+      @expression)
+
+    _paren_expression: -> seq(
+      "(", err(@expression), ")")
+
+    #
+    # Expressions
+    #

     expression: -> choice(
+      @object,
+      @array,
+      @function,
+      @function_call,
+      @constructor_call,
+      @member_access,
+      @subscript_access,
+      @assignment,
+      @math_assignment,
+      @ternary,
+      @bool_op,
+      @math_op,
+      @comma_op,
+      @bitwise_op,
+      @rel_op,
+      @type_op,
+      @_paren_expression,
+
       @identifier,
       @number,
       @string,
@@ -155,23 +194,19 @@
       @false,
       @null,
       @undefined,
-      @object,
-      @array,
-      @function,
-      @member_access,
-      @subscript_access,
-      @function_call,
-      @constructor_call,
-      @ternary,
-      @bool_op,
-      @math_op,
-      @comma_op,
-      @bitwise_op,
-      @rel_op,
-      @type_op,
-      @assignment,
-      @math_assignment,
-      @_paren_expression)
+    )
+
+    object: -> seq(
+      "{", commaSep(err(@pair)), "}")
+
+    array: -> seq(
+      "[", commaSep(err(@expression)), "]")
+
+    function: -> seq(
+      keyword("function"),
+      optional(@identifier),
+      "(", optional(@formal_parameters), ")",
+      @statement_block)

     function_call: -> prec(20, seq(
       @expression,
@@ -182,9 +217,6 @@
       @expression,
       optional(seq(
         "(", err(optional(@arguments)), ")"))))
-
-    arguments: ->
-      commaSep1(err(@expression))

     member_access: -> prec(31, seq(
       @expression,
@@ -211,38 +243,8 @@
       choice("+=", "-=", "*=", "/="),
       @expression))

-    object: -> seq(
-      "{", commaSep(err(@pair)), "}")
-
-    pair: -> seq(
-      choice(@identifier, @string),
-      ":",
-      @expression)
-
-    function: -> seq(
-      keyword("function"),
-      optional(@identifier),
-      "(", optional(@formal_parameters), ")",
-      @statement_block)
-
-    formal_parameters: ->
-      commaSep1(@identifier)
-
-    statement_block: -> seq(
-      "{", err(repeat(@statement)), "}")
-
-    array: -> seq(
-      "[", commaSep(err(@expression)), "]")
-
-    identifier: -> /[\a_$][\a\d_$]*/
-
-    number: -> token(choice(
-      seq(
-        "0x",
-        /[\da-fA-F]+/),
-      seq(
-        /\d+/,
-        optional(seq(".", /\d*/)))))
+    ternary: -> prec(-1, seq(
+      @expression, "?", @expression, ":", @expression))

     bool_op: -> choice(
       prec(4, seq("!", @expression)),
@@ -254,9 +256,6 @@
       prec(13, seq(@expression, "<<", @expression)),
       prec(12, seq(@expression, "&", @expression)),
       prec(11, seq(@expression, "|", @expression)))
-
-    ternary: -> prec(-1, seq(
-      @expression, "?", @expression, ":", @expression))

     comma_op: -> prec(-3, seq(
       @expression, ",", @expression))
@@ -286,24 +285,50 @@
       prec(12, seq(@expression, keyword("instanceof"), @expression)),
       prec(12, seq(@expression, keyword("in"), @expression)))

+    #
+    # Primitives
+    #
+
+    comment: -> token(choice(
+      seq("//", /.*/),
+      seq("/*", repeat(choice(/[^\*]/, /\*[^/]/)), "*/")))
+
     string: -> token(choice(
       seq('"', repeat(choice(/[^\\"\n]/, /\\./)), '"'),
       seq("'", repeat(choice(/[^\\'\n]/, /\\./)), "'")))

-    comment: -> token(choice(
-      seq("//", /.*/),
-      seq("/*", repeat(choice(/[^\*]/, /\*[^/]/)), "*/")))
-
     regex: -> token(seq(
       '/', repeat(choice(/[^\\/\n]/, /\\./)), '/',
       repeat(choice('i', 'g'))))
+
+    number: -> token(choice(
+      seq(
+        "0x",
+        /[\da-fA-F]+/),
+      seq(
+        /\d+/,
+        optional(seq(".", /\d*/)))))
+
+    identifier: -> /[\a_$][\a\d_$]*/

     true: -> keyword("true")
     false: -> keyword("false")
     null: -> keyword("null")
     undefined: -> keyword("undefined")
-
-    _paren_expression: -> seq(
-      "(", err(@expression), ")")
-
     _line_break: -> "\n"
+
+    #
+    # Expression components
+    #
+
+    arguments: ->
+      commaSep1(err(@expression))
+
+    formal_parameters: ->
+      commaSep1(@identifier)
+
+    pair: -> seq(
+      choice(@identifier, @string),
+      ":",
+      @expression)
+


fix empty values

fix empty values

--- hooks.coffee
+++ hooks.coffee
@@ -84,17 +84,20 @@
         this.autorun (c) ->
           url = window.location.href
           for prop in props
-            url = VmHelper.updateQueryString( tName + "." + prop, that.viewmodel[prop]().toString(), url)
+            value = that.viewmodel[prop]() or ""
+            url = VmHelper.updateQueryString( tName + "." + prop, value.toString(), url)
           window.history.pushState(null, null, url) if not c.firstRun and document.URL isnt url

         # Update view model from URL
         updateFromUrl = (state, title, url = document.URL) ->
+          processed = []
           for key, value of VmHelper.url(url).queryKey when ~key.indexOf(".")
             [template, property] = key.split(".")
             if property in props
+              processed.push property
               template = template.split("%2E").join(".") if ~template.indexOf("%2E")
               decodedValue = decodeURI(value)
-              if template is tName and that.viewmodel[property]() isnt decodedValue
+              if template is tName
                 that.viewmodel[property] decodedValue
         window.onpopstate = window.history.onstatechange = updateFromUrl
         updateFromUrl()


Change easing

Change easing

--- compare-ipo/main.coffee
+++ compare-ipo/main.coffee
@@ -5,6 +5,7 @@
 valueheaders = []
 logscale = false
 duration = 2000
+ease = 'cubic-in-out'
 first = true

 powerOfTen = (d) -> d / Math.pow(10, Math.ceil(Math.log(d) / Math.LN10 - 1e-12)) == 1
@@ -170,7 +171,7 @@
   if svg.select('.x').empty()
     svg.append('g').call(setupXAxis)
   else
-    svg.select('.y').transition().duration(duration).call(setupXAxis)
+    svg.select('.y').transition().duration(duration).ease(ease).call(setupXAxis)

   if svg.select('.y').empty()
     svg.append('g').call(setupYAxis)
@@ -179,7 +180,7 @@
       .attr y: '6px', dy: '.71em', transform: 'rotate(-90)'
       .text("In billions of #{unit}")
   else
-    svg.select('.y').transition().duration(duration).call(setupYAxis)
+    svg.select('.y').transition().duration(duration).ease(ease).call(setupYAxis)

   plot = svg.selectAll('.target')
     .data(targets)
@@ -196,7 +197,7 @@
         stroke: 'black'
         'stroke-width': 0

-  plot.transition().duration(duration).call(setupPlot)
+  plot.transition().duration(duration).ease(ease).call(setupPlot)

   enter = plot.enter().append('circle').call(setupPlot)
     .on 'mouseover', (d) ->
@@ -214,12 +215,12 @@
     enter
       .attr
         r: 0
-      .transition().duration(duration)
+      .transition().duration(duration).ease(ease)
       .attr
         r: (d) -> r(rvalue(d[selected]))

   plot.exit()
-    .transition().duration(duration)
+    .transition().duration(duration).ease(ease)
     .attr
       r: 0
     .remove()


Bugfix

Bugfix

--- app/views/base/view.coffee
+++ app/views/base/view.coffee
@@ -15,7 +15,7 @@
     @stickit()

   initSelectors: ->
-    for element, selector of @elements do (element, selector) =>
+    for element, selector of @elements then do (element, selector) =>
       this["$#{element}"] = (subSelector) =>
         $el = @$(selector)
         $el = $el.find subSelector if subSelector


init cache before setting up app

init cache before setting up app

--- index.coffee
+++ index.coffee
@@ -7,12 +7,17 @@
 { PORT } = require "./config"
 express = require "express"
 setup = require "./lib/setup"
+cache = require './lib/cache'

 app = module.exports = express()
-setup app

 # Start the server and send a message to IPC for the integration test
 # helper to hook into.
-app.listen PORT, ->
-  console.log "Listening on port " + PORT
-  process.send? "listening"+cache.setup ->
+  setup app
+
+  # Start the server and send a message to IPC for the integration test helper
+  # to hook into.
+  app.listen PORT, ->
+    console.log "Listening on port " + PORT
+    process.send? "listening"


fix merge conflict

fix merge conflict

--- index.coffee
+++ index.coffee
@@ -3,7 +3,7 @@
 {EventEmitter} = require 'events'
 debug          = require('debug')('meshblu-splunk-event-collector')

-
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
 ERROR_BASE_URL_INVALID = "SplunkEventUrl is undefined or invalid"
 ERROR_EVENT_COLLECTOR_TOKEN_INVALID = "EventCollectorToken is undefined or invalid"

@@ -40,7 +40,7 @@
   onMessage: (message) =>
      @emit('message', {topic: "error", error: ERROR_BASE_URL_INVALID}) if not @options?.SplunkEventUrl?
      @emit('message', {topic: "error", error: ERROR_EVENT_COLLECTOR_TOKEN_INVALID}) if not @options?.EventCollectorToken?
-     @request.post(@options.SplunkEventUrl, {
+     @request.post @options.SplunkEventUrl, {
         json : true
         rejectUnauthorized: false
         requestCert: true
@@ -50,18 +50,19 @@
         body :
           event : message
        }, (error, response, body) ->
-            @emit('message', {
-              devices: ["*"],
-              topic: 'error',
-              errorMessage: error
-            }) if error
-            @emit('message', {
-              devices : ["*"],
-              statusCode: response.statusCode,
-              result: body
-            }
-            ) unless error
-     )
+
+          @emit('message', {
+            devices: ["*"],
+            topic: 'error',
+            errorMessage: error
+          }) if error
+
+          @emit('message', {
+            devices : ["*"],
+            statusCode: response.statusCode,
+            result: body
+          }) unless error
+

   onConfig: (device) =>
     @setOptions device.options


adding test NODE_ENV

adding test NODE_ENV

--- index.coffee
+++ index.coffee
@@ -51,7 +51,7 @@

 reddish_port = 8000
 reddish_monitor_port = 8001
-reddish_hostname = 'reddi.sh'
+reddish_hostname = if env = process.env.NODE_ENV is 'test' then 'dev.freeflow.io' else 'reddi.sh'

 # setup handshaken and connected state


fix missing module

fix missing module

--- index.coffee
+++ index.coffee
@@ -1,4 +1,5 @@
 fs = require 'fs'
+path = require 'path'
 debug = require('debug') 'tastebin'
 express = require 'express'
 morgan = require 'morgan'


Update texturePath for previous commit

Update texturePath for previous commit

--- index.coffee
+++ index.coffee
@@ -59,7 +59,7 @@
       generateChunks: false
       chunkDistance: 2
       materials: []  # added dynamically later
-      texturePath: 'AssetPacks/ProgrammerArt/textures/blocks/' # subproject with textures
+      texturePath: 'ArtPacks/ProgrammerArt/textures/blocks/' # subproject with textures
       worldOrigin: [0, 0, 0]
       controls:
         discreteFire: false


Add command to get HEAD's commit hash

Add command to get HEAD's commit hash

--- index.coffee
+++ index.coffee
@@ -45,6 +45,15 @@

         Impromptu.exec 'git rev-parse --short HEAD', (_err, _result) ->
           done _err, _result
+
+  # Short commit hash
+  register 'commit',
+    update: (done) ->
+      Impromptu.exec 'git rev-parse --short HEAD 2>/dev/null', (err, result) ->
+        if err
+          done err, null
+        else
+          done err, result

   # Determine whether the repo is currently in a detached head state
   # This happens when you checkout, for example, a commit hash


[Async] Reduce generator passes with "yield return"

We can do something like tail call optimization in the methods where the last statement is a yield.

--- index.coffee
+++ index.coffee
@@ -214,7 +214,7 @@
 sendSmsAsync = co.wrap (userMobileNumber, message) ->
   """Sends an SMS to a given number"""

-  yield twilio.promise.sendMessage {
+  yield return twilio.promise.sendMessage {
     to: userMobileNumber
     from: FROM_MOBILE_NUMBER
     body: message
@@ -250,7 +250,7 @@
     messageToSend = "#{ message } #{ code }"

-  yield sendSmsAsync userMobileNumber, messageToSend
+  yield return sendSmsAsync userMobileNumber, messageToSend

 checkCodeAsync = co.wrap (opts) ->
   """Checks a code"""


lexer: simplified tokenizers' responsibility

--- lexer.coffee
+++ lexer.coffee
@@ -26,9 +26,8 @@
   # (for interpolations). When the next token has been recorded, we move forward
   # within the code past the token, and begin again.
   #
-  # Each tokenizing method is responsible for incrementing `@i` by the number of
-  # characters it has consumed. `@i` can be thought of as our finger on the page
-  # of source.
+  # Each tokenizing method is responsible for returning the number of characters
+  # it has consumed.
   #
   # Before returning the token stream, run it through the [Rewriter](rewriter.html)
   # unless explicitly asked not to.
@@ -36,7 +35,6 @@
     code     = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
     o        = options or {}
     @code    = code         # The remainder of the source code.
-    @i       = 0            # Current character position we're parsing.
     @line    = o.line or 0  # The current line.
     @indent  = 0            # The current indentation level.
     @indebt  = 0            # The over-indentation at the current level.
@@ -48,17 +46,18 @@
     # At every position, run through this list of attempted matches,
     # short-circuiting if any of them succeed. Their order determines precedence:
     # `@literalToken` is the fallback catch-all.
-    while @chunk = code.slice @i
-      @identifierToken() or
-      @commentToken()    or
-      @whitespaceToken() or
-      @lineToken()       or
-      @heredocToken()    or
-      @stringToken()     or
-      @numberToken()     or
-      @regexToken()      or
-      @jsToken()         or
-      @literalToken()
+    i = 0
+    while @chunk = code.slice i
+      i += @identifierToken() or
+           @commentToken()    or
+           @whitespaceToken() or
+           @lineToken()       or
+           @heredocToken()    or
+           @stringToken()     or
+           @numberToken()     or
+           @regexToken()      or
+           @jsToken()         or
+           @literalToken()
     @closeIndentation()
     return @tokens if o.rewrite is off
     (new Rewriter).rewrite @tokens
@@ -73,21 +72,20 @@
   # referenced as property names here, so you can still do `jQuery.is()` even
   # though `is` means `===` otherwise.
   identifierToken: ->
-    return false unless match = IDENTIFIER.exec @chunk
+    return 0 unless match = IDENTIFIER.exec @chunk
     [input, id, colon] = match
-    @i += input.length
     if id is 'all' and @tag() is 'FOR'
       @token 'ALL', id
-      return true
+      return 3
     if id is 'from' and @tag(1) is 'FOR'
       @seenFor  = no
       @seenFrom = yes
       @token 'FROM', id
-      return true
+      return 4
     if id is 'to' and @seenFrom
       @seenFrom = no
       @token 'TO', id
-      return true
+      return 2
     forcedIdentifier = colon or @tagAccessor()
     tag = 'IDENTIFIER'
     if id in JS_KEYWORDS or
@@ -126,41 +124,39 @@
         tag = 'BOOL'
     @token tag, id
     @token ':', ':' if colon
-    true
+    input.length

   # Matches numbers, including decimals, hex, and exponential notation.
   # Be careful not to interfere with ranges-in-progress.
   numberToken: ->
-    return false unless match = NUMBER.exec @chunk
+    return 0 unless match = NUMBER.exec @chunk
     number = match[0]
-    return false if @tag() is '.' and number.charAt(0) is '.'
-    @i += number.length
+    return 0 if @tag() is '.' and number.charAt(0) is '.'
     @token 'NUMBER', number
-    true
+    number.length

   # Matches strings, including multi-line strings. Ensures that quotation marks
   # are balanced within the string's contents, and within nested interpolations.
   stringToken: ->
     switch @chunk.charAt 0
       when "'"
-        return false unless match = SIMPLESTR.exec @chunk
+        return 0 unless match = SIMPLESTR.exec @chunk
         @token 'STRING', (string = match[0]).replace MULTILINER, '\\\n'
       when '"'
-        return false unless string = @balancedString @chunk, [['"', '"'], ['#{', '}']]
+        return 0 unless string = @balancedString @chunk, [['"', '"'], ['#{', '}']]
         if 0 < string.indexOf '#{', 1
           @interpolateString string.slice 1, -1
         else
           @token 'STRING', @escapeLines string
       else
-        return false
+        return 0
     @line += count string, '\n'
-    @i += string.length
-    true
+    string.length

   # Matches heredocs, adjusting indentation to the correct level, as heredocs
   # preserve whitespace, but ignore indentation to the left.
   heredocToken: ->
-    return false unless match = HEREDOC.exec @chunk
+    return 0 unless match = HEREDOC.exec @chunk
     heredoc = match[0]
     quote = heredoc.charAt 0
     doc = @sanitizeHeredoc match[2], {quote, indent: null}
@@ -169,49 +165,44 @@
     else
       @token 'STRING', @makeString doc, quote, yes
     @line += count heredoc, '\n'
-    @i += heredoc.length
-    true
+    heredoc.length

   # Matches and consumes comments.
   commentToken: ->
-    return false unless match = @chunk.match COMMENT
+    return 0 unless match = @chunk.match COMMENT
     [comment, here] = match
     @line += count comment, '\n'
-    @i += comment.length
     if here
       @token 'HERECOMMENT', @sanitizeHeredoc here,
         herecomment: true, indent: Array(@indent + 1).join(' ')
       @token 'TERMINATOR', '\n'
-    true
+    comment.length

   # Matches JavaScript interpolated directly into the source via backticks.
   jsToken: ->
-    return false unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
+    return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
     @token 'JS', (script = match[0]).slice 1, -1
-    @i += script.length
-    true
+    script.length

   # Matches regular expression literals. Lexing regular expressions is difficult
   # to distinguish from division, so we borrow some basic heuristics from
   # JavaScript and Ruby.
   regexToken: ->
-    return false if @chunk.charAt(0) isnt '/'
+    return 0 if @chunk.charAt(0) isnt '/'
     return @heregexToken match if match = HEREGEX.exec @chunk
-    return false if @tag() in NOT_REGEX
-    return false unless match = REGEX.exec @chunk
+    return 0 if @tag() in NOT_REGEX
+    return 0 unless match = REGEX.exec @chunk
     [regex] = match
     @token 'REGEX', if regex is '//' then '/(?:)/' else regex
-    @i += regex.length
-    true
+    regex.length

   # Matches experimental, multiline and extended regular expression literals.
   heregexToken: (match) ->
     [heregex, body, flags] = match
-    @i += heregex.length
     if 0 > body.indexOf '#{'
       re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
       @token 'REGEX', "/#{ re or '(?:)' }/#{flags}"
-      return true
+      return heregex.length
     @token 'IDENTIFIER', 'RegExp'
     @tokens.push ['CALL_START', '(']
     tokens = []
@@ -228,7 +219,7 @@
     @tokens.push tokens...
     @tokens.push [',', ','], ['STRING', '"' + flags + '"'] if flags
     @token ')', ')'
-    true
+    heregex.length

   # Matches newlines, indents, and outdents, and determines which is which.
   # If we can detect that the current line is continued onto the the next line,
@@ -241,21 +232,21 @@
   # Keeps track of the level of indentation, because a single outdent token
   # can close multiple indents, so we need to know how far in we happen to be.
   lineToken: ->
-    return false unless match = MULTI_DENT.exec @chunk
+    return 0 unless match = MULTI_DENT.exec @chunk
     indent = match[0]
     @line += count indent, '\n'
-    @i    += indent.length
     prev = last @tokens, 1
     size = indent.length - 1 - indent.lastIndexOf '\n'
     nextCharacter = NEXT_CHARACTER.exec(@chunk)[1]
     noNewlines    = (nextCharacter in ['.', ','] and not NEXT_ELLIPSIS.test(@chunk)) or @unfinished()
     if size - @indebt is @indent
-      return @suppressNewlines() if noNewlines
-      return @newlineToken indent
-    else if size > @indent
+      if noNewlines then @suppressNewlines() else @newlineToken()
+      return indent.length
+    if size > @indent
       if noNewlines
         @indebt = size - @indent
-        return @suppressNewlines()
+        @suppressNewlines()
+        return indent.length
       diff = size - @indent + @outdebt
       @token 'INDENT', diff
       @indents.push diff
@@ -264,7 +255,7 @@
       @indebt = 0
       @outdentToken @indent - size, noNewlines
     @indent = size
-    true
+    indent.length

   # Record an outdent token or multiple tokens, if we happen to be moving back
   # inwards past several recorded indents.
@@ -286,27 +277,27 @@
         @token 'OUTDENT', dent
     @outdebt -= moveOut if dent
     @token 'TERMINATOR', '\n' unless @tag() is 'TERMINATOR' or noNewlines
-    true
+    this

   # Matches and consumes non-meaningful whitespace. Tag the previous token
   # as being "spaced", because there are some cases where it makes a difference.
   whitespaceToken: ->
-    return false unless (match = WHITESPACE.exec @chunk) or nline = @chunk.substring(0, 1) is '\n'
+    return 0 unless (match = WHITESPACE.exec @chunk) or
+                    (nline = @chunk.charAt(0) is '\n')
     prev = last @tokens
     prev[if match then 'spaced' else 'newLine'] = true if prev
-    @i += match[0].length if match
-    !!match
+    if match then match[0].length else 0

   # Generate a newline token. Consecutive newlines get merged together.
-  newlineToken: (newlines) ->
+  newlineToken: ->
     @token 'TERMINATOR', '\n' unless @tag() is 'TERMINATOR'
-    true
+    this

   # Use a `\` at a line-ending to suppress the newline.
   # The slash is removed here once its job is done.
   suppressNewlines: ->
     @tokens.pop() if @value() is '\\'
-    true
+    this

   # We treat all other single characters as a token. Eg.: `( ) , . !`
   # Multi-character operators are also literal tokens, so that Jison can assign
@@ -319,23 +310,21 @@
       @tagParameters() if CODE.test value
     else
       value = @chunk.charAt 0
-    @i += value.length
-    tag = value
+    tag  = value
     prev = last @tokens
     if value is '=' and prev
       @assignmentError() if not prev[1].reserved and prev[1] in JS_FORBIDDEN
       if prev[1] in ['||', '&&']
         prev[0] = 'COMPOUND_ASSIGN'
         prev[1] += '='
-        return true
+        return 1
     if      value is ';'             then tag = 'TERMINATOR'
-    else if value in LOGIC           then tag = 'LOGIC'
     else if value in MATH            then tag = 'MATH'
     else if value in COMPARE         then tag = 'COMPARE'
     else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
     else if value in UNARY           then tag = 'UNARY'
     else if value in SHIFT           then tag = 'SHIFT'
-    else if value is '?' and prev?.spaced  then tag = 'LOGIC'
+    else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
     else if prev and not prev.spaced
       if value is '(' and prev[0] in CALLABLE
         prev[0] = 'FUNC_EXIST' if prev[0] is '?'
@@ -346,7 +335,7 @@
           when '?'  then prev[0] = 'INDEX_SOAK'
           when '::' then prev[0] = 'INDEX_PROTO'
     @token tag, value
-    true
+    value.length

   # Token Manipulators
   # ------------------
@@ -361,7 +350,7 @@
     else if prev[1] is '.' and @value(1) isnt '.'
       if @tag(1) is '?'
         @tag 0, 'SOAK_ACCESS'
-        @tokens.splice(-2, 1)
+        @tokens.splice -2, 1
       else
         @tag 0, 'PROPERTY_ACCESS'
     else
@@ -385,14 +374,14 @@
   # definitions versus argument lists in function calls. Walk backwards, tagging
   # parameters specially in order to make things easier for the parser.
   tagParameters: ->
-    return if @tag() isnt ')'
+    return this if @tag() isnt ')'
     i = @tokens.length
     while tok = @tokens[--i]
       switch tok[0]
         when 'IDENTIFIER'       then tok[0] = 'PARAM'
         when ')'                then tok[0] = 'PARAM_END'
         when '(', 'CALL_START'  then tok[0] = 'PARAM_START'; return true
-    true
+    this

   # Close up all remaining open blocks at the end of the file.
   closeIndentation: ->


updating the Lexer to use classes and some of the older documentation

--- lexer.coffee
+++ lexer.coffee
@@ -3,11 +3,6 @@
 else
   this.exports: this
   Rewriter: this.Rewriter
-
-# The lexer reads a stream of CoffeeScript and divvys it up into tagged
-# tokens. A minor bit of the ambiguity in the grammar has been avoided by
-# pushing some extra smarts into the Lexer.
-exports.Lexer: lex: ->

 # Constants ============================================================

@@ -86,241 +81,246 @@
 # Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
 BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']

-# Scan by attempting to match tokens one character at a time. Slow and steady.
-lex::tokenize: (code) ->
-  @code    : code       # Cleanup code by remove extra line breaks, TODO: chomp
-  @i       : 0          # Current character position we're parsing
-  @line    : 1          # The current line.
-  @indent  : 0          # The current indent level.
-  @indents : []         # The stack of all indent levels we are currently within.
-  @tokens  : []         # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
-  while @i < @code.length
-    @chunk: @code.slice(@i)
-    @extract_next_token()
-  @close_indentation()
-  (new Rewriter()).rewrite @tokens
-
-# At every position, run through this list of attempted matches,
-# short-circuiting if any of them succeed.
-lex::extract_next_token: ->
-  return if @identifier_token()
-  return if @number_token()
-  return if @heredoc_token()
-  return if @string_token()
-  return if @js_token()
-  return if @regex_token()
-  return if @indent_token()
-  return if @comment_token()
-  return if @whitespace_token()
-  return    @literal_token()
-
-# Tokenizers ==========================================================
-
-# Matches identifying literals: variables, keywords, method names, etc.
-lex::identifier_token: ->
-  return false unless id: @match IDENTIFIER, 1
-  @tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
-  if @value() is '.' and not (@value(2) is '.')
-    if @tag(2) is '?'
-      @tag(1, 'SOAK_ACCESS')
-      @tokens.splice(-2, 1)
+# The lexer reads a stream of CoffeeScript and divvys it up into tagged
+# tokens. A minor bit of the ambiguity in the grammar has been avoided by
+# pushing some extra smarts into the Lexer.
+exports.Lexer: class Lexer
+
+  # Scan by attempting to match tokens one character at a time. Slow and steady.
+  tokenize: (code) ->
+    @code    : code       # Cleanup code by remove extra line breaks, TODO: chomp
+    @i       : 0          # Current character position we're parsing
+    @line    : 1          # The current line.
+    @indent  : 0          # The current indent level.
+    @indents : []         # The stack of all indent levels we are currently within.
+    @tokens  : []         # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
+    while @i < @code.length
+      @chunk: @code.slice(@i)
+      @extract_next_token()
+    @close_indentation()
+    (new Rewriter()).rewrite @tokens
+
+  # At every position, run through this list of attempted matches,
+  # short-circuiting if any of them succeed.
+  extract_next_token: ->
+    return if @identifier_token()
+    return if @number_token()
+    return if @heredoc_token()
+    return if @string_token()
+    return if @js_token()
+    return if @regex_token()
+    return if @indent_token()
+    return if @comment_token()
+    return if @whitespace_token()
+    return    @literal_token()
+
+  # Tokenizers ==========================================================
+
+  # Matches identifying literals: variables, keywords, method names, etc.
+  identifier_token: ->
+    return false unless id: @match IDENTIFIER, 1
+    @tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
+    if @value() is '.' and not (@value(2) is '.')
+      if @tag(2) is '?'
+        @tag(1, 'SOAK_ACCESS')
+        @tokens.splice(-2, 1)
+      else
+        @tag(1, 'PROPERTY_ACCESS')
+    tag: 'IDENTIFIER'
+    tag:  id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
+      not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
+    throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
+    tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
+    @token(tag, id)
+    @i += id.length
+    true
+
+  # Matches numbers, including decimals, hex, and exponential notation.
+  number_token: ->
+    return false unless number: @match NUMBER, 1
+    @token 'NUMBER', number
+    @i += number.length
+    true
+
+  # Matches strings, including multi-line strings.
+  string_token: ->
+    return false unless string: @match STRING, 1
+    escaped: string.replace STRING_NEWLINES, " \\\n"
+    @token 'STRING', escaped
+    @line += @count string, "\n"
+    @i += string.length
+    true
+
+  # Matches heredocs, adjusting indentation to the correct level.
+  heredoc_token: ->
+    return false unless match = @chunk.match(HEREDOC)
+    doc: match[2] or match[4]
+    indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
+    doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
+            .replace(MULTILINER, "\\n")
+            .replace('"', '\\"')
+    @token 'STRING', '"' + doc + '"'
+    @line += @count match[1], "\n"
+    @i += match[1].length
+    true
+
+  # Matches interpolated JavaScript.
+  js_token: ->
+    return false unless script: @match JS, 1
+    @token 'JS', script.replace(JS_CLEANER, '')
+    @i += script.length
+    true
+
+  # Matches regular expression literals.
+  regex_token: ->
+    return false unless regex: @match REGEX, 1
+    return false if NOT_REGEX.indexOf(@tag()) >= 0
+    @token 'REGEX', regex
+    @i += regex.length
+    true
+
+  # Matches and conumes comments.
+  comment_token: ->
+    return false unless comment: @match COMMENT, 1
+    @line += (comment.match(MULTILINER) or []).length
+    @token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
+    @token 'TERMINATOR', "\n"
+    @i += comment.length
+    true
+
+  # Record tokens for indentation differing from the previous line.
+  indent_token: ->
+    return false unless indent: @match MULTI_DENT, 1
+    @line += indent.match(MULTILINER).length
+    @i    += indent.length
+    next_character: @chunk.match(MULTI_DENT)[4]
+    prev: @prev(2)
+    no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
+    return @suppress_newlines(indent) if no_newlines
+    size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
+    return @newline_token(indent) if size is @indent
+    if size > @indent
+      diff: size - @indent
+      @token 'INDENT', diff
+      @indents.push diff
     else
-      @tag(1, 'PROPERTY_ACCESS')
-  tag: 'IDENTIFIER'
-  tag:  id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
-    not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
-  throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
-  tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
-  @token(tag, id)
-  @i += id.length
-  true
-
-# Matches numbers, including decimals, hex, and exponential notation.
-lex::number_token: ->
-  return false unless number: @match NUMBER, 1
-  @token 'NUMBER', number
-  @i += number.length
-  true
-
-# Matches strings, including multi-line strings.
-lex::string_token: ->
-  return false unless string: @match STRING, 1
-  escaped: string.replace STRING_NEWLINES, " \\\n"
-  @token 'STRING', escaped
-  @line += @count string, "\n"
-  @i += string.length
-  true
-
-# Matches heredocs, adjusting indentation to the correct level.
-lex::heredoc_token: ->
-  return false unless match = @chunk.match(HEREDOC)
-  doc: match[2] or match[4]
-  indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
-  doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
-          .replace(MULTILINER, "\\n")
-          .replace('"', '\\"')
-  @token 'STRING', '"' + doc + '"'
-  @line += @count match[1], "\n"
-  @i += match[1].length
-  true
-
-# Matches interpolated JavaScript.
-lex::js_token: ->
-  return false unless script: @match JS, 1
-  @token 'JS', script.replace(JS_CLEANER, '')
-  @i += script.length
-  true
-
-# Matches regular expression literals.
-lex::regex_token: ->
-  return false unless regex: @match REGEX, 1
-  return false if NOT_REGEX.indexOf(@tag()) >= 0
-  @token 'REGEX', regex
-  @i += regex.length
-  true
-
-# Matches and conumes comments.
-lex::comment_token: ->
-  return false unless comment: @match COMMENT, 1
-  @line += (comment.match(MULTILINER) or []).length
-  @token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
-  @token 'TERMINATOR', "\n"
-  @i += comment.length
-  true
-
-# Record tokens for indentation differing from the previous line.
-lex::indent_token: ->
-  return false unless indent: @match MULTI_DENT, 1
-  @line += indent.match(MULTILINER).length
-  @i    += indent.length
-  next_character: @chunk.match(MULTI_DENT)[4]
-  prev: @prev(2)
-  no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
-  return @suppress_newlines(indent) if no_newlines
-  size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
-  return @newline_token(indent) if size is @indent
-  if size > @indent
-    diff: size - @indent
-    @token 'INDENT', diff
-    @indents.push diff
-  else
-    @outdent_token @indent - size
-  @indent: size
-  true
-
-# Record an oudent token or tokens, if we're moving back inwards past
-# multiple recorded indents.
-lex::outdent_token: (move_out) ->
-  while move_out > 0 and @indents.length
-    last_indent: @indents.pop()
-    @token 'OUTDENT', last_indent
-    move_out -= last_indent
-  @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
-  true
-
-# Matches and consumes non-meaningful whitespace.
-lex::whitespace_token: ->
-  return false unless space: @match WHITESPACE, 1
-  prev: @prev()
-  prev.spaced: true if prev
-  @i += space.length
-  true
-
-# Multiple newlines get merged together.
-# Use a trailing \ to escape newlines.
-lex::newline_token: (newlines) ->
-  @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
-  true
-
-# Tokens to explicitly escape newlines are removed once their job is done.
-lex::suppress_newlines: (newlines) ->
-  @tokens.pop() if @value() is "\\"
-  true
-
-# We treat all other single characters as a token. Eg.: ( ) , . !
-# Multi-character operators are also literal tokens, so that Racc can assign
-# the proper order of operations.
-lex::literal_token: ->
-  match: @chunk.match(OPERATOR)
-  value: match and match[1]
-  @tag_parameters() if value and value.match(CODE)
-  value ||= @chunk.substr(0, 1)
-  not_spaced: not @prev() or not @prev().spaced
-  tag: value
-  if value.match(ASSIGNMENT)
-    tag: 'ASSIGN'
-    throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
-  else if value is ';'
-    tag: 'TERMINATOR'
-  else if value is '[' and @tag() is '?' and not_spaced
-    tag: 'SOAKED_INDEX_START'
-    @soaked_index: true
-    @tokens.pop()
-  else if value is ']' and @soaked_index
-    tag: 'SOAKED_INDEX_END'
-    @soaked_index: false
-  else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
-    tag: 'CALL_START'  if value is '('
-    tag: 'INDEX_START' if value is '['
-  @token tag, value
-  @i += value.length
-  true
-
-# Helpers =============================================================
-
-# Add a token to the results, taking note of the line number.
-lex::token: (tag, value) ->
-  @tokens.push([tag, value, @line])
-
-# Look at a tag in the current token stream.
-lex::tag: (index, tag) ->
-  return unless tok: @prev(index)
-  return tok[0]: tag if tag?
-  tok[0]
-
-# Look at a value in the current token stream.
-lex::value: (index, val) ->
-  return unless tok: @prev(index)
-  return tok[1]: val if val?
-  tok[1]
-
-# Look at a previous token.
-lex::prev: (index) ->
-  @tokens[@tokens.length - (index or 1)]
-
-# Count the occurences of a character in a string.
-lex::count: (string, letter) ->
-  num: 0
-  pos: string.indexOf(letter)
-  while pos isnt -1
-    num += 1
-    pos: string.indexOf(letter, pos + 1)
-  num
-
-# Attempt to match a string against the current chunk, returning the indexed
-# match.
-lex::match: (regex, index) ->
-  return false unless m: @chunk.match(regex)
-  if m then m[index] else false
-
-# A source of ambiguity in our grammar was parameter lists in function
-# definitions (as opposed to argument lists in function calls). Tag
-# parameter identifiers in order to avoid this. Also, parameter lists can
-# make use of splats.
-lex::tag_parameters: ->
-  return if @tag() isnt ')'
-  i: 0
-  while true
-    i += 1
-    tok: @prev(i)
-    return if not tok
-    switch tok[0]
-      when 'IDENTIFIER' then tok[0]: 'PARAM'
-      when ')'          then tok[0]: 'PARAM_END'
-      when '('          then return tok[0]: 'PARAM_START'
-  true
-
-# Close up all remaining open blocks. IF the first token is an indent,
-# axe it.
-lex::close_indentation: ->
-  @outdent_token(@indent)
+      @outdent_token @indent - size
+    @indent: size
+    true
+
+  # Record an oudent token or tokens, if we're moving back inwards past
+  # multiple recorded indents.
+  outdent_token: (move_out) ->
+    while move_out > 0 and @indents.length
+      last_indent: @indents.pop()
+      @token 'OUTDENT', last_indent
+      move_out -= last_indent
+    @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
+    true
+
+  # Matches and consumes non-meaningful whitespace.
+  whitespace_token: ->
+    return false unless space: @match WHITESPACE, 1
+    prev: @prev()
+    prev.spaced: true if prev
+    @i += space.length
+    true
+
+  # Multiple newlines get merged together.
+  # Use a trailing \ to escape newlines.
+  newline_token: (newlines) ->
+    @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
+    true
+
+  # Tokens to explicitly escape newlines are removed once their job is done.
+  suppress_newlines: (newlines) ->
+    @tokens.pop() if @value() is "\\"
+    true
+
+  # We treat all other single characters as a token. Eg.: ( ) , . !
+  # Multi-character operators are also literal tokens, so that Racc can assign
+  # the proper order of operations.
+  literal_token: ->
+    match: @chunk.match(OPERATOR)
+    value: match and match[1]
+    @tag_parameters() if value and value.match(CODE)
+    value ||= @chunk.substr(0, 1)
+    not_spaced: not @prev() or not @prev().spaced
+    tag: value
+    if value.match(ASSIGNMENT)
+      tag: 'ASSIGN'
+      throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
+    else if value is ';'
+      tag: 'TERMINATOR'
+    else if value is '[' and @tag() is '?' and not_spaced
+      tag: 'SOAKED_INDEX_START'
+      @soaked_index: true
+      @tokens.pop()
+    else if value is ']' and @soaked_index
+      tag: 'SOAKED_INDEX_END'
+      @soaked_index: false
+    else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
+      tag: 'CALL_START'  if value is '('
+      tag: 'INDEX_START' if value is '['
+    @token tag, value
+    @i += value.length
+    true
+
+  # Helpers =============================================================
+
+  # Add a token to the results, taking note of the line number.
+  token: (tag, value) ->
+    @tokens.push([tag, value, @line])
+
+  # Look at a tag in the current token stream.
+  tag: (index, tag) ->
+    return unless tok: @prev(index)
+    return tok[0]: tag if tag?
+    tok[0]
+
+  # Look at a value in the current token stream.
+  value: (index, val) ->
+    return unless tok: @prev(index)
+    return tok[1]: val if val?
+    tok[1]
+
+  # Look at a previous token.
+  prev: (index) ->
+    @tokens[@tokens.length - (index or 1)]
+
+  # Count the occurences of a character in a string.
+  count: (string, letter) ->
+    num: 0
+    pos: string.indexOf(letter)
+    while pos isnt -1
+      num += 1
+      pos: string.indexOf(letter, pos + 1)
+    num
+
+  # Attempt to match a string against the current chunk, returning the indexed
+  # match.
+  match: (regex, index) ->
+    return false unless m: @chunk.match(regex)
+    if m then m[index] else false
+
+  # A source of ambiguity in our grammar was parameter lists in function
+  # definitions (as opposed to argument lists in function calls). Tag
+  # parameter identifiers in order to avoid this. Also, parameter lists can
+  # make use of splats.
+  tag_parameters: ->
+    return if @tag() isnt ')'
+    i: 0
+    while true
+      i += 1
+      tok: @prev(i)
+      return if not tok
+      switch tok[0]
+        when 'IDENTIFIER' then tok[0]: 'PARAM'
+        when ')'          then tok[0]: 'PARAM_END'
+        when '('          then return tok[0]: 'PARAM_START'
+    true
+
+  # Close up all remaining open blocks. IF the first token is an indent,
+  # axe it.
+  close_indentation: ->
+    @outdent_token(@indent)


lexer: improved logics

lexer: improved logics

--- lexer.coffee
+++ lexer.coffee
@@ -43,8 +43,7 @@
     @outdebt = 0            # The under-outdentation at the current level.
     @indents = []           # The stack of all current indentation levels.
     @tokens  = []           # Stream of parsed tokens in the form ['TYPE', value, line]
-    while @i < @code.length
-      @chunk = @code[@i..]
+    while (@chunk = code[@i..])
       @extractNextToken()
     @closeIndentation()
     return @tokens if o.rewrite is off
@@ -54,16 +53,16 @@
   # short-circuiting if any of them succeed. Their order determines precedence:
   # `@literalToken` is the fallback catch-all.
   extractNextToken: ->
-    return if @identifierToken()
-    return if @commentToken()
-    return if @whitespaceToken()
-    return if @lineToken()
-    return if @heredocToken()
-    return if @stringToken()
-    return if @numberToken()
-    return if @regexToken()
-    return if @jsToken()
-    return    @literalToken()
+    @identifierToken() or
+    @commentToken()    or
+    @whitespaceToken() or
+    @lineToken()       or
+    @heredocToken()    or
+    @stringToken()     or
+    @numberToken()     or
+    @regexToken()      or
+    @jsToken()         or
+    @literalToken()

   # Tokenizers
   # ----------
@@ -79,11 +78,15 @@
     @i += id.length
     forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1
     tag = 'IDENTIFIER'
-    tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
-    tag = 'LEADING_WHEN'   if tag is 'WHEN' and include LINE_BREAK, @tag()
-    tag = 'ALL'            if id is 'all' and @tag() is 'FOR'
-    tag = 'UNARY'          if include UNARY, tag
-    if include(JS_FORBIDDEN, id)
+    if include(JS_KEYWORDS, id) or
+       not forcedIdentifier and include(COFFEE_KEYWORDS, id)
+      tag = id.toUpperCase()
+      tag = 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag()
+    else if id is 'all' and @tag() is 'FOR'
+      tag = 'ALL'
+    if include UNARY, tag
+      tag = 'UNARY'
+    else if include JS_FORBIDDEN, id
       if forcedIdentifier
         tag = 'STRING'
         id  = "\"#{id}\""
@@ -95,8 +98,10 @@
         @identifierError id
     unless forcedIdentifier
       tag = id = CONVERSIONS[id] if include COFFEE_ALIASES, id
-      tag = 'LOGIC' if include LOGIC, id
-      tag = 'UNARY' if id is '!'
+      if id is '!'
+        tag = 'UNARY'
+      else if include LOGIC, id
+        tag = 'LOGIC'
     @token tag, id
     @token ']', ']' if close_index
     true
@@ -177,7 +182,7 @@
     @i += regex.length + flags.length
     true

-  # Matches a token in which which the passed delimiter pairs must be correctly
+  # Matches a token in which the passed delimiter pairs must be correctly
   # balanced (ie. strings, JS literals).
   balancedToken: (delimited...) ->
     @balancedString @chunk, delimited
@@ -292,8 +297,9 @@
         tag = 'CALL_START'
       else if value is '['
         tag = 'INDEX_START'
-        @tag 1, 'INDEX_SOAK'  if @tag() is '?'
-        @tag 1, 'INDEX_PROTO' if @tag() is '::'
+        switch @tag()
+          when '?'  then @tag 1, 'INDEX_SOAK'
+          when '::' then @tag 1, 'INDEX_PROTO'
     @token tag, value
     true

@@ -307,7 +313,7 @@
     return false if (not prev = @prev()) or (prev and prev.spaced)
     accessor = if prev[1] is '::'
       @tag 1, 'PROTOTYPE_ACCESS'
-    else if prev[1] is '.' and not (@value(2) is '.')
+    else if prev[1] is '.' and @value(2) isnt '.'
       if @tag(2) is '?'
         @tag(1, 'SOAK_ACCESS')
         @tokens.splice(-2, 1)


fixing the lexer so that leading commas surpress newlines

--- lexer.coffee
+++ lexer.coffee
@@ -202,8 +202,8 @@
     @i   : + indent.length
     prev: @prev(2)
     size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
-    next_character: @chunk.match(MULTI_DENT)[4]
-    no_newlines: next_character is '.' or @unfinished()
+    next_character: @chunk.match(NEXT_CHARACTER)[1]
+    no_newlines: next_character is '.' or next_character is ',' or @unfinished()
     if size is @indent
       return @suppress_newlines() if no_newlines
       return @newline_token indent
@@ -511,6 +511,7 @@
 NO_NEWLINE      : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
 HEREDOC_INDENT  : /(\n+([ \t]*)|^([ \t]+))/g
 ASSIGNED        : /^([a-zA-Z\$_]\w*[ \t]*?[:=])/
+NEXT_CHARACTER  : /^\s*(\S)/

 # Tokens which a regular expression will never immediately follow, but which
 # a division operator might.


Log page load time for debugging

Log page load time for debugging

Using Navigation Timing API: http://www.w3.org/TR/navigation-timing/#sec-navigation-timing-interface

--- index.coffee
+++ index.coffee
@@ -28,6 +28,10 @@

 module.exports = () ->
   console.log 'voxpopuli starting'
+
+  if window.performance && window.performance.timing
+    loadingTime = Date.now() - window.performance.timing.navigationStart
+    console.log "User-perceived page loading time: #{loadingTime / 1000}s"

   # setup the game
   console.log 'creating game'


cleanup

cleanup

--- index.coffee
+++ index.coffee
@@ -98,7 +98,6 @@
   ).end accessTokenCallback(req, done)

 facebookCallback = (req, token, refreshToken, profile, done) ->
-  console.log 'CALLBACK', token, refreshToken, req.url
   if req.user
     request.post(
       "#{opts.SECURE_ARTSY_URL}/api/v1/me/authentications/facebook"


Whitespace

Whitespace

--- index.coffee
+++ index.coffee
@@ -14,13 +14,13 @@
 		inViewportOnce:
 			type: Boolean
 			default: false
-
-		# The IntersectionObserver root margin adds offsets to when the now and
+
+		# The IntersectionObserver root margin adds offsets to when the now and
 		# fully get updated.
-		inViewportRootMargin:
+		inViewportRootMargin:
 			type: Number|String
 			default: '0px 0px -1px 0px'
-
+
 		# Specify the IntersectionObserver root to use.
 		inViewportRoot:
 			type: String|Function|Object
@@ -28,10 +28,10 @@

 		# The IntersectionObserver threshold defines the intersection ratios that
 		# fire the observer callback
-		inViewportThreshold:
+		inViewportThreshold:
 			type: Number|Array
 			default: -> [0, 1] # Fire on enter/leave and fully enter/leave
-
+
 	# Bindings that are used by the host component
 	data: -> inViewport:

@@ -50,17 +50,17 @@
 	destroyed: -> @removeInViewportHandlers()

 	computed:
-
+
 		# Add the maxThreshold to the @inViewportThreshold prop so that the handler
 		# is fired for elements that are talled than the viewport
 		inViewportThresholdWithMax: ->
-
+
 			# Support number and array thresholds
-			threshold =
+			threshold =
 				if typeof @inViewportThreshold == 'object'
 				then @inViewportThreshold
 				else [ @inViewportThreshold ]
-
+
 			# Add only if not already in the threshold list
 			if @inViewport.maxThreshold in threshold
 			then threshold
@@ -78,8 +78,8 @@
 		# If any of the Observer options change, re-init.
 		inViewportRootMargin: -> @reInitInViewportMixin()
 		inViewportRoot: -> @reInitInViewportMixin()
-		inViewportThresholdWithMax: (now, old) ->
-
+		inViewportThresholdWithMax: (now, old) ->
+
 			# In IE, this is kept getting retriggered, to doing a manual comparison
 			# of old and new before deciding whether to take action.
 			@reInitInViewportMixin() unless now.toString() == old.toString()
@@ -111,17 +111,17 @@
 					else undefined
 				rootMargin: @inViewportRootMargin
 				threshold: @inViewportThresholdWithMax
-
+
 			# Start listening
 			@inViewportObserver.observe @$el
-
+
 		# Remove listeners
 		removeInViewportHandlers: ->

 			# Don't remove twice
 			return unless @inViewport.listening
 			@inViewport.listening = false
-
+
 			# Destroy instance, which also removes listeners
 			@inViewportObserver?.disconnect()
 			delete @inViewportObserver
@@ -132,11 +132,11 @@
 				boundingClientRect: target,
 				rootBounds: root
 			}]) ->
-
+
 			# Get the maximum threshold ratio, which is less than 1 when the
 			# element is taller than the viewport.
 			@inViewport.maxThreshold = Math.min 1, root.height / target.height
-
+
 			# Check if some part of the target is in the root box.  The isIntersecting
 			# property from the IntersectionObserver was not used because it reports
 			# the case where a box is immediately offscreen as intersecting, even
@@ -147,12 +147,12 @@
 			# the default root-margin which has a -1 on the bottom margin.
 			@inViewport.above = target.top < root.top
 			@inViewport.below = target.bottom > root.bottom + 1
-
+
 			# Determine whether fully in viewport. The rules are different based on
 			# whether the target is taller than the viewport.
 			@inViewport.fully = if target.height > root.height
 			then target.top <= root.top and target.bottom >= root.bottom + 1
 			else not @inViewport.above and not @inViewport.below
-
+
 			# If set to update "once", remove listeners if in viewport
 			@removeInViewportHandlers() if @inViewportOnce and @inViewport.now


setup cache

setup cache

--- index.coffee
+++ index.coffee
@@ -5,16 +5,18 @@
 #
 express = require "express"
 setup = require "./lib/setup"
+cache = require './lib/cache'
 { RESTART_INTERVAL } = require "./config"

 app = module.exports = express()
-setup app

-# Start the server and send a message to IPC for the integration test
-# helper to hook into.
-app.listen process.env.PORT, ->
-  console.log "Listening on port " + process.env.PORT
-  process.send? "listening"
+cache.setup ->
+  setup app
+
+  app.listen process.env.PORT, ->
+    console.log "Listening on port " + process.env.PORT
+    process.send? "listening"
+

 # Reboot for memory leak
 setTimeout process.exit, RESTART_INTERVAL


uncaughtException handler.

uncaughtException handler.

--- index.coffee
+++ index.coffee
@@ -1,6 +1,13 @@
 async = require 'async'
 {program, client, delay, handleError, runJsFile, runJs, exit} = require './common'
 PreviousStatementsDownloader = require './previous-statements-downloader'
+
+process.on 'uncaughtException', (err) ->
+  console.error "Uncaught exception occurred!"
+  console.error "ERROR: #{err.message}"
+  console.error err.stack
+  console.error require('util').inspect err, {colors: true}
+  console.error "Exiting."

 # For if you're using the REPL
 myRepl = null


add create/delete

add create/delete

--- index.coffee
+++ index.coffee
@@ -16,6 +16,8 @@
       'remove-device': @removeDevice
       'nodered-instance-start': @addDevice
       'nodered-instance-stop': @removeDevice
+      'create': @addDevice
+      'delete': @removeDevice

   run: =>
     @connection = meshblu.createConnection _.cloneDeep(@meshbluOptions)


added two more debugs

added two more debugs

--- index.coffee
+++ index.coffee
@@ -97,9 +97,11 @@
       return @emit "message", result

   onConfig: (device) =>
+    debug "Options", device.options
     @setOptions device.options

   setOptions: (options={}) =>
+    debug "Set Options", options
     @options = options

 module.exports =


Load voxel-furnace plugin

Load voxel-furnace plugin

--- index.coffee
+++ index.coffee
@@ -6,6 +6,7 @@
 require 'voxel-recipes'
 require 'voxel-webview'
 require 'voxel-workbench'
+require 'voxel-furnace'
 require 'voxel-chest'
 require 'voxel-inventory-hotbar'
 require 'voxel-inventory-dialog'
@@ -109,6 +110,7 @@
     'voxel-blockdata': {}
     'voxel-chest': {}
     'voxel-workbench': {}
+    'voxel-furnace': {}
     'voxel-pickaxe': {}
     'voxel-daylight': {ambientColor: 0x888888, directionalColor: 0xffffff}


basic in memory cache to reduce load on poor play api

--- server/authentication/strategies/play.coffee
+++ server/authentication/strategies/play.coffee
@@ -2,6 +2,7 @@
 util = require 'util'
 http = require 'http'
 _ = require 'underscore'
+moment = require 'moment'

 Strategy = (options, verify) ->
   if (typeof options == 'function')
@@ -21,6 +22,8 @@

 # Inherit from `passport.Strategy`.
 util.inherits Strategy, passport.Strategy
+
+cache = {}

 Strategy::authenticate = (req) ->
   if req.headers.cookie?
@@ -51,20 +54,35 @@
           @success user, info

       that = @
-      playRequest = http.request playRequestOptions, (res) ->
+      isValidatedInCache = false

-        if res.statusCode is 200
-          if that._passReqToCallback
-            that._verify req, userId, systemId, verified
+      if cache[userId]?
+        now = moment()
+        if moment(cache[userId]).isAfter(now)
+          isValidatedInCache = true
+
+      if isValidatedInCache
+        if that._passReqToCallback
+          that._verify req, userId, systemId, verified
+        else
+          that._verify userId, systemId, verified
+      else
+
+        playRequest = http.request playRequestOptions, (res) ->
+
+          if res.statusCode is 200
+            cache[userId] = moment().add('days', 1)
+            if that._passReqToCallback
+              that._verify req, userId, systemId, verified
+            else
+              that._verify userId, systemId, verified
           else
-            that._verify userId, systemId, verified
-        else
-          that.fail { message: 'Not Authorized'}
-
-      playRequest.on 'error', (e) ->
-        that.fail { message: 'problem with request: ' + e.message }
-
-      playRequest.end()
+            that.fail { message: 'Not Authorized'}
+
+        playRequest.on 'error', (e) ->
+          that.fail { message: 'problem with request: ' + e.message }
+
+        playRequest.end()

     else
       @fail 'Not signed into play'


auto pin mode

auto pin mode

--- grovepi.coffee
+++ grovepi.coffee
@@ -72,7 +72,7 @@
 		args = arguments
 		@mode port, MODES.output, => @send args...

-	receiveWithMode: (cmd, port) ->
+	receiveWithMode: (port) ->
 		args = arguments
 		@mode port, MODES.input, => @send args...


Use pull request URL for link to PRs

Use pull request URL for link to PRs

--- src/release-announce.coffee
+++ src/release-announce.coffee
@@ -85,7 +85,7 @@
             else
                 # construct email
                 base_issue_url = "https://github.com/#{repo}/issues"
-                pr_url = "#{base_issue_url}/#{pr}"
+                pr_url = "https://github.com/#{repo}/pull/#{pr}"
                 linked_body = pull.body.replace /#(\d+)/g, (match) ->
                     num = match.slice 1
                     link = "#{base_issue_url}/#{num}"


NEW: delay: (Seconds) option for animations

--- source/Art/Engine/Animation/EasingPersistantAnimator.coffee
+++ source/Art/Engine/Animation/EasingPersistantAnimator.coffee
@@ -14,14 +14,23 @@

 module.exports = class EasingPersistantAnimator extends PersistantAnimator

-  @getter "duration easingFunction"
+  @getter "duration easingFunction delay"

   @getter animationPos: ->
-    min 1, @getAnimationSeconds() / @_duration
+    s = @getAnimationSeconds()
+    {duration, delay} = @
+    if s <= delay
+      0
+    else
+      s -= delay
+      min 1, s / @_duration

   @setter
     duration: (d) ->
       @_duration = if isNumber(d) then max .001, d else .25
+
+    delay: (d) ->
+      @_delay = if isNumber(d) then max 0, d else 0

     easingFunction: (f) ->
       @_easingFunction = f
@@ -34,7 +43,7 @@
   constructor: (_, options = {}) ->
     super
     @setEasingFunction options.f || options.easingFunction
-    @setDuration if options.d? then options.d else options.duration
+    {d, @duration = d, @delay} = options

   animate: () ->
     {startValue, toValue, animationPos, easingFunction} = @


improve share link for image

improve share link for image

--- query_runner.coffee
+++ query_runner.coffee
@@ -83,11 +83,12 @@

   postImage: (query, imageData, options = {}) ->
     success = (url) =>
+      share = if @showShareUrl() then query.share_url else ""
       @reply(
         attachments: [
-          _.extend({}, options, {image_url: url})
-        ]
-        text: if @showShareUrl() then query.share_url else ""
+          _.extend({}, options, {image_url: url, title: share, title_link: share})
+        ]
+        text: ""
       )
     error = (error) =>
       @reply(":warning: #{error}")


Get rid of some old code.

Get rid of some old code.

--- keypress.coffee
+++ keypress.coffee
@@ -20,7 +20,6 @@
 ###

 ###
-
 Options available and defaults:
     keys            : []            - An array of the keys pressed together to activate combo
     count           : 0             - The number of times a counting combo has been pressed. Reset on release.
@@ -451,15 +450,6 @@
         unless key in _valid_keys
             _log_error "Do not recognize the key \"#{key}\""
             return false
-
-    # Make sure the combo isn't already registered
-    ###
-    for registered_combo in _registered_combos
-        if _compare_arrays combo.keys, registered_combo.keys
-            _log_error "Warning: we're overwriting another combo", combo.keys
-            _unregister_combo registered_combo
-            break
-    ###

     # We can only allow a single non-modifier key
     # in combos that include the command key (this


activity: XSS encode `TagContextMenuItem` view content

--- client/Main/CommonViews/tagcontextmenuitem.coffee
+++ client/Main/CommonViews/tagcontextmenuitem.coffee
@@ -5,9 +5,10 @@

   pistachio: ->
     {$suggest, $deleted} = @getData()
+
     if $suggest
-      """Suggest {{#($suggest)}} as a new topic?"""
+      """Suggest #{Encoder.XSSEncode $suggest} as a new topic?"""
     else if $deleted
-      """You can not tag your post with {{#($deleted)}}"""
+      """You can not tag your post with #{Encoder.XSSEncode $deleted}"""
     else
       "{{#(title)}}"


ComputeController: fetchStackTemplate: fetch from db each time

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1177,7 +1177,7 @@

   fetchStackTemplate: (id, callback = kd.noop) ->

-    remote.cacheable 'JStackTemplate', id, (err, template) ->
+    remote.api.JStackTemplate.one { _id: id }, (err, template) ->
       return callback err  if err

       # Follow update events to get change set from remote-extensions


ComputeController: fixMachinePermissions: fixes permission for provided machine by using kloud's addAdmin functionality

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1051,6 +1051,45 @@
     else @emit 'GroupStacksConsistent'

+  fixMachinePermissions: (machine, dontAskAgain = no) ->
+
+    { groupsController } = kd.singletons
+
+    # This is for admins only
+    return  unless groupsController.canEditGroup()
+
+    @ui.askFor 'permissionFix', { machine, dontAskAgain }, (state) =>
+
+      if state.dontAskAgain is yes
+
+        ignoredMachines = @storage.getValue('ignoredMachines') ? {}
+        ignoredMachines[machine.uid] = yes
+
+        @storage.setValue 'ignoredMachines', ignoredMachines
+
+        new kd.NotificationView
+          title    : "We won't bother you again for this machine"
+          duration : 5000
+          content  : "You can fix permissions anytime you want from
+                      settings panel of this machine."
+
+        return
+
+      return  if not state.confirmed
+
+      notification = new kd.NotificationView
+        title    : "Fixing permissions..."
+        duration : 15000
+
+      kloud = @getKloud()
+      kloud.addAdmin machineId: machine._id
+        .finally ->
+          notification.destroy()
+        .then (shared) ->
+          new kd.NotificationView title: 'Permissions fixed'
+        .catch (err) ->
+          showError err, 'Failed to fix permissions'
+
   verifyStackRequirements: (stack) ->

     unless stack


Add introspection message types.

Add introspection message types.

--- salvus/message.coffee
+++ salvus/message.coffee
@@ -82,6 +82,42 @@
     done         : false
     session_uuid : undefined
 )
+
+############################################
+# Session Introspection
+#############################################
+# An introspect message from the client can result in numerous types
+# of responses (but there will only be one response).  The id of the
+# message from hub back to client will match the id of the message
+# from client to hub; the client is responsible for deciding
+# what/where/how to deal with the message.
+
+# client --> hub
+message
+    event              : 'introspect'
+    id                 : undefined
+    text_before_cursor : required       # text before the cursor
+    text_after_cursor  : undefined      # optional text after the cursor
+
+# hub --> client (can be sent in response to introspect message)
+message
+    event       : 'introspection_completions'
+    id          : undefined   # match id of 'introspect' message
+    completions : required    # a list of strings (no HTML)
+
+# hub --> client  (can be sent in response to introspect message)
+message
+    event       : 'introspection_docstring'
+    id          : undefined
+    docstring   : required    # a string (no HTML), probably formatted using Sphinx/REST
+
+# hub --> client
+message
+    event       : 'introspection_source'
+    id          : undefined
+    docstring   : required    # a string
+
+

 ############################################
 # Ping/pong


LinkController: openOrFocus to provide referenced open feature for links

--- client/app/lib/linkcontroller.coffee
+++ client/app/lib/linkcontroller.coffee
@@ -1,14 +1,19 @@
-getGroup = require './util/getGroup'
 remote = require('./remote').getInstance()
 kd = require 'kd'
-KDController = kd.Controller
-module.exports = class LinkController extends KDController
+
+
+module.exports = class LinkController extends kd.Controller

   constructor: ->
+
     super
+
     @linkHandlers = {}
+    @windowReferences = {}
+

   handleLinkClick: (link) ->
+
     { JAccount, JGroup } = remote.api
     data = link.getData?()

@@ -26,12 +31,25 @@

     kd.getSingleton('router').handleRoute route, { state : data }  if route?

+
   registerLink: (link) ->
+
     id = link.getId()
     link.on 'LinkClicked', handler = => @handleLinkClick link
     @linkHandlers[id] = handler

+
   unregisterLink: (link) ->
+
     id = link.getId()
     link.off @linkHandlers[id]
     delete @linkHandlers[id]
+
+
+  openOrFocus: (url) ->
+
+    if reference = @windowReferences[url]
+      unless reference.closed
+        return reference.focus()
+
+    @windowReferences[url] = window.open url, '_blank'


another non-pushstate href

another non-pushstate href

--- client/code/view/set-password.coffee
+++ client/code/view/set-password.coffee
@@ -25,7 +25,7 @@
         type: 'POST'
         dataType: 'json'
         success: (profile) =>
-          @$el.children('form').html """
Thanks for setting your password. Click here to see your data!
"""
+          @$el.children('form').html """
Thanks for setting your password. Click here to see your data!
"""
         error: (jqxhr, textStatus, errorThrown) =>
           console.warn "#{textStatus}: #{errorThrown}"
           @$el.children('form').prepend """
Oh no! Something went wrong. Are you sure you clicked the right link?
"""


WIP: Rename dragged chips that have same name

--- client/src/app/util/dndHelper.coffee
+++ client/src/app/util/dndHelper.coffee
@@ -32,9 +32,28 @@
     sourceChipCollection = chips.getChipCollection(sourceGameCode, chipType)
     targetChipCollection = chips.getChipCollection(currentGame.version, chipType)

+    # Find new name
+    newChipName = if @isChipNameTaken(chipType, chipName) then @findChipName(chipType, chipName) else chipName
+
     # Copy over chip
-    targetChipCollection[chipName] = sourceChipCollection[chipName]
+    targetChipCollection[newChipName] = sourceChipCollection[chipName]
     currentGame.updateLocalVersion()
+
+  isChipNameTaken: (chipType, chipName) ->
+    chipCollection = chips.getChipCollection(currentGame.version, chipType)
+    return chipName of chipCollection
+
+  findChipName: (chipType, chipName) ->
+    chipCollection = chips.getChipCollection(currentGame.version, chipType)
+    return @findNewName(_.keys(chipCollection), chipName)
+
+  findNewName: (existingNames, currentName) ->
+    nextName = currentName
+    nextIndex = 2
+    while nextName in existingNames
+      nextName = "#{currentName} #{nextIndex}"
+      nextIndex++
+    return nextName

   getDependencies: (gameId, versionId, chip) ->
     getTransformerReferences: (code) ->


Handle cases where compatible frames for a model could be empty.

--- client/src/scripts/frame-view.coffee
+++ client/src/scripts/frame-view.coffee
@@ -7,7 +7,8 @@
         #TODO handle errors
       else
         #TODO typecheck
-        _compatibleModels createCompatibleModelsSection head data.frames
+        unless isEmpty data.frames
+          _compatibleModels createCompatibleModelsSection head data.frames
         return

   # Columns section


Fix the limit

Fix the limit

--- scripts/coffee.coffee
+++ scripts/coffee.coffee
@@ -14,7 +14,7 @@

 StatusEmoji = require('./status-emoji')

-dibsLimit = process.env.HUBOT_COFFEE_DIBS_LIMIT || 1
+dibsLimit = process.env.HUBOT_COFFEE_DIBS_LIMIT || 6

 freshPots = [
   "http://stream1.gifsoup.com/view6/3131142/chug-coffee-o.gif",


fixed the new connect directive

fixed the new connect directive

--- bokehselectize/selectizeselect.coffee
+++ bokehselectize/selectizeselect.coffee
@@ -50,8 +50,7 @@
     @_selectize = select[0].selectize;

     @render()
-    #@listenTo(@model, 'change', @update_value)
-    @connect(@model.properties.attr.change, @updateValue)
+    @connect(@model.change, @updateValue)

   get_options: () ->
     options_source = @model.options


remove whitespace

remove whitespace

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -1,7 +1,7 @@
 class KodingRouter extends KDRouter

   constructor:(@defaultRoute)->
-    @landingPageLoading = KD.config.groupEntryPoint?
+    @landingPageLoading = KD.config.groupEntryPoint?

     @openRoutes = {}
     @openRoutesById = {}


Client: Add damage % to summary.

Client: Add damage % to summary.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -386,7 +386,7 @@
       when 'MOVE_MISS', 'MOVE_FAIL', 'IMMUNITY'
         @addSummary(cannedText)
         setTimeout(done, 500)
-      when 'CRITICAL_HIT', 'SUPER_EFFECTIVE', 'NOT_VERY_EFFECTIVE'
+      when 'GOT_HIT', 'CRITICAL_HIT', 'SUPER_EFFECTIVE', 'NOT_VERY_EFFECTIVE'
         @addSummary(cannedText)
         done()
       else


Client: Speed up sprite changes.

Client: Speed up sprite changes.

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -464,11 +464,11 @@
       done()
       return

-    $sprite.fadeOut =>
+    $sprite.fadeOut 200, =>
       $sprite.popover('destroy')
       $sprite.remove()
       @renderPokemon $spriteContainer, ($image) ->
-        $image.hide().fadeIn(done)
+        $image.hide().fadeIn(200, done)

   changeWeather: (newWeather, done) =>
     $overlays = @$('.battle_overlays')


corrects more dumb mistakes

corrects more dumb mistakes

--- workers/vhostconfigurator/helpers.coffee
+++ workers/vhostconfigurator/helpers.coffee
@@ -1,6 +1,6 @@
 {exec} = require 'child_process'

-exports.error =(message)->
+exports.error = error = (message)->
   {message} = message if message.message?
   return no unless message
   console.log "There was an error: "


fix test name

fix test name

--- test/javascripts/component_url_test.coffee
+++ test/javascripts/component_url_test.coffee
@@ -1,6 +1,6 @@
 describe 'ComponentUrl', ->
   describe 'constructor', ->
-    it 'uses current location when given not given a url', ->
+    it 'uses current location when not given a url', ->
       url = new ComponentUrl()
       assert.equal(url.absolute, location.href)


remove notificationbox bar when unload addon

--- firefox/lib/notify.coffee
+++ firefox/lib/notify.coffee
@@ -14,16 +14,16 @@
 {setTimeout} = require('sdk/timers')
 NS_XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'

+notificationbox_id = 'xunlei-lixian-web-notificationbox'
 get_notification_box_in_status_bar = ->
 #	window_utils.getMostRecentBrowserWindow().gBrowser.getNotificationBox()
 	doc = window_utils.getMostRecentBrowserWindow().document
-	box_id = 'xunlei-lixian-web-notificationbox'
-	notification_box = doc.getElementById box_id
+	notification_box = doc.getElementById notificationbox_id
 	if notification_box?
 		return notification_box

 	notification_box = doc.createElementNS(NS_XUL, 'notificationbox')
-	notification_box.setAttribute 'id', box_id
+	notification_box.setAttribute 'id', notificationbox_id
 	notification_box.setAttribute 'dir', 'reverse'

 	addon_bar = doc.getElementById 'addon-bar'
@@ -74,4 +74,11 @@
 		message = message: message
 	notify_in_status_bar message

+
+require("sdk/system/unload").when ->
+	for w in window_utils.windows()
+		box = w.document.getElementById notificationbox_id
+		if box?
+			box.parentNode.removeChild box
+
 module.exports = notify


Remove needless connection to user pusher channel

--- components/layout/client.coffee
+++ components/layout/client.coffee
@@ -99,9 +99,6 @@
     pusher = new Pusher sd.PUSHER_KEY
     mediator.shared.pusher = pusher

-    if currentUser.id
-      mediator.shared.current_user_channel = pusher.subscribe "user_#{currentUser.id}"
-
 # TODO: Extract
 setupViews = ->
   # TODO: Fix all of these selectors


Tests: Pricing: Payment: Fixed checkPrices test.

--- client/test/lib/pricing/payment.coffee
+++ client/test/lib/pricing/payment.coffee
@@ -81,10 +81,14 @@
     developerSelector    = '.developer .plan-price cite'
     professionalSelector = '.professional .plan-price cite'
     buttonSelector       = '.interval-toggle button.clean-gray:not(.toggle)'
+    pricingSelector      = '.content-page.pricing section.plans'

     browser
       .url                    helpers.getUrl() + '/Pricing'
       .waitForElementVisible  '.content-page.pricing', 20000
+      .pause                  3000 # wait for pricing page load
+      .waitForElementVisible  pricingSelector,20000
+      .waitForElementVisible  hobbyistSelector, 20000
       .assert.containsText    hobbyistSelector, '9.95' # Assertion
       .assert.containsText    developerSelector, '19.95' # Assertion
       .assert.containsText    professionalSelector, '39.95' # Assertion


Set base path for assets in gulp 'assets' task

--- gulp/tasks/build/assets.coffee
+++ gulp/tasks/build/assets.coffee
@@ -16,7 +16,7 @@
     .pipe(gulp.dest(DESTINATIONS.assets))

 gulp.task 'assets:others', ->
-  gulp.src(PATHS.assets)
+  gulp.src(PATHS.assets, base: "assets")
     .pipe(changed(DESTINATIONS.assets))
     .pipe(gulp.dest(DESTINATIONS.assets))


Add ping command.

Add ping command.

--- core/htdocs_source/src/pods/openxpki/route.coffee
+++ core/htdocs_source/src/pods/openxpki/route.coffee
@@ -18,6 +18,7 @@

     source: Em.computed -> Em.Object.create
         page: null
+        ping: null
         refresh: null
         structure: null
         status: null
@@ -32,7 +33,7 @@
         model_id = req.params.openxpki.model_id

         if not source.get("navEntries.length") or model_id in @needReboot
-            @sendAjax data:
+            @sendAjax data:
                 page: "bootstrap!structure"
                 baseurl: window.location.pathname

@@ -57,6 +58,14 @@
             data: data
         .then (doc) ->
             source
+
+    doPing: (cfg) ->
+        @set "source.ping", Em.run.later(@, ->
+            Em.$.ajax
+                url: cfg.href
+            doPing cfg
+        , cfg.timeout)
+

     sendAjax: (req) ->
         req.dataType = "json"
@@ -85,6 +94,11 @@

                 source.set "status", doc.status
                 source.set "modal", null
+
+                if doc.ping
+                    if source.get "ping"
+                        Em.run.cancel source.get "ping"
+                    @doPing doc.ping

                 if doc.refresh
                     source.set "refresh", Em.run.later(@, ->


bug in authorization matcher

bug in authorization matcher

--- coffeescript/service/matchers.coffee
+++ coffeescript/service/matchers.coffee
@@ -84,9 +84,11 @@
   match: (input) ->
     if @value == "none"
       true
-    else
+    else if input
       scheme = input.split(" ")[0]
       scheme == @value
+    else
+      false

 class ContentTypeMatcher extends BasicMatcher
   # TODO: handle mediatypes in a better way than simple string match


terminal: minor comment

terminal: minor comment

--- client/app/lib/terminal/webtermview.coffee
+++ client/app/lib/terminal/webtermview.coffee
@@ -285,6 +285,9 @@
   triggerFitToWindow: ->

     return  unless @terminal?.server?
+    # bc a hidden terminal has 1 col and 1 row
+    # we assume that the terminal is hidden and do not trigger resize
+    # maybe, better would be to check if the dom element is in the body - SY
     return  if @terminal.sizeX + @terminal.sizeY <= 2

     @terminal.server.controlSequence String.fromCharCode 2


mixpanel: add helper functions to log to mixpanel

--- client/landing/app/MainApp/analytic.coffee
+++ client/landing/app/MainApp/analytic.coffee
@@ -5,6 +5,12 @@
     logToGoogle rest...
     KD.kdMixpanel.createEvent rest...

+  KD.mixpanel = (eventName, options)->
+    mixpanel.track eventName, options
+
+  KD.mixpanel.alias = (userId)->
+    mixpanel.alias userId
+
   logToGoogle = (rest...)->
     category = action = rest.first
     trackArray = ['_trackEvent', category, action]


Remove unused component

Remove unused component

--- components/PersistedToContext.coffee
+++ components/PersistedToContext.coffee
@@ -1,30 +0,0 @@
-noflo = require 'noflo'
-
-exports.getComponent = ->
-  c = new noflo.Component
-  c.inPorts.add 'entity',
-    datatype: 'object'
-  c.inPorts.add 'original',
-    datatype: 'object'
-  c.inPorts.add 'type',
-    datatype: 'string'
-    required: true
-  c.inPorts.add 'action',
-    datatype: 'string'
-    required: true
-  c.outPorts.add 'context',
-    datatype: 'object'
-
-  noflo.helpers.WirePattern c,
-    in: ['entity', 'original', 'type']
-    params: ['action']
-    out: 'context'
-    async: true
-  , (data, groups, out, callback) ->
-    out.send
-      state: 'ok'
-      persisted:
-        type: data.type
-        action: c.params.action
-        entity: data.original
-    do callback


Better explanation

Better explanation

--- railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
+++ railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.coffee
@@ -1,7 +1,11 @@
+# Action Cable provides the framework to deal with WebSockets in Rails.
+# You can generate new channels where WebSocket features live using the rails generate channel command.
+#
+# Turn on the cable connection by removing the comments after the require statements (and ensure it's also on in config/routes.rb).
+#
 #= require action_cable
 #= require_self
 #= require_tree ./channels
-
-# Turn on the cable connection (ensure it's also on in config/routes.rb):
+#
 # @App ||= {}
 # App.cable = ActionCable.createConsumer()


fix square_test

fix square_test

--- tests/coffee/primitives/square_test.coffee
+++ tests/coffee/primitives/square_test.coffee
@@ -1,7 +1,8 @@
 Collections = require('../../base').Collections
 make_glyph_test = require('../testutils').make_glyph_test

-range = Collections('Range1d').create({start: 0, end: 10})
+xrange = Collections('Range1d').create({start: 0, end: 10})
+yrange = Collections('Range1d').create({start: 0, end: 10})

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -25,7 +26,7 @@
   y: 'y'
 }

-test('square_glyph', make_glyph_test('square_glyph', data_source, defaults, glyph, range, range, null, null, null, null, null))
+test('square_glyph', make_glyph_test('square_glyph', data_source, defaults, glyph, xrange, yrange, {}))

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -48,8 +49,7 @@
   y: 'y'
 }

-test('square_glyph_angle', make_glyph_test('square_glyph_angle', data_source, defaults, glyph, range, range,
-))
+test('square_glyph_angle', make_glyph_test('square_glyph_angle', data_source, defaults, glyph, xrange, yrange, {}))

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -72,8 +72,7 @@
   y: 'y'
 }

-test('square_glyph_line_width', make_glyph_test('square_glyph_line_width', data_source, defaults, glyph, range, range, null, null, null, null,
-{x: 4, y: 2, lwidth:4}))
+test('square_glyph_line_width', make_glyph_test('square_glyph_line_width', data_source, defaults, glyph, xrange, yrange, {reference_point: {x: 4, y: 2, lwidth:4}}))

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -99,7 +98,7 @@
   y: 'y'
 }

-test('square_glyph_line_alpha', make_glyph_test('square_glyph_line_alpha', data_source, defaults, glyph, range, range))
+test('square_glyph_line_alpha', make_glyph_test('square_glyph_line_alpha', data_source, defaults, glyph, xrange, yrange, {}))

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -121,7 +120,7 @@
   y: 'y'
 }

-test('square_glyph_fill', make_glyph_test('square_glyph_fill', data_source, defaults, glyph, range, range))
+test('square_glyph_fill', make_glyph_test('square_glyph_fill', data_source, defaults, glyph, xrange, yrange, {}))

 data_source = Collections('ObjectArrayDataSource').create(
   data: [
@@ -147,4 +146,4 @@
   y: 'y'
 }

-test('square_glyph_linedash', make_glyph_test('square_glyph_linedash', data_source, defaults, glyph, range, range))
+test('square_glyph_linedash', make_glyph_test('square_glyph_linedash', data_source, defaults, glyph, xrange, yrange, {}))


removes flicker on new channel close

removes flicker on new channel close

--- components/layout/header/view.coffee
+++ components/layout/header/view.coffee
@@ -69,7 +69,9 @@
       @$('.dropdown--is_active').removeClass 'dropdown--is_active'

     ac = $el.toggleClass('dropdown--is_active')
-    $el.find('input').focus()
+
+    if $el.hasClass 'dropdown--is_active'
+      $el.find('input').focus()

   openAuth: (options) ->
     @modal = new AuthModalView _.extend({ width: '500px' }, options)


Update shell.coffee with initial repl rewrite

--- src/adapters/shell.coffee
+++ src/adapters/shell.coffee
@@ -1,29 +1,50 @@
-Robot   = require '../robot'
-Adapter = require '../adapter'
+Readline = require 'readline'
+
+Robot    = require '../robot'
+Adapter  = require '../adapter'

 class Shell extends Adapter
+  constructor: (robot) ->
+    super robot
+    @repl = null
+
   send: (user, strings...) ->
-    for str in strings
-      console.log str
+    console.log str for str in strings
+    @repl.prompt()

   reply: (user, strings...) ->
-    for str in strings
-      @send user, "#{user.name}: #{str}"
+    @send user, strings...

   run: ->
-    user = @userForId('1', { name: "Shell" })
+    stdin = process.openStdin()
+    stdout = process.stdout

-    process.stdin.resume()
-    process.stdin.on 'data', (txt) =>
-      txt.toString().split("\n").forEach (line) =>
-        return if line.length is 0
-        @receive new Robot.TextMessage user, line
+    process.on "uncaughtException", (err) =>
+      @robot.logger.error "#{err}"

-    setTimeout =>
-      user   = @userForId('1', { name: "Shell" })
-      atmos  = @userForId('2', { name: "atmos" })
-      holman = @userForId('3', { name: "Zach Holman" })
-    , 3000
+    if Readline.createInterface.length > 3
+      @repl = Readline.createInterface stdin, null
+
+      stdin.on "data", (buffer) =>
+        @repl.write buffer
+    else
+      @repl = Readline.createInterface stdin, stdout, null
+
+    @repl.on "attemptClose", =>
+      @repl.close()
+
+    @repl.on "close", =>
+      process.stdout.write "\n"
+      stdin.destroy()
+      process.exit 0
+
+    @repl.on "line", (buffer) =>
+      user = @userForId '1', name: "Shell"
+      @receive new Robot.TextMessage user, buffer
+      @repl.prompt()
+
+    @repl.setPrompt "#{@robot.name}> "
+    @repl.prompt()

 exports.use = (robot) ->
   new Shell robot


Cleanup: minor whitespace

Cleanup: minor whitespace

--- components/tabs/KDTabPaneView.coffee
+++ components/tabs/KDTabPaneView.coffee
@@ -1,4 +1,5 @@
 class KDTabPaneView extends KDView
+
   constructor:(options = {},data)->

     options.hiddenHandle ?= no      # a Boolean
@@ -13,28 +14,34 @@
     @on "KDTabPaneActive",        @bound "setMainView"
     @on "KDTabPaneLazyViewAdded", @bound "fireLazyCallback"

+
   show:->
+
+    @setClass "active"
     @unsetClass "kdhiddentab"
-    @setClass "active"
     @active = yes
     @emit "KDTabPaneActive"

+
   hide:->
+
+    @setClass "kdhiddentab"
     @unsetClass "active"
-    @setClass "kdhiddentab"
     @active = no
     @emit "KDTabPaneInactive"

+
   setTitle:(title)->
-    @getDelegate().setPaneTitle @,title
-    # @setOption "name", name
+
+    @getDelegate().setPaneTitle this, title
     @name = title

-  getHandle: ->
-    @getDelegate().getHandleByPane @

-  hideTabCloseIcon:->
-    @getDelegate().hideCloseIcon @
+  getHandle: -> @getDelegate().getHandleByPane this
+
+
+  hideTabCloseIcon:-> @getDelegate().hideCloseIcon this
+

   setMainView:(view)->

@@ -55,12 +62,18 @@
     @emit "KDTabPaneLazyViewAdded", this, @mainView
     return @mainView

+
   getMainView:-> @mainView
+
+
   destroyMainView:->
+
     @mainView.destroy()
     delete @mainView

+
   fireLazyCallback:(pane, view)->
+
     {viewOptions} = @getOptions()
     return  unless viewOptions
     {callback} = viewOptions


Add end_token and replacement rule options for replacements.

--- autocomplete-client.coffee
+++ autocomplete-client.coffee
@@ -255,9 +255,14 @@
     startpos = @element.selectionStart
     fullStuff = @getText()
     val = fullStuff.substring(0, startpos)
-    val = val.replace(@expressions[@matched], "$1" + @rules[@matched].token + replacement)
+
+    replace = if @rules[@matched].replacement != undefined then @rules[@matched].replacement else @rules[@matched].token
+
+    val = val.replace(@expressions[@matched], "$1" + replace + replacement)
     posfix = fullStuff.substring(startpos, fullStuff.length)
-    separator = (if posfix.match(/^\s/) then "" else " ")
+
+    separator = if @rules[@matched].end_token != undefined then @rules[@matched].end_token else (if posfix.match(/^\s/) then "" else " ")
+
     finalFight = val + separator + posfix
     @setText finalFight


Changing position of login indicator in network mode

--- src/main/webapp/lib/views/TreeView.coffee
+++ src/main/webapp/lib/views/TreeView.coffee
@@ -216,7 +216,7 @@
     $('#right_tree').css('width', '1%')
     $('#map_canvas').css('width', '99.2%')
     # change location of login indicator
-    $('.login-container').css('margin-left', '88%')
+    $('.login-container').css('margin-left', '87%')

   openTree: ->
     @treeOpen = true


Try to deal with auth redirect loops

Try to deal with auth redirect loops

--- controllers/auth.coffee
+++ controllers/auth.coffee
@@ -9,6 +9,8 @@
 exports.storeNext = (req, res, next) ->
   if req.query.next
     req.session.next = req.query.next
+  else
+    delete req.session.next

   next()

@@ -22,7 +24,7 @@
 exports.after = (req, res) ->
   if req.session.next
     next = req.session.next
-    delete req.session
+    delete req.session.next
     res.redirect next
   else
     res.redirect '/'


Change copy.

Change copy.

--- client/Dashboard/AppController.coffee
+++ client/Dashboard/AppController.coffee
@@ -76,7 +76,7 @@
           viewClass  : BadgeDashboardView
           lazy       : yes
       ,
-        name         : 'Customization'
+        name         : 'Widgets'
         hiddenHandle : @getData().privacy is 'public'
         kodingOnly   : yes # this is only intended for koding group, we assume koding group is super-group
         viewOptions  :


KodingKontrol: simplfy getVersion, remove not needed methods

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -41,15 +41,8 @@
     super (@injectQueryParams query), rest...

   getVersion: (name) ->
-    return '1.0.0'  unless name?
-    { os, terminal, klient, kloud } = KD.config.kites
-    # TODO: this could be more elegant:
-    {
-      oskite   : os.version
-      terminal : terminal.version
-      klient   : klient.version
-      kloud    : kloud.version
-    }[name] ? ''
+    return KD.config.kites[name].version ? '1.0.0'
+

   injectQueryParams: (args) ->

@@ -59,21 +52,15 @@

     return args

+
   getCachedKite: (name, correlationName) ->
     @kites[name]?[correlationName]
+

   setCachedKite: (name, correlationName, kite) ->
     @kites[name] ?= {}
     @kites[name][correlationName] = kite

-  hasKite: (options = {}) ->
-    { name, correlationName, region } = options
-    return (kite = @getCachedKite name, correlationName)?
-
-  getWhoParams: (kiteName, correlationName) ->
-    if kiteName in ['oskite', 'terminal']
-      return vmName: correlationName
-    return { correlationName }

   getKiteProxy: (options) ->


Activity: appview removed old deps

Activity: appview removed old deps

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -1,8 +1,4 @@
 class ActivityAppView extends KDScrollView
-
-  JView.mixin @prototype
-
-  headerHeight = 0

   {entryPoint, permissions, roles} = KD.config

@@ -31,7 +27,6 @@
       hideHandleContainer : yes

     @appStorage.setValue 'liveUpdates', off
-

   lazyLoadThresholdReached: -> @tabs.getActivePane()?.emit 'LazyLoadThresholdReached'


remove style tag in text version of composed message

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -14,7 +14,7 @@
 COMPOSE_STYLE = """
 <style>
 p {margin: 0;}
-pre {background: transparent; border: 0}^8
+pre {background: transparent; border: 0}
 </style>
 """

@@ -438,9 +438,11 @@

         # Convert HTML to markdown
         try
-            result = toMarkdown html
+            result = html.replace(/<(style>)[^\1]*\1/gim, '')
+            result = toMarkdown result
         catch
-            result = html.replace /<[^>]*>/gi, '' if html?
+            if html?
+                result = html.replace /<[^>]*>/gi, ''

         # convert HTML entities
         tmp = document.createElement 'div'


Gracefully handle signing up for a user that already exists

--- components/artist_page_cta/view.coffee
+++ components/artist_page_cta/view.coffee
@@ -50,6 +50,8 @@
       error: (model, response, options) =>
         @reenableForm()
         message = @errorMessage response
+        @$('button').attr 'data-state', 'error'
+        @$('.auth-errors').text message
         mediator.trigger 'auth:error', message

   onRegisterSuccess: (model, response, options) =>


enable/disable rotate and hand menu items

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -596,10 +596,10 @@
 	# HANDS

 	handSwitchingSupported: ->
-		return @_device.hands != undefined
+		return @_device.hands isnt undefined

 	nextHand: ->
-		return if @hands.rotationZ != 0
+		return if @hands.rotationZ isnt 0
 		if @handSwitchingSupported()
 			hands = _.keys(@_device.hands)
 			if hands.length > 0


Added new device dimensions

Added new device dimensions

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -724,6 +724,7 @@
 ###########################################################################
 # DEVICE CONFIGURATIONS

+iphoneXReleaseVersion = 105
 googlePixelReleaseVersion = 75
 desktopReleaseVersion = 70
 newDeviceMinVersion = 53
@@ -768,7 +769,7 @@
 	screenHeight: 2436
 	devicePixelRatio: 3
 	deviceType: "phone"
-	minStudioVersion: 105
+	minStudioVersion: iphoneXReleaseVersion
 	screenMask: "apple-iphone-x-mask.svg"
 	hands:
 		"iphone-hands-2":
@@ -780,6 +781,42 @@
 			height: 5559
 			offset: -15

+iPhone8BaseDevice =
+	deviceImageWidth: 871
+	deviceImageHeight: 1776
+	deviceImageCompression: true
+	screenWidth: 750
+	screenHeight: 1334
+	devicePixelRatio: 2
+	deviceType: "phone"
+	minStudioVersion: iphoneXReleaseVersion
+	hands:
+		"iphone-hands-2":
+			width: 2400
+			height: 3740
+		"iphone-hands-1":
+			width: 2400
+			height: 3740
+
+iPhone8PlusBaseDevice =
+	deviceImageWidth: 1436
+	deviceImageHeight: 2876
+	deviceImageCompression: true
+	screenWidth: 1242
+	screenHeight: 2208
+	devicePixelRatio: 3
+	deviceType: "phone"
+	minStudioVersion: iphoneXReleaseVersion
+	hands:
+		"iphone-hands-2":
+			width: 3949
+			height: 6192
+			offset: -15
+		"iphone-hands-1":
+			width: 3949
+			height: 6192
+			offset: -15
+
 iPhone7BaseDevice =
 	deviceImageWidth: 874
 	deviceImageHeight: 1792
@@ -964,6 +1001,43 @@
 			height: 5211
 			offset: 23

+Pixel2BaseDevice =
+	deviceImageWidth: 1210
+	deviceImageHeight: 2513
+	deviceImageCompression: true
+	screenWidth: 1080
+	screenHeight: 1920
+	devicePixelRatio: 2.627
+	deviceType: "phone"
+	minStudioVersion: iphoneXReleaseVersion
+	hands:
+		"iphone-hands-2":
+			width: 3320
+			height: 5178
+		"iphone-hands-1":
+			width: 3327
+			height: 5188
+
+Pixel2XLBaseDevice =
+	deviceImageWidth: 1840
+	deviceImageHeight: 3560
+	deviceImageCompression: true
+	screenWidth: 1440
+	screenHeight: 2880
+	# devicePixelRatio: 3.5
+	deviceType: "phone"
+	minStudioVersion: iphoneXReleaseVersion
+	screenMask: "google-pixel-2-xl-mask.svg"
+	hands:
+		"iphone-hands-2":
+			width: 4530
+			height: 7064
+		"iphone-hands-1":
+			width: 4530
+			height: 7064
+
+
+
 Nexus9BaseDevice =
 	deviceImageWidth: 1896
 	deviceImageHeight: 2648
@@ -1049,6 +1123,26 @@
 			width: 4279
 			height: 6668
 			offset: -84
+
+
+SamsungGalaxyS8BaseDevice =
+	deviceImageWidth: 1640
+	deviceImageHeight: 3600
+	deviceImageCompression: true
+	screenWidth: 1440
+	screenHeight: 2960
+	devicePixelRatio: 4
+	deviceType: "phone"
+	minStudioVersion: iphoneXReleaseVersion
+	screenMask: "samsung-galaxy-s8-mask.svg"
+	hands:
+		"iphone-hands-2":
+			width: 4219
+			height: 6573
+		"iphone-hands-1":
+			width: 4219
+			height: 6573
+

 AppleWatchSeries242Device =
 	deviceImageWidth: 512
@@ -1341,6 +1435,16 @@
 	# iPhone X
 	"apple-iphone-x-silver": _.clone(iPhoneXBaseDevice)
 	"apple-iphone-x-space-gray": _.clone(iPhoneXBaseDevice)
+
+	# iPhone 8
+	"apple-iphone-8-silver": _.clone(iPhone8BaseDevice)
+	"apple-iphone-8-gold": _.clone(iPhone8BaseDevice)
+	"apple-iphone-8-space-gray": _.clone(iPhone8BaseDevice)
+
+	# iPhone 8 Plus
+	"apple-iphone-8-plus-silver": _.clone(iPhone8PlusBaseDevice)
+	"apple-iphone-8-plus-gold": _.clone(iPhone8PlusBaseDevice)
+	"apple-iphone-8-plus-space-gray": _.clone(iPhone8PlusBaseDevice)

 	# iPhone 7
 	"apple-iphone-7-gold": _.clone(iPhone7BaseDevice)
@@ -1471,6 +1575,13 @@
 	"google-pixel-really-blue": _.clone(PixelBaseDevice)
 	"google-pixel-very-silver": _.clone(PixelBaseDevice)

+	# Pixel 2
+	"google-pixel-2-clearly-white": _.clone(Pixel2BaseDevice)
+	"google-pixel-2-just-black": _.clone(Pixel2BaseDevice)
+	"google-pixel-2-kinda-blue": _.clone(Pixel2BaseDevice)
+	"google-pixel-2-xl-black-and-white": _.clone(Pixel2XLBaseDevice)
+	"google-pixel-2-xl-just-black": _.clone(Pixel2XLBaseDevice)
+
 	# HTC ONE A9
 	"htc-one-a9-black": _.clone(HTCa9BaseDevice)
 	"htc-one-a9-white": _.clone(HTCa9BaseDevice)
@@ -1490,6 +1601,13 @@
 	"samsung-galaxy-note-5-pink": _.clone(SamsungGalaxyNote5BaseDevice)
 	"samsung-galaxy-note-5-silver-titanium": _.clone(SamsungGalaxyNote5BaseDevice)
 	"samsung-galaxy-note-5-white": _.clone(SamsungGalaxyNote5BaseDevice)
+
+	#Samsug Galaxy S8
+	"samsung-galaxy-s8-orchid-gray": _.clone(SamsungGalaxyS8BaseDevice)
+	"samsung-galaxy-s8-midnight-black": _.clone(SamsungGalaxyS8BaseDevice)
+	"samsung-galaxy-s8-maple-gold": _.clone(SamsungGalaxyS8BaseDevice)
+	"samsung-galaxy-s8-coral-blue": _.clone(SamsungGalaxyS8BaseDevice)
+	"samsung-galaxy-s8-arctic-silver": _.clone(SamsungGalaxyS8BaseDevice)

 	# Notebooks
 	"apple-macbook": _.clone(AppleMacBook)


Koding Provider: use provided region if possible

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -111,7 +111,7 @@

   @create = (client, options, callback)->

-    { instanceType, label, storage } = options
+    { instanceType, label, storage, region } = options
     { r: { group, user, account } } = client

     storage ?= 3
@@ -131,11 +131,14 @@
           if err = checkUsage usage, userPlan, storage
             return callback err

-          region = Regions.findRegion client.clientIP, SUPPORTED_REGIONS
+          region = null  unless region in SUPPORTED_REGIONS
+          region = region ? (
+            Regions.findRegion client.clientIP, SUPPORTED_REGIONS
+          ).regions[0]

           meta =
             type          : "amazon"
-            region        : region.regions[0] or "us-east-1"
+            region        : region ? "us-east-1"
             source_ami    : "ami-2651904e"
             instance_type : "t2.micro"
             storage_size  : storage


Remove debugger statement

Remove debugger statement

--- backbone.typeahead.coffee
+++ backbone.typeahead.coffee
@@ -119,7 +119,6 @@

     if event is "change:#{model.idAttribute}"
       add = true
-      debugger
       @_removeFromIndex id: model.previous(model.idAttribute)
     else if _.indexOf(_.map(@typeaheadAttributes, (att) -> 'change:' + att), event) >= 0
       add = true


service loggin

service loggin

--- lib/Vein.coffee
+++ lib/Vein.coffee
@@ -41,6 +41,7 @@
       socket.session = sess
       socket.write JSON.stringify id: 'session', params: [sess]
     socket.session = session
+    console.log method, JSON.stringify(params)
     @routes[method] send, socket, params...

 module.exports = Vein


Add all overlongs

Add all overlongs

--- test/28.coffee
+++ test/28.coffee
@@ -37,7 +37,8 @@
 #
 # Make overlong sequence from valid one
 #
-utf8.overlong = (buffer)->
+utf8.overlong =
+overlong = (buffer)->
   return unless valid buffer
   switch x = buffer.length
     when 1
@@ -51,3 +52,7 @@
   buffer[0] &= 0x7F + x
   buffer.unshift 0x100 - x
   buffer
+
+utf8.overlongs = (code)->
+  code = utf8 code if 'number'==typeof code
+  code while code = overlong code


support for loading multiple extensions

support for loading multiple extensions

--- init-nw.coffee
+++ init-nw.coffee
@@ -306,4 +306,6 @@
     $(window).on 'focus blur', repaintTitle = -> nww.resizeBy 0, 1; nww.resizeBy 0, -1; return
     D.setTitle = (s) -> document.title = s; repaintTitle(); return

-  if process.env.DYALOG_IDE_JS then $.getScript 'file://' + path.resolve process.cwd(), process.env.DYALOG_IDE_JS
+  if process.env.DYALOG_IDE_JS
+    for f in process.env.DYALOG_IDE_JS.split path.delimiter when f
+      $.getScript 'file://' + path.resolve process.cwd(), f