ruleset directive menu translation

ruleset directive menu translation

--- static/coffee/flows/directives.coffee
+++ static/coffee/flows/directives.coffee
@@ -265,13 +265,25 @@

       # USSD translations
       if Flow.flow.flow_type == 'U'
+
+        # USSD message translation
         ruleset.config._ussd_translation = ruleset.config.ussd_message[iso_code]
-        if ruleset.config._ussd_translation is undefined
+        if ruleset.config._ussd_translation is undefined or ruleset.config._ussd_translation == ""
           ruleset.config._ussd_translation = ruleset.config.ussd_message[baseLanguage]
           ruleset.config._missingTranslation = true
         else
           ruleset.config._missingTranslation = false

+        # USSD menu translation
+        if ruleset.ruleset_type == "wait_menu"
+          for item in ruleset.config.ussd_menu
+            item._missingTranslation = false
+            if item.label
+              item._translation = item.label[iso_code]
+              if item._translation is undefined or item._translation == ""
+                item._translation = item.label[baseLanguage]
+                item._missingTranslation = true
+
       Flow.updateTranslationStats()
       Plumb.repaint(element)


Delete soundcloud.coffee

Delete soundcloud.coffee


fix deprecated API

fix deprecated API

--- lib/commands.coffee
+++ lib/commands.coffee
@@ -115,7 +115,7 @@
     editor = atom.workspace.getActiveTextEditor()

     unless editor.getSelectedText()
-      editor.moveCursorToBeginningOfPreviousParagraph()
+      editor.moveToBeginningOfPreviousParagraph()
       editor.selectToBeginningOfNextParagraph()

     lines = editor.getSelectedText().split("\n")


x86 needs sret stuff as well

x86 needs sret stuff as well

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -139,7 +139,7 @@
         createFunction: (inModule, name, ret_type, param_types) -> inModule.getOrInsertFunction name, ret_type, param_types
         createFunctionType: (ret_type, param_types) -> llvm.FunctionType.get ret_type, param_types

-# armv7 requires us to pass a pointer to a stack slot for the return value when it's EjsValue.
+# armv7/x86 requires us to pass a pointer to a stack slot for the return value when it's EjsValue.
 # so functions that would normally be defined as:
 #
 #   ejsval _ejs_normal_func (ejsval env, ejsval this, uint32_t argc, ejsval* args)
@@ -148,7 +148,7 @@
 #
 #   void _ejs_sret_func (ejsval* sret, ejsval env, ejsval this, uint32_t argc, ejsval* args)
 #
-class ArmABI extends ABI
+class SRetABI extends ABI
         constructor: () ->
                 super
                 @ejs_return_type = types.void
@@ -2250,7 +2250,7 @@
         tree

 exports.compile = (tree, base_output_filename, source_filename, options) ->
-        abi = if (options.target is "armv7" or options.target is "armv7s") then new ArmABI() else new ABI()
+        abi = if (options.target_arch is "armv7" or options.target_arch is "armv7s" or options.target_arch is "x86") then new SRetABI() else new ABI()

         tree = insert_toplevel_func tree, source_filename

@@ -2351,4 +2351,4 @@
         visitor = new GatherImports(filename, top_path)
         visitor.visit(tree)
         visitor.importList
-        +


Add notifications

Add notifications

--- lib/css-comb.coffee
+++ lib/css-comb.coffee
@@ -28,5 +28,6 @@
       @processFile filePath, CSScomb.getConfig('csscomb')

   processFile: (filePath, config) ->
+    atom.notifications.addInfo('File processed by csscomb')
     comb = new CSScomb(config)
     comb.processFile(filePath)


Estrutura de testes AAA

Estrutura de testes AAA

--- spec/SkuSelectorSpec.coffee
+++ spec/SkuSelectorSpec.coffee
@@ -9,6 +9,8 @@
 		@mocks = (getJSONFixture(name) for name in mockNames)

 	it 'should have loaded JSON fixtures correctly', ->
+		#Assert
+		expect(typeof @mocks).toBe(typeof [])
 		expect(@mocks[0]).toBeDefined()
 		expect(typeof @mocks[0]).toBe(typeof {})

@@ -18,71 +20,155 @@
 			@ss = new vtex.portalPlugins.SkuSelector(@mock)

 		it 'should exist', ->
+			#Assert
 			expect(vtex.portalPlugins.SkuSelector).toBeDefined()
+
 		it 'should be instantiated', ->
+			#Assert
 			expect(@ss instanceof vtex.portalPlugins.SkuSelector).toBe(true)

-		it 'should import productId', ->
-			expect(@ss.productId).toEqual(@mock.productId)
-		it 'should import name', ->
-			expect(@ss.name).toEqual(@mock.name)
-		it 'should import dimensions', ->
-			# Number of dimensions
-			expect(@ss.dimensions.length).toEqual(@mock.dimensions.length)
-			# Dimension names
-			expect((dim.name for dim in @ss.dimensions)).toEqual(@mock.dimensions)
-			# Dimension selected map
-			expect(dim.selected).toBeUndefined() for dim in @ss.dimensions
-			# Dimension values
-			expect(dim.values).toEqual(@mock.dimensionsMap[dim.name]) for dim in @ss.dimensions
-		it 'should import skus', ->
-			expect(@ss.skus).toEqual(@mock.skus)
+		describe 'constructor', ->
+			it 'should import productId', ->
+				#Assert
+				expect(@ss.productId).toEqual(@mock.productId)
+
+			it 'should import product name', ->
+				#Assert
+				expect(@ss.name).toEqual(@mock.name)
+
+			it 'should import all dimensions', ->
+				#Assert
+				expect(@ss.dimensions.length).toEqual(@mock.dimensions.length)
+
+			it 'should import dimension names', ->
+				#Assert
+				expect((dim.name for dim in @ss.dimensions)).toEqual(@mock.dimensions)
+
+			it 'should initialize', ->
+				#Assert
+				expect(dim.selected).toBeUndefined() for dim in @ss.dimensions
+
+			it "should import each dimension's values", ->
+				#Assert
+				expect(dim.values).toEqual(@mock.dimensionsMap[dim.name]) for dim in @ss.dimensions
+
+			it "should import each dimension's input types", ->
+				#Assert
+				expect(dim.inputType).toEqual(@mock.dimensionsInputType[dim.name] or "radio") for dim in @ss.dimensions
+
+			it 'should import skus', ->
+				#Assert
+				expect(@ss.skus).toEqual(@mock.skus)

 		it 'should find undefined dimensions', ->
-			undefinedDimensions = (dim for dim in @ss.dimensions when dim.selected is undefined)
-			expect(@ss.findUndefinedDimensions()).toEqual(undefinedDimensions)
+			#Arrange
+			ours = (dim for dim in @ss.dimensions when dim.selected is undefined)
+
+			#Act
+			theirs =  @ss.findUndefinedDimensions()
+
+			#Assert
+			expect(theirs).toEqual(ours)

 		it 'should find available skus', ->
-			availableSkus = (sku for sku in @ss.skus when sku.available is true)
-			expect(@ss.findAvailableSkus()).toEqual(availableSkus)
+			#Arrange
+			ours = (sku for sku in @ss.skus when sku.available is true)
+
+			#Act
+			theirs = @ss.findAvailableSkus()
+
+			#Assert
+			expect(theirs).toEqual(ours)
+
+		it "should select a sku by setting each dimension's selected to its values", ->
+			#Arrange
+			sku = @mock.skus[0]
+
+			#Act
+			@ss.selectSku(sku)
+
+			#Assert
+			expect(dim.selected).toEqual(sku.dimensions[dim.name]) for dim in @ss.dimensions

 		#TODO it 'should assert that a sku is selectable', ->

 		#TODO it 'should find the selectable skus', ->

-
 		it 'should search the dimensions using the given function', ->
-			fn = ()->true
+			#Arrange
+			fn = -> true
 			spyOn($, 'grep').andCallThrough()
-			expect(@ss.searchDimensions(fn)).toEqual(@ss.dimensions)
+
+			#Act
+			searchResults = @ss.searchDimensions(fn)
+
+			#Assert
+			expect(searchResults).toEqual(@ss.dimensions)
 			expect($.grep).toHaveBeenCalledWith(@ss.dimensions, fn)

-		#TODO it 'should search the dimensions correctly', ->
-
 		it 'should get the dimension by its name', ->
-			expect(@ss.getDimensionByName(@ss.dimensions[0].name)).toEqual(@ss.dimensions[0])
+			#Arrange
+			dimension = @ss.dimensions[0]
+			name = dimension.name
+
+			#Act
+			result = @ss.getDimensionByName(name)
+
+			#Assert
+			expect(result).toEqual(dimension)

 		describe 'findSelectedSku', ->
 			it 'should find when it is unique', ->
-				spyOn(@ss, 'findSelectableSkus').andReturn([@mock.skus[0]])
-				expect(@ss.findSelectedSku()).toEqual(@mock.skus[0])
+				#Arrange
+				mockResult = [@mock.skus[0]]
+				spyOn(@ss, 'findSelectableSkus').andReturn(mockResult)
+
+				#Act
+				result = @ss.findSelectedSku()
+
+				#Assert
+				expect(result).toEqual(mockResult...)

 			it 'should not find when it is not unique', ->
-				spyOn(@ss, 'findSelectableSkus').andReturn([@mock.skus[0], @mock.skus[1]])
-				expect(@ss.findSelectedSku()).toBeUndefined()
+				#Arrange
+				mockResult = [@mock.skus[0], @mock.skus[1]]
+				spyOn(@ss, 'findSelectableSkus').andReturn(mockResult)
+
+				#Act
+				result = @ss.findSelectedSku()
+
+				#Assert
+				expect(result).toBeUndefined()

 			it 'should not find when it is empty', ->
-				spyOn(@ss, 'findSelectableSkus').andReturn([])
-				expect(@ss.findSelectedSku()).toBeUndefined()
+				#Arrange
+				mockResult = []
+				spyOn(@ss, 'findSelectableSkus').andReturn(mockResult)
+
+				#Act
+				result = @ss.findSelectedSku()
+
+				#Assert
+				expect(result).toBeUndefined()

 		it 'should get and set selected dimension', ->
+			#Arrange
 			dim  = @ss.dimensions[0]
-			@ss.setSelectedDimension(dim.name, "12kg")
-			expect(@ss.getSelectedDimension(dim.name)).toEqual("12kg")
+			value = "12kg"
+
+			#Act
+			@ss.setSelectedDimension(dim.name, value)
+			result = @ss.getSelectedDimension(dim.name)
+
+			#Assert
+			expect(result).toEqual(value)

 		it 'should reset the next dimensions', ->
+			#Act
 			@ss.resetNextDimensions(@ss.dimensions[0].name)
+
+			#Assert
 			expect(@ss.getSelectedDimension(dim.name)).toBeUndefined() for dim, i in @ss.dimensions when i > 0

@@ -91,22 +177,34 @@
 			@ssr = new vtex.portalPlugins.SkuSelectorRenderer({}, {}, {})

 		it 'should exist', ->
+			#Assert
 			expect(vtex.portalPlugins.SkuSelectorRenderer).toBeDefined()
+
 		it 'should be instantiated', ->
+			#Assert
 			expect(@ssr instanceof vtex.portalPlugins.SkuSelectorRenderer).toBe(true)

 		it 'should call updatePriceAvailable', ->
+			#Arrange
 			sku = {available: true}
 			spyOn(@ssr, 'updatePriceAvailable')
+
+			#Act
 			@ssr.updatePrice(sku)

+			#Assert
 			expect(@ssr.updatePriceAvailable).toHaveBeenCalledWith(sku)

+
 		it 'should call updatePriceUnavailable', ->
+			#Arrange
 			sku = {available: false}
 			spyOn(@ssr, 'updatePriceUnavailable')
+
+			#Act
 			@ssr.updatePrice(sku)

+			#Assert
 			expect(@ssr.updatePriceUnavailable).toHaveBeenCalled()

@@ -115,15 +213,25 @@
 			loadFixtures 'sku-selector.html'

 		it 'should have jQuery', ->
+			#Assert
 			expect($).toBeDefined()
+
 		it 'should exist', ->
+			#Assert
 			expect($.fn.skuSelector).toBeDefined()
 			expect($.skuSelector).toBeDefined()

 		it 'should have loaded HTML fixtures correctly', ->
+			#Assert
 			expect($('.sku-selector-container')).toExist()

-		it 'should return jQuery object, for chaining', ->
-			$el = $('.sku-selector-container')
-			expect($el.skuSelector(@mocks[0])).toBe($el)
-
+		it 'should return a jQuery object, for chaining', ->
+			#Arrange
+			element = $('.sku-selector-container')
+
+			#Act
+			result = element.skuSelector(@mocks[0])
+
+			#Assert
+			expect(result).toBe(element)
+


Beautiful!

Beautiful!

--- spec/api/server_spec.coffee
+++ spec/api/server_spec.coffee
@@ -51,12 +51,45 @@

         http.does
             createServer: ->
-                mock('server').does
+
+                return mock('server').does
+
+                    #
+                    # a "tier2" mock, with function expectations...
+                    # ---------------------------------------------
+                    #
+                    # * worth it's weight in gold!
+                    #
+
                     listen: (args...) -> args.pop()()
                     address: ->
-                    close: -> facto()
+                    close: -> facto()

         instance = create http: listen: {}
-        instance.listen -> instance.close()
+        instance.listen ->  # instance.close()

+
+        #
+        #  actual expected
+        #
+        #   1 | {
+        #   2 |   "http": {
+        #   3 |     "functions": {
+        #   4 |       "Object.createServer()": "was called"
+        #   5 |     }
+        #   6 |   },
+        #   7 |   "server": {
+        #   8 |     "functions": {
+        #   9 |       "Object.listen()": "was called",
+        #  10 |       "Object.address()": "was called",
+        #  11 |       "Object.close()": "was NOT called"  <----------------
+        #  12 |     }
+        #  13 |   }
+        #  14 | }
+        #
+        #  as opposed to:
+        #
+        #     Error: timeout of 2000ms exceeded
+        #
+


Adding configuration doc

Adding configuration doc

--- mail.coffee
+++ mail.coffee
@@ -5,7 +5,7 @@
 #   "moment": "^2.8.3"
 #
 # Configuration:
-#   None
+#   HUBOT_MAIL_KEY
 #
 # Commands:
 #   hubot mail <recipient> <message> - Sends a <message> to <recipient> when found available


Added initializers, parameters, started work on templates and declaration

Signed-off-by: ysgard <125fa00567d70184e4d0dc6a16b66d7bf20d7d44@gmail.com>

--- grammars/d.cson
+++ grammars/d.cson
@@ -119,6 +119,40 @@
         'name': 'storage.type.basic-type-x.d'
       ]
     },
+    'basic-type-2': {
+      'patterns': [
+        {
+          'match': '\\*',
+          'name': 'storage.type.pointer.d'
+        },
+        {
+          'match': '\\[\\s*\\]',
+          'name': 'storage.type.array.empty.d'
+        },
+        {
+          'begin': '\\[',
+          'beginCaptures': { '0': { 'name': 'storage.type.array.expression.begin.d' } },
+          'end': '\\]',
+          'endCaptures': { '0': { 'name': 'storage.type.array.expression.end.d' } },
+          'patterns': [
+            {
+              'match': '\\.\\.|$',
+              'name': 'keyword.operator.slice.d'
+            },
+            { 'include': '#type' },
+            { 'include': '#expression' }
+          ]
+        },
+        { 'include': '#delegate-basic-type-2' },
+        { 'include': '#function-basic-type-2' }
+      ]
+    }
+    'delegate-basic-type-2': {
+
+    },
+    'function-basic-type-2': {
+
+    },
     # Defined in object.di
     'defined-type': {
       'patterns': [
@@ -147,36 +181,31 @@
       ]
     },
     #
-    # assert
+    #
+    #
+    #
+    # Assert
     #
     'assert-expression': {
       'patterns': [
         'begin': 'assert\\s*\\(',
-        'beginCaptures': {
-          '0': { 'name': 'keyword.assert.begin.d' }
-        },
+        'beginCaptures': { '0': { 'name': 'keyword.assert.begin.d' } },
         'end': '\\)',
-        'endCaptures': {
-          '0': { 'name': 'keyword.assert.end.d' }
-        },
+        'endCaptures': { '0': { 'name': 'keyword.assert.end.d' } },
         'patterns': [
           { 'include': '#expression' }
         ]
       ]
     },
     #
-    # typeid
+    # Typeid
     #
     'typeid-expression': {
       'patterns': [
         'begin': 'typeid\\s*\\(',
-        'beginCaptures': {
-          '0': { 'name': 'keyword.typeid.begin.d' }
-        },
+        'beginCaptures': { '0': { 'name': 'keyword.typeid.begin.d' } },
         'end': '\\)',
-        'endCaptures': {
-          '0': { 'name': 'keyword.typeid.end.d' }
-        },
+        'endCaptures': { '0': { 'name': 'keyword.typeid.end.d' } },
         'patterns': [
           { 'include': '#type' },
           { 'include': '#expression' }
@@ -184,17 +213,13 @@
       ]
     },
     #
-    # imports
+    # Imports
     #
     'import-expression': {
       'begin': 'import\\s*\\(',
-      'beginCaptures': {
-        '0': { 'name': 'keyword.import.begin.d' }
-      },
+      'beginCaptures': { '0': { 'name': 'keyword.import.begin.d' } },
       'end': '\\)',
-      'endCaptures': {
-        '0': { 'name': 'keyword.import.end.d' }
-      },
+      'endCaptures': { '0': { 'name': 'keyword.import.end.d' } },
       'patterns': [
         { 'include': '#expression' }
       ]
@@ -206,13 +231,9 @@
       'patterns': [
         {
           'begin': 'is\\s*\\(',
-          'beginCaptures': {
-            '0': { 'name': 'keyword.operator.is.begin.d' },
-          },
+          'beginCaptures': { '0': { 'name': 'keyword.operator.is.begin.d' } },
           'end': '\\)',
-          'endCaptures': {
-            '0': { 'name': 'keyword.operator.is.end.d' },
-          },
+          'endCaptures': { '0': { 'name': 'keyword.operator.is.end.d' } },
           'patterns': [
             {
               'match': ':|==|,',
@@ -256,13 +277,9 @@
     'traits-expression': {
       'patterns': [
         'begin': '__traits\\s*\\(',
-        'beginCaptures': {
-          '0': { 'name': 'keyword.other.traits.begin.d' }
-        },
+        'beginCaptures': { '0': { 'name': 'keyword.other.traits.begin.d' } },
         'end': '\\)',
-        'endCaptures': {
-          '0': { 'name': 'keyword.other.traits.end.d' }
-        },
+        'endCaptures': { '0': { 'name': 'keyword.other.traits.end.d' } },
         'patterns': [
           { 'include': '#traits-keyword' },
           { 'include': '#traits-argument' }
@@ -287,6 +304,51 @@
       'patterns': [
         'match': '__FILE__|__MODULE__|__LINE__|__FUNCTION__|__PRETTY_FUNCTION__',
         'name': 'keyword.other.special.d'
+      ]
+    }
+    #
+    # Parameters
+    #
+    'parameters': {
+      'patterns': [
+        {
+          'match': '\\(\\s*\\.\\.\\.\\s*\\)',
+          'name': 'support.type.tuple-parameter.d'
+        },
+        {
+          'begin': '\\(',
+          'beginCaptures': { '0': { 'name': 'support.type.parameters.begin.d' } },
+          'end': '\\)',
+          'endCaptures': { '0': { 'name': 'support.type.parameters.end.d' } },
+          'patterns': [
+              {
+                'match': ',',
+                'name': 'keyword.operator.comma.d'
+              },
+              { 'include': '#parameter' }
+          ]
+        }
+      ]
+    },
+    'parameter': {
+      'patterns': [
+        { 'include': '#in-out' },
+        { 'include': '#basic-type' },
+        { 'include': '#type' },
+        {
+          'match': '\\.\\.\\.',
+          'name': 'support.type.tuple.d'
+        },
+        { 'include': '#declarator' }
+      ]
+    },
+    'in-out': {
+      'patterns': [
+        {
+          'match': 'auto|final|in|out|lazy|ref|scope',
+          'name': 'storage.type.modifier.inout.d'
+        },
+        { 'include': '#type-ctor' }
       ]
     }
     #
@@ -310,26 +372,18 @@
       'patterns': [
         {
           'begin': 'extern\\s*\\(\\s*C\\+\\+,',
-          'beginCaptures': {
-            '0': { 'name': 'keyword.other.extern.cplusplus.begin.d' },
-          },
+          'beginCaptures': { '0': { 'name': 'keyword.other.extern.cplusplus.begin.d' } },
           'end': '\\)',
-          'endCaptures': {
-            '0': { 'name': 'keyword.other.extern.cplusplus.end.d' },
-          },
+          'endCaptures': { '0': { 'name': 'keyword.other.extern.cplusplus.end.d' } },
           'patterns': [
             { 'include': '#identifier-list' },
           ]
         },
         {
           'begin': 'extern\\s*\\(',
-          'beginCaptures': {
-            '0': { 'name': 'keyword.other.extern.begin.d' },
-          },
+          'beginCaptures': { '0': { 'name': 'keyword.other.extern.begin.d' } },
           'end': '\\)',
-          'endCaptures': {
-            '0': { 'name': 'keyword.other.extern.end.d' },
-          },
+          'endCaptures': { '0': { 'name': 'keyword.other.extern.end.d' } },
           'patterns': [
             { 'include': '#linkage-type' },
           ]
@@ -365,13 +419,9 @@
       'patterns': [
         {
           'begin': 'deprecated\\s*\\(',
-          'beginCaptures': {
-            '0': { 'name': 'keyword.other.deprecated.begin.d' },
-          },
+          'beginCaptures': { '0': { 'name': 'keyword.other.deprecated.begin.d' } },
           'end': '\\)',
-          'endCaptures': {
-            '0': { 'name': 'keyword.other.deprecated.end.d' },
-          },
+          'endCaptures': { '0': { 'name': 'keyword.other.deprecated.end.d' } },
           'patterns': [
             { 'include': '#string-literal' },
           ]
@@ -409,7 +459,7 @@
           'end': '\\)',
           'name': 'entity.name.tag.user-defined-property.d',
           'patterns': [
-
+            { 'include': '#expression' }
           ]
         }
       ]
@@ -454,13 +504,9 @@
       'patterns': [
         {
           'begin': 'debug\\s*\\(',
-          'beginCaptures': {
-            '0': { 'name': 'keyword.other.debug.identifier.begin.d' }
-          },
+          'beginCaptures': { '0': { 'name': 'keyword.other.debug.identifier.begin.d' } },
           'end': '\\)',
-          'endCaptures': {
-            '0': { 'name': 'keyword.other.debug.identifier.end.d' }
-          },
+          'endCaptures': { '0': { 'name': 'keyword.other.debug.identifier.end.d' } },
           'patterns': [
             { 'include': '#integer-literal' },
             { 'include': '#identifier' },
@@ -480,13 +526,9 @@
     },
     'static-if-assert': {
       'begin': '(static\\s*(assert|if)\\s*\\()',
-      'beginCaptures': {
-        '0': { 'name': 'keyword.other.if-assert.static.begin.d' },
-      },
+      'beginCaptures': { '0': { 'name': 'keyword.other.if-assert.static.begin.d' } },
       'end': '\\)',
-      'endCaptures': {
-        '0': { 'name': 'keyword.other.if-assert.static.end.d' },
-      },
+      'endCaptures': { '0': { 'name': 'keyword.other.if-assert.static.end.d' } },
       'patterns': [
         { 'include' : '#expression' }
       ],
@@ -497,12 +539,8 @@
     'mixin-expression': {
       'begin': 'mixin\\s*\\(',
       'end': '\\)',
-      'beginCaptures': {
-        '0': { 'name': 'keyword.mixin.begin.d' }
-      },
-      'endCaptures': {
-        '0': { 'name': 'keyword.mixin.end.d' }
-      },
+      'beginCaptures': { '0': { 'name': 'keyword.mixin.begin.d' } },
+      'endCaptures': { '0': { 'name': 'keyword.mixin.end.d' } },
       'patterns': [
         { 'include': '#expression' }
       ]
@@ -613,7 +651,6 @@

       ]
     },
-
     'assign-operator': {
       'patterns': [
         {
@@ -635,18 +672,249 @@
       ]
     },
     #
+    # Template
+    #
+    'template-declaration': {
+      'patterns': [
+
+      ]
+    },
+    'template-parameters': {
+      'patterns': [
+
+      ]
+    },
+    'template-parameter': {
+      'patterns': [
+
+      ]
+    },
+    'constraint': {
+      'patterns': [
+
+      ]
+    },
+    'template-instance': {
+      'patterns': [
+
+      ]
+    },
+    'template-arguments': {
+      'patterns': [
+
+      ]
+    },
+    'template-argument': {
+      'patterns': [
+
+      ]
+    },
+    'symbol': {
+      'patterns': [
+
+      ]
+    },
+    'symbol-tail': {
+      'patterns': [
+
+      ]
+    },
+    'template-single-argument': {
+      'patterns': [
+
+      ]
+    },
+    'template-type-parameter': {
+      'patterns': [
+
+      ]
+    },
+    'template-type-parameter-specialization': {
+      'patterns': [
+
+      ]
+    },
+    'template-type-parameter-default': {
+      'patterns': [
+
+      ]
+    },
+    'template-this-parameter': {
+      'patterns': [
+
+      ]
+    },
+    'template-value-parameter': {
+      'patterns': [
+
+      ]
+    },
+    'template-value-parameter-specialization': {
+      'patterns': [
+
+      ]
+    },
+    'template-value-parameter-default': {
+      'patterns': [
+
+      ]
+    },
+    'template-alias-parameter': {
+      'patterns': [
+
+      ]
+    },
+    'template-alias-parameter-specialization': {
+      'patterns': [
+
+      ]
+    },
+    'template-alias-parameter-default': {
+      'patterns': [
+
+      ]
+    },
+    'template-tuple-parameter': {
+      'patterns': [
+        { 'include': '#identifier' },
+        {
+          'match': '\\.\\.\\.',
+          'name': 'support.type.tuple.d'
+        }
+      ]
+    },
+
+
+    #
+    # Declarations
+    #
+    'declaration': {
+      'patterns': [
+        { 'include': '#alias-declaration' },
+        { 'include': '#aggregate-declaration' },
+        { 'include': '#enum-declaration' },
+        { 'include': '#import-declaration' },
+        { 'include': '#decl' }
+      ]
+    },
+    'alias-declaration': {
+      'patterns': [
+        {
+          'match': 'alias',
+          'name': 'keyword.alias.d'
+        },
+        { 'include': '#storage-class' },
+        { 'include': '#basic-type' },
+        { 'include': '#declarator' },
+        { 'include': '#alias-declaration-x' }
+      ]
+    },
+    #
+    # Initializers
+    #
+    'initializer': {
+      'patterns': [
+        { 'include': '#void-initializer' },
+        { 'include': '#non-void-initializer' }
+      ]
+    },
+    'void-initializer': {
+      'patterns': [
+        'match': 'void',
+        'name': 'support.type.void.d'
+      ]
+    },
+    'non-void-initializer': {
+      'patterns': [
+        { 'include': '#exp-initializer' },
+        { 'include': '#array-initializer' },
+        { 'include': '#struct-initializer' }
+      ]
+    },
+    'exp-initializer': {
+      'patterns': [
+        { 'include': '#expression' }
+      ]
+    },
+    'array-initializer': {
+      'patterns': [
+        'begin': '\\[',
+        'beginCaptures': { '0': { 'name': 'support.type.array.initializer.begin.d' } },
+        'end': '\\]',
+        'endCaptures': {'0': { 'name': 'support.type.array.initializer.end.d' } },
+        'patterns': [
+          {
+            'match': ',',
+            'name': 'keyword.operator.comma.d'
+          },
+          { 'include': '#array-member-initialization' },
+        ]
+      ]
+    },
+    'array-member-initializations': {
+      'patterns': [
+        {
+          'match': ',',
+          'name': 'keyword.operator.comma.d'
+        },
+        { 'include': '#array-member-initialization' },
+      ]
+    },
+    'array-member-initialization': {
+      'patterns': [
+        { 'include': '#non-void-initializer' },
+        { 'include': '#expression' },
+        {
+          'match': ':',
+          'name': 'support.type.tag.d'
+        }
+      ]
+    },
+    'struct-initializer': {
+      'patterns': [
+        'begin': '\\{',
+        'beginCaptures': { '0': { 'name': 'support.type.struct.initializer.begin.d' } },
+        'end': '\\}',
+        'endCaptures': { '0': { 'name': 'support.type.struct.initializer.end.d' } },
+        'patterns': [
+          { 'include': '#struct-member-initializers' }
+        ]
+      ]
+    }
+    'struct-member-initializers': {
+      'patterns': [
+        {
+          'match': ',',
+          'name': 'keyword.operator.comma.d'
+        },
+        { 'include': '#struct-member-initializer' },
+      ]
+    },
+    'struct-member-initializer': {
+      'patterns': [
+        { 'include': '#non-void-initializer' },
+        { 'include': '#identifier' },
+        {
+          'match': ':',
+          'name': 'support.type.tag.d'
+        },
+      ]
+    },
+    'default-initializer-expression': {
+      'patterns': [
+        { 'include': '#expression' },
+        { 'include': '#special-keyword' }
+      ]
+    },
+    #
     # Assembly (Iasm)
     #
     'asm-statement': {
       'patterns': [
         'begin': 'asm\\s*\\{',
         'end': '\\}',
-        'beginCaptures': {
-          '0': { 'name': 'keyword.control.asm.begin.d' },
-        },
-        'endCaptures': {
-          '0': { 'name': 'keyword.control.asm.end.d' },
-        },
+        'beginCaptures': { '0': { 'name': 'keyword.control.asm.begin.d' } },
+        'endCaptures': { '0': { 'name': 'keyword.control.asm.end.d' } },
         'patterns': [
           { 'include': '#asm-instruction' },
         ],


readFile util function

readFile util function

--- main.coffee
+++ main.coffee
@@ -32,10 +32,14 @@
   process.exit 1

+readFile = (path) ->
+  fs.readFileSync path, encoding: 'ascii'
+
+
 editSync = (path, pairs) ->
   fs.writeFileSync path, pairs.reduce (contents, [rx, replacement]) ->
     contents.replace rx, replacement
-  , fs.readFileSync path, encoding: 'ascii'
+  , readFile path

 writeConfig = (config) ->
@@ -51,7 +55,7 @@

 readConfig = ->
   try
-    JSON.parse fs.readFileSync '.natal'
+    JSON.parse readFile '.natal'
   catch {message}
     logErr \
       if message.match /ENOENT/i


add ioLanguage support

add ioLanguage support

just add ioLanguage support (http://iolanguage.org/)
Try it :)

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -153,6 +153,14 @@
       command: "iced"
       args: (context) -> [context.filepath]

+  ioLanguage:
+    "Selection Based":
+      command: "io"
+      args: (context) -> [context.filepath]
+    "File Based":
+      command: "io"
+      args: (context) -> ['-e', context.getCode()]
+
   Java:
     "File Based":
       command: if GrammarUtils.OperatingSystem.isWindows() then "cmd" else "bash"


Javascript -> JavaScript, for language-babel

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -148,7 +148,7 @@
       command: "node"
       args: (context) -> [context.filepath]

-  'Babel ES6 Javascript':
+  'Babel ES6 JavaScript':
     "Selection Based":
       command: "babel-node"
       args: (context) -> ['-e', context.getCode()]


Add tests for constant float values.

Add tests for constant float values.

--- spec/powershell-spec.coffee
+++ spec/powershell-spec.coffee
@@ -204,3 +204,15 @@
           {tokens} = grammar.tokenizeLine constant
           expect(tokens[0].value).toEqual constant
           expect(tokens[0]).toHaveScope "constant.numeric.integer.bytes.powershell"
+
+    describe "Constant float values", ->
+      constants = [
+        "1.0", "0.89324", "123124235.2385923234", "3.23e24", "2.33e-12",
+        "9.11e+21", "21e6", "7e-12", "12e+24"
+      ]
+
+      it "tokenizes constant float values", ->
+        for constant in constants
+          {tokens} = grammar.tokenizeLine constant
+          expect(tokens[0].value).toEqual constant
+          expect(tokens[0]).toHaveScope "constant.numeric.float.powershell"


Don't use WorkspaceView in specs

Don't use WorkspaceView in specs

--- spec/whitespace-spec.coffee
+++ spec/whitespace-spec.coffee
@@ -1,16 +1,14 @@
 path = require 'path'
 fs = require 'fs-plus'
-{WorkspaceView} = require 'atom'
 temp = require 'temp'

 describe "Whitespace", ->
-  [editor, buffer] = []
+  [editor, buffer, workspaceElement] = []

   beforeEach ->
     directory = temp.mkdirSync()
     atom.project.setPath(directory)
-    atom.workspaceView = new WorkspaceView()
-    atom.workspace = atom.workspaceView.model
+    workspaceElement = atom.views.getView(atom.workspace)
     filePath = path.join(directory, 'atom-whitespace.txt')
     fs.writeFileSync(filePath, '')
     fs.writeFileSync(path.join(directory, 'sample.txt'), 'Some text.\n')
@@ -227,7 +225,7 @@

   describe "when the editor is split", ->
     it "does not throw exceptions when the editor is saved after the split is closed (regression)", ->
-      atom.workspaceView.getActivePaneView().trigger 'pane:split-right'
+      atom.workspace.getActivePane().splitRight(copyActiveItem: true)
       atom.workspace.getPanes()[0].destroyItems()

       editor = atom.workspace.activePaneItem
@@ -258,7 +256,7 @@
       buffer.setText("foo   \nbar\t   \n\nbaz")

     it "removes the trailing whitespace in the active editor", ->
-      atom.workspaceView.trigger 'whitespace:remove-trailing-whitespace'
+      atom.commands.dispatch(workspaceElement, 'whitespace:remove-trailing-whitespace')
       expect(buffer.getText()).toBe "foo\nbar\n\nbaz"

     it "does not attempt to remove whitespace when the package is deactivated", ->
@@ -269,22 +267,22 @@
     it "removes all \t characters and replaces them with spaces using the configured tab length", ->
       editor.setTabLength(2)
       buffer.setText('\ta\n\t\nb\t\nc\t\td')
-      atom.workspaceView.trigger 'whitespace:convert-tabs-to-spaces'
+      atom.commands.dispatch(workspaceElement, 'whitespace:convert-tabs-to-spaces')
       expect(buffer.getText()).toBe "  a\n  \nb  \nc    d"

       editor.setTabLength(3)
       buffer.setText('\ta\n\t\nb\t\nc\t\td')
-      atom.workspaceView.trigger 'whitespace:convert-tabs-to-spaces'
+      atom.commands.dispatch(workspaceElement, 'whitespace:convert-tabs-to-spaces')
       expect(buffer.getText()).toBe "   a\n   \nb   \nc      d"

   describe "when the 'whitespace:convert-spaces-to-tabs' command is run", ->
     it "removes all space characters and replaces them with hard tabs", ->
       editor.setTabLength(2)
       buffer.setText("  a\n  \nb  \nc    d")
-      atom.workspaceView.trigger 'whitespace:convert-spaces-to-tabs'
+      atom.commands.dispatch(workspaceElement, 'whitespace:convert-spaces-to-tabs')
       expect(buffer.getText()).toBe '\ta\n\t\nb\t\nc\t\td'

       editor.setTabLength(3)
       buffer.setText("   a\n   \nb   \nc      d"
-      atom.workspaceView.trigger 'whitespace:convert-spaces-to-tabs'
+      atom.commands.dispatch(workspaceElement, 'whitespace:convert-spaces-to-tabs')
       expect(buffer.getText()).toBe '\ta\n\t\nb\t\nc\t\td')


emergency patch XD

emergency patch XD

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -28,8 +28,8 @@
     registerEditorActivation: ->
         currentEditor = atom.workspace.getActivePaneItem()
         atom.workspace.onDidChangeActivePaneItem (nextEditor) =>
-            current = currentEditor.getPath?()
-            next = nextEditor.getPath?()
+            current = currentEditor?.getPath?()
+            next = nextEditor?.getPath?()
             if next isnt current
                 currentEditor?.core?.emit 'deactivate'
                 nextEditor?.core?.emit 'activate'


enabled passing of arrays to mixin function

--- lib/baseclass.coffee
+++ lib/baseclass.coffee
@@ -1,15 +1,15 @@
 DocumentClass.Base = class Base
   @defaultMixins: ['transform']
   @_mixins: []
-  @mixin: (mixins...) -> @mixinArray mixins
-  @mixinArray: (mixins) ->
+  @mixin: (mixins...) ->
+    mixins = mixins[0] if mixins[0] instanceof Array
     for mixin in mixins
       mixins = _.unique mixins.concat mixin.dependsOn or []

     for mixin in mixins
       @_mixins.push mixin
       DocumentClass.Mixins.get(mixin)?.applyMixin this
-  @mixinArray @defaultMixins
+  @mixin @defaultMixins

   @extend: (extension) ->
     Constructor = null


Always throw exceptions when translating rows on destroyed DisplayBuffer

Previously we only threw exceptions on non-release builds, but we
haven’t seen bundled packages cause issues in a while. Time to see what
third party packages cause problems.

Refs #3192

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -735,9 +735,7 @@
   #
   # Returns a {Point}.
   screenPositionForBufferPosition: (bufferPosition, options) ->
-    # TODO: Expand this exception to cover all versions once we burn it in on non-release builds
-    if @isDestroyed() and not atom.isReleasedVersion()
-      throw new Error("This TextEditor has been destroyed")
+    throw new Error("This TextEditor has been destroyed") if @isDestroyed()

     { row, column } = @buffer.clipPosition(bufferPosition)
     [startScreenRow, endScreenRow] = @rowMap.screenRowRangeForBufferRow(row)


Finder: loader color changed on finderitems

--- client/Finder/filetree/itemviews/fileitem.coffee
+++ client/Finder/filetree/itemviews/fileitem.coffee
@@ -14,7 +14,7 @@
         width       : 16
       loaderOptions :
         # color       : @utils.getRandomHex()
-        color       : "#222222"
+        color       : "#71BAA2"
         shape       : "spiral"
         diameter    : 16
         density     : 30


Client: AccountSessionListItem: generate hostname according to env

--- client/account/lib/accountsessionlistitem.coffee
+++ client/account/lib/accountsessionlistitem.coffee
@@ -1,5 +1,6 @@
 kd             = require 'kd'
 JView          = require 'app/jview'
+globals        = require 'globals'
 kookies        = require 'kookies'
 timeago        = require 'timeago'
 KDListItemView = kd.ListItemView
@@ -29,10 +30,15 @@

     { groupName, lastAccess } = @getData()

+    console.log globals.config
+    hostname = switch globals.config.environment
+      when 'production' then 'koding.com'
+      else "#{globals.config.environment}.koding.com"
+
     group =
       if groupName is 'koding'
-      then 'koding.com'
-      else "#{groupName}.koding.com"
+      then hostname
+      else "#{groupName}.#{hostname}"

     """
     <div class="session-item">


If loadsFromOrigin emit a signal when its finished

--- client/app/CommonViews/linkviews/linkview.coffee
+++ client/app/CommonViews/linkviews/linkview.coffee
@@ -1,16 +1,16 @@
 class LinkView extends KDCustomHTMLView

   constructor:(options = {}, data)->
-
+
     options.tagName or= 'a'
     data            or= fake : yes
     data.profile    or= {}
-
+
     data.profile.firstName or= "a koding"
     data.profile.lastName  or= "user"

     super options, data
-
+
     if data.fake and options.origin
       @loadFromOrigin options.origin

@@ -19,6 +19,7 @@
     callback = (data)=>
       @setData data
       @render()
+      @emit "OriginLoadComplete", data

     if origin.constructorName
       bongo.cacheable origin.constructorName, origin.id, (err, origin)=>
@@ -27,6 +28,6 @@
       callback origin

   viewAppended:->
-
+
     @setTemplate @pistachio()
     @template.update()


payment: plan subscribe refactoring

payment: plan subscribe refactoring

--- client/landing/Groups/Views/groupcreation.coffee
+++ client/landing/Groups/Views/groupcreation.coffee
@@ -42,8 +42,6 @@
     @buttons.next.hide()

     @on 'ready', @buttons.next.enable.bind @buttons.next
-
-  charge:(plan, callback)-> plan.subscribe { pin: '0000' }, callback

   setPositions:->
     super


revert analysis_toolbar.coffee

revert analysis_toolbar.coffee

--- lib/analysis/analysis_toolbar.coffee
+++ lib/analysis/analysis_toolbar.coffee
@@ -29,18 +29,18 @@
     totalInfo     = 0
     for path, errors of @errors.repository
       totalProblems += errors.length
-
-      # totalInfo     += _.sum errors, (p) =>
-      #   if AnalysisToolbar.infoTypes.indexOf(p.type) != -1 then 1 else 0
-      #
-      # totalWarnings += _.sum errors, (p) =>
-      #   if AnalysisToolbar.warningTypes.indexOf(p.type) != -1 then 1 else 0
-      #
-      # totalErrors   += _.sum errors, (p) =>
-      #   isInfo    = AnalysisToolbar.infoTypes.indexOf(p.type) != -1
-      #   isWarning = AnalysisToolbar.warningTypes.indexOf(p.type) != -1
-      #
-      #   if !isInfo && !isWarning then 1 else 0
+
+      totalInfo     += _.sum errors, (p) =>
+        if AnalysisToolbar.infoTypes.indexOf(p.type) != -1 then 1 else 0
+
+      totalWarnings += _.sum errors, (p) =>
+        if AnalysisToolbar.warningTypes.indexOf(p.type) != -1 then 1 else 0
+
+      totalErrors   += _.sum errors, (p) =>
+        isInfo    = AnalysisToolbar.infoTypes.indexOf(p.type) != -1
+        isWarning = AnalysisToolbar.warningTypes.indexOf(p.type) != -1
+
+        if !isInfo && !isWarning then 1 else 0

     @view.problemCount  = totalProblems
     @view.infoCount     = totalInfo


FF: Add contentEditable to clipboard operation textboxes

--- lib/clipboard.coffee
+++ lib/clipboard.coffee
@@ -3,6 +3,7 @@
     textArea = document.createElement "textarea"
     textArea.style.position = "absolute"
     textArea.style.left = "-100%"
+    textArea.contentEditable = "true"
     textArea

   # http://groups.google.com/group/chromium-extensions/browse_thread/thread/49027e7f3b04f68/f6ab2457dee5bf55


Iterate over all generator plugins when generating models, views, or scaffolding.

--- src/command.coffee
+++ src/command.coffee
@@ -8,7 +8,6 @@
 async = require 'async'
 {spawn, exec} = require 'child_process'
 watcher = require './watcher'
-generator = require './generator'
 server = require './server'
 pkgmgr = require './pkgmgr'
 optimizer = require './optimizer'
@@ -138,42 +137,48 @@
   model = opts.arguments[2]
   utils.fatal "Must supply a name for the model" unless model
   app = opts.app ? 'main'
-  generator.generateModel(model, app, opts)
+  for generator in project.plugins.generators
+    generator.generateModel(model, app, opts)

 # Task - remove a generated model
 task 'destroy model', 'remove a generated model', ->
   model = opts.arguments[2]
   utils.fatal "Must supply a name for the model" unless model
   app = opts.app ? 'main'
-  generator.destroyModel(model, app)
+  for generator in project.plugins.generators
+    generator.destroyModel(model, app)

 # Task - create a new view
 task 'generate view', 'create a new view', ->
   view = opts.arguments[2]
   utils.fatal "Must supply a name for the view" unless view
   app = opts.app ? 'main'
-  generator.generateView(view, app)
+  for generator in project.plugins.generators
+    generator.generateView(view, app)

 # Task - remove a generated view
 task 'destroy view', 'remove a generated view', ->
   view = opts.arguments[2]
   utils.fatal "Must supply a name for the view" unless view
   app = opts.app ? 'main'
-  generator.destroyView(view, app)
+  for generator in project.plugins.generators
+    generator.destroyView(view, app)

 # Task - create scaffold for a resource, including client models, views, templates, tests, and server models, RESTful APIs
 task 'generate scaffold', 'create scaffold for a resource', ->
   model = opts.arguments[2]
   utils.fatal "Must supply a name for the model" unless model
   app = opts.app ? 'main'
-  generator.generateScaffold(model, app, opts)
+  for generator in project.plugins.generators
+    generator.generateScaffold(model, app, opts)

 # Task - remove generated scaffold for a resource
 task 'destroy scaffold', 'remove generated scaffold for a resource', ->
   model = opts.arguments[2]
   utils.fatal "Must supply a name for the model" unless model
   app = opts.app ? 'main'
-  generator.destroyScaffold(model, app)
+  for generator in project.plugins.generators
+    generator.destroyScaffold(model, app)

 # Task - install packages
 task 'install', 'install packages', ->


Added a class for checked integer operations.

--- lib/long_int.coffee
+++ lib/long_int.coffee
@@ -164,6 +164,8 @@
   @make: (x) ->
     if x instanceof LongInt
       x
+    else if x instanceof CheckedInt
+      new LongInt x.val
     else if typeof x == 'number'
       new LongInt x
     else
@@ -258,6 +260,82 @@
     [a, b] = [this.abs(), other.abs()]
     if a.cmp(b) > 0 then trampoline step a, b else trampoline step b, a

+
+# -- The CheckedInt class provides integer operations that propagate
+#    to LongInt on overflow.
+
+class CheckedInt
+  constructor: (val = 0) ->
+    if typeof val != "number"
+      throw new Error "#{val} is not a number"
+    else if val % 1
+      throw new Error "#{val} is not an integer"
+    else if Math.abs(val) >= BASE
+      throw new Error "#{val} is not between #{-BASE+1} and #{BASE-1}"
+    else
+      @val = val
+
+  @make: (x) ->
+    if x instanceof CheckedInt
+      x
+    else
+      new CheckedInt x
+
+  make = (val) ->
+    if Math.abs(val) < BASE then new CheckedInt val else new LongInt val
+
+  @operator: (names, arity, code) ->
+    f = (args...) -> code.apply(this,
+                                CheckedInt.make x for x in args[...arity-1])
+    @::[name] = f for name in names
+    null
+
+  @operator ['neg', '-'], 1, -> new CheckedInt -@val
+
+  @operator ['abs'], 1, -> new CheckedInt Math.abs @val
+
+  @operator ['cmp', '<=>'], 2, (other) ->
+    if @val < other.val then -1 else if @val > other.val then 1 else 0
+
+  @operator ['sgn'], 1, -> @cmp 0
+
+  @operator ['plus', '+'], 2, (other) -> make @val + other.val
+
+  @operator ['minus', '-'], 2, (other) -> make @val - other.val
+
+  @operator ['times', '*'], 2, (other) ->
+    tmp = @val * other.val
+    if Math.abs(tmp) < BASE
+      new CheckedInt tmp
+    else
+      new LongInt(@val).times other.val
+
+  @operator ['div', '/'], 2, (other) -> make (@val / other.val) >> 0
+
+  @operator ['mod', '%'], 2, (other) -> make @val % other.val
+
+  @operator ['pow', '**'], 2, (other) ->
+    switch other.sgn()
+      when  0 then 1
+      when -1 then throw Error 'exponent must not be negative'
+      else
+        tmp = Math.pow @val, other.val
+        if Math.abs(tmp) < BASE
+          new CheckedInt tmp
+        else
+          new LongInt(@val).pow other.val
+
+  @operator ['sqrt'], 1, -> Math.sqrt(@val) >> 0
+
+  @operator ['gcd'], 2, (other) ->
+    step = (a, b) -> if b > 0 then -> step b, a % b else a
+    [a, b] = [Math.abs(@val), Math.abs(other.val)]
+    val = if a > b then trampoline step a, b else trampoline step b, a
+    new CheckedInt val
+
+  toString: -> "" + @val
+
+
 # --------------------------------------------------------------------
 # Exporting.
 # --------------------------------------------------------------------
@@ -266,8 +344,10 @@
 exports.LongInt = LongInt

 if quicktest
-  a = new LongInt 9950
+  a = new CheckedInt 9950
+  log "a = #{a}"
   a2 = a['*'] a
+  log "a * a = #{a2}"
   a3 = a2['*'] a
   log "(#{a}**3 + 1) / #{a}**2 = #{a3.plus(1).div a2} (#{a3.plus(1).mod a2})"
   log ""
@@ -282,3 +362,10 @@
   d = new LongInt 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31
   log "#{c} gcd #{d} = #{c.gcd d} (expected #{29 * 31})"
   log ""
+
+  e = new CheckedInt(5).pow 5
+  log "5**5 = #{e}"
+  log "class of 5**5 is #{e.constructor.name}"
+  f = new CheckedInt(5).pow 6
+  log "5**6 = #{f}"
+  log "class of 5**6 is #{f.constructor.name}"


save a variable for less typing.

save a variable for less typing.

--- lib/converter.coffee
+++ lib/converter.coffee
@@ -20,11 +20,12 @@

 getConvertSettings = (req) ->
-	if req.body.convert
-		res = convertSettings[req.body.convert]
-		res.input = req.body.input if req.body.input
-		res.output = req.body.output if req.body.output
-		res.args = req.body.args if req.body.args
+	b = req.body
+	if b.convert
+		res = convertSettings[b.convert]
+		res.input = b.input if b.input
+		res.output = b.output if b.output
+		res.args = b.args if b.args
 		return res
 	else
 		convertSettings[defaultConvert]


Support reparenting across groups

Support reparenting across groups

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -690,7 +690,7 @@
   message.updators = authors
   diff = _.clone message
   for author in authors
-    message["authors." + escapeUser author] = now
+    message["authors.#{escapeUser author}"] = now
   Messages.update id,
     $set: message
   diff.id = id
@@ -785,12 +785,27 @@
   if parent?
     _messageAddChild child, parent, position
   if root != cmsg.root
-    Messages.update child,
-      $set: root: root
+    update = root: root
+    if group != cmsg.group
+      update.group = group
+      ## First MessagesDiff has the initial group; add Diff if it changes.
+      ## (Unclear whether we should track group at all, though.)
+      now = new Date
+      username = Meteor.user().username
+      MessagesDiff.insert
+        id: cmsg._id
+        group: cmsg.group
+        updated: now
+        updators: [username]
+      Messages.update child,
+        $set: _.extend {"authors.#{escapeUser username}": now}, update
+    else
+      Messages.update child,
+        $set: update
     EmojiMessages.update
       message: child
     ,
-      $set: root: root
+      $set: update
     ,
       multi: true
     if cmsg.root?
@@ -815,13 +830,13 @@
       Messages.update
         root: child
       ,
-        $set: root: root ? child  ## actually must be root
+        $set: update  # root: root ? child  ## actually must be root
       ,
         multi: true
       EmojiMessages.update
         root: child
       ,
-        $set: root: root ? child  ## actually must be root
+        $set: update  # root: root ? child  ## actually must be root
       ,
         multi: true
       _noLongerRoot child if root?


Vertical brinstar shaft rooms

Vertical brinstar shaft rooms

--- src/javascript/game/map/room_data.coffee
+++ src/javascript/game/map/room_data.coffee
@@ -1,4 +1,90 @@
 roomData = []
+
+# Vertical shaft, door on left
+roomData[0x03] =
+  chunks: [
+    # [col, row, chunkId]
+    [0x0,0x0, 0x0B]
+    [0x0,0x5, 0x0B]
+    [0x0,0xA, 0x0B]
+
+    [0xE,0x0, 0x0B]
+    [0xE,0x8, 0x0B]
+    [0xE,0xC, 0x0B]
+
+    [0x2,0x0, 0x09]
+    [0x7,0xC, 0x09]
+    [0x6,0x5, 0x0A]
+    [0x2,0x9, 0x0A]
+    [0xB,0x8, 0x0A]
+  ]
+  enemies: [
+    [0xA,0xB, 'basicZoomer']
+    #TODO [0x7,0x4, 'basicZoomer'] # angry/purple zoomer
+    #TODO [0x5,0xC, 'skimmer']
+    #TODO [0x8,0x0, 'skimmer']
+    #TODO [0x3,0x5, 'angrySkimmer']
+    #TODO [0x7,0x0, 'angrySkimmer']
+  ]
+  fixtures:
+    doors: [
+      ['blue-left', 0xF, 0x5]
+    ]
+
+# Vertical shaft, door on left
+roomData[0x04] =
+  chunks: [
+    # [col, row, chunkId]
+    [0x0,0x0, 0x0B]
+    [0x0,0x8, 0x0B]
+    [0x0,0xC, 0x0B]
+
+    [0xE,0x0, 0x0B]
+    [0xE,0x5, 0x0B]
+    [0xE,0xA, 0x0B]
+
+    [0x4,0xA, 0x0A]
+    [0x2,0x8, 0x0A]
+    [0xC,0x9, 0x0A]
+    [0x6,0xB, 0x0A]
+    [0x7,0x4, 0x09]
+  ]
+  enemies: [
+    [0x9,0x3, 'basicZoomer']
+    # [0x5,0x4, 'skimmer']
+    # [0xB,0x4, 'skimmer']
+  ]
+  fixtures:
+    doors: [
+      ['blue-right', 0x0, 0x5]
+    ]
+
+# Vertical shaft
+roomData[0x06] =
+  chunks: [
+    # [col, row, chunkId]
+    [0x0,0x0, 0x0B]
+    [0x0,0x5, 0x0B]
+    [0x0,0xA, 0x0B]
+
+    [0xE,0x0, 0x0B]
+    [0xE,0x5, 0x0B]
+    [0xE,0xA, 0x0B]
+
+    [0x2,0x1, 0x0A]
+    [0x7,0x3, 0x0A]
+    [0x3,0x7, 0x0A]
+    [0xA,0x8, 0x0A]
+    [0x6,0xB, 0x09]
+  ]
+  enemies: [
+    [0x8,0x2, 'basicZoomer']
+    [0x4,0x6, 'basicZoomer']
+    [0x8,0xA, 'basicZoomer']
+    # [0xA,0x5, 'basicZoomer']  # TODO angry/purple zoomer
+    # [0xC,0x3, 'skimmer']
+    # [0x3,0xB, 'skimmer']
+  ]

 # Main entry: two towers + 2 zoomers
 roomData[0x09] =


Allow . in component paths

Allow . in component paths

--- src/lib/Fbp.coffee
+++ src/lib/Fbp.coffee
@@ -1,6 +1,6 @@
 class Fbp
     matchPort: new RegExp "([A-Z\.]+)"
-    matchComponent: new RegExp "([A-Za-z]+)\\(([A-Za-z\/]+|)\\)"
+    matchComponent: new RegExp "([A-Za-z]+)\\(([A-Za-z\/\.]+|)\\)"
     matchInitial: new RegExp "\'(.+)\'"
     matchConnection: new RegExp "\-\>"
     matchSeparator: new RegExp "[\\s,\\n]"


make vmp-plugin dev easier

make vmp-plugin dev easier

--- lib/developer.coffee
+++ lib/developer.coffee
@@ -35,6 +35,8 @@
   reloadPackages: ->
     packages = settings.get('devReloadPackages') ? []
     packages.push('vim-mode-plus')
+
+    # Deactivate
     for packName in packages
       pack = atom.packages.getLoadedPackage(packName)

@@ -50,9 +52,11 @@
           .forEach (p) ->
             delete require.cache[p]

-        atom.packages.loadPackage(packName)
-        console.log "+ activating #{packName}"
-        atom.packages.activatePackage(packName)
+    # Activate
+    for packName in packages
+      atom.packages.loadPackage(packName)
+      console.log "+ activating #{packName}"
+      atom.packages.activatePackage(packName)

   toggleReloadPackagesOnSave: ->
     return unless editor = atom.workspace.getActiveTextEditor()


Fixed gulp-watch implementation for errors

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -18,7 +18,9 @@
 	gulp.src [paths.test, paths.src], { read: false }
 	.pipe watch { emit: 'all' }, (files) ->
 		files
-		.pipe mocha()
+		.pipe mocha {reporter: 'spec'}
 		.on 'error', (err) ->
 			if not /tests? failed/.test(err.stack)
 				util.log err.stack
+			@emit 'end'
+	return


upload to ftp only on 'prod' task

upload to ftp only on 'prod' task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -102,8 +102,7 @@
   gulp.watch 'coffee/make_barrier.coffee', ['lint']
   gulp.watch ['jade/*.jade', 'res/*.json'], ['jade']
   gulp.watch 'css/main.css', ['css']
-  gulp.watch 'dist/*', ['ftp']

 gulp.task 'recompile', gulpsync.sync [ ['lint', 'coffee'], 'concat', 'uglify']
-gulp.task 'prod', ['set_prod', 'recompile', 'jade', 'css']
+gulp.task 'prod', ['set_prod', 'recompile', 'jade', 'css', 'ftp']
 gulp.task 'default', ['recompile', 'css', 'jade', 'connect', 'open', 'watch']


found a node module that could help with the sass import dependencies, just adding a comment for now

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -30,6 +30,7 @@
       root = path.dirname(config)
       # NOTE: skipped tracking sass import dependencies...
       # Thus, compile the whole project if a given file is _{underscored} for @import.
+      # Next, https://github.com/lox/sass-graph could help with optimizing it further.
       file = "" if typeof(file) isnt "string" or path.basename(file).indexOf("_") is 0
       exec "compass compile -c #{config} #{file}"
       , {cwd: root}


Fix clean

Fix clean

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -11,8 +11,10 @@
 libFiles = ['./lib/**/*.js']

 gulp.task 'clean-build', ->
-  gulp.src libFiles
+  gulp.src './lib/*'
     .pipe clean { force: true }
+
+gulp.task 'clean', ['clean-build']

 gulp.task 'lint', ->
   gulp.src srcFiles.concat ['./*.coffee', './spec/**/*.coffee']


Fix results html generation, quotes escaping

Old approach relied on constructing html text by hand, resulting
for example in invalid html if item name contained quotes.
It is better to rely on jQuery's html constructing methods to
handle this.

--- coffee/selectorablium.coffee
+++ coffee/selectorablium.coffee
@@ -314,17 +314,24 @@
         text = 'No results found'
         ## TODO: if @state isnt 'XHR'
         # text = 'Loading...' if query.length >= @config.minCharsForRemoteSearch
-        items_templates.push "

#{text}

"
+        $li = $('
').append($("
", class: 'empty-message').text(text)).get(0)
+        items_templates.push $li
       else
         for item in @structure
           class_name = 'item'
           class_name += ' new' if item.fresh is true
           class_name += ' selected' if item.selected is true

-          items_templates.push "
#{item.template}
"
+          $li = $('
').append($("",
+            'href': '#',
+            'class': class_name,
+            'data-value': item.id,
+            'data-text': item.name).html(item.template)).get(0)
+
+          items_templates.push $li
           item.fresh    = false

-      items_templates.join('\n')
+      items_templates

     ## HELPERS ##
     _createAccentIndependentRE: (query)->


IMPORTANT createCheckpoint by `p`, `P` itself to preserve cursor information.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -183,6 +183,19 @@
   getBufferCheckpoint: (purpose) ->
     @bufferCheckpointByPurpose?[purpose]

+  deleteBufferCheckpoint: (purpose) ->
+    if @bufferCheckpointByPurpose?
+      delete @bufferCheckpointByPurpose[purpose]
+
+  groupChangesSinceBufferCheckpoint: (purpose) ->
+    console.log "called groupChangesSinceCheckpoint #{purpose}"
+    if checkpoint = @getBufferCheckpoint(purpose)
+      console.log "checkpoint found: #{purpose}"
+      @editor.groupChangesSinceCheckpoint(checkpoint)
+    else
+      console.log "checkpoint NOT found: #{purpose}"
+    @deleteBufferCheckpoint(purpose)
+
   # Main
   execute: ->
     canMutate = true
@@ -195,7 +208,7 @@
         @mutateSelection(selection, stopMutation)
       @restoreCursorPositionsIfNecessary()

-    @editor.groupChangesSinceCheckpoint(@getBufferCheckpoint('undo'))
+    @groupChangesSinceBufferCheckpoint('undo')

     # Even though we fail to select target and fail to mutate,
     # we have to return to normal-mode from operator-pending or visual
@@ -571,14 +584,19 @@
       row = cursor.getBufferRow()
       switch @location
         when 'before'
-          setTextAtBufferPosition(@editor, [row, 0], text)
+          range = setTextAtBufferPosition(@editor, [row, 0], text)
+          @groupChangesSinceBufferCheckpoint('undo')
+          range
         when 'after'
           unless isEndsWithNewLineForBufferRow(@editor, row)
             eol = getEndOfLineForBufferRow(@editor, row)
             range = setTextAtBufferPosition(@editor, eol, "\n" + text.trimRight())
+            @groupChangesSinceBufferCheckpoint('undo')
             range.traverse([1, 0], [0, 0])
           else
-            setTextAtBufferPosition(@editor, [row + 1, 0], text)
+            range = setTextAtBufferPosition(@editor, [row + 1, 0], text)
+            @groupChangesSinceBufferCheckpoint('undo')
+            range
     else
       if @isMode('visual', 'linewise')
         unless selection.getBufferRange().end.column is 0


:bug: fix #57 change operation wasn't atomic when multi-cursor

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -860,9 +860,10 @@

     @setTextToRegister @editor.getSelectedText()
     text = if @target.isLinewise?() then "\n" else ""
-    for s in @editor.getSelections()
-      range = s.insertText(text, autoIndent: true)
-      s.cursor.moveLeft() unless range.isEmpty()
+    @editor.transact =>
+      for s in @editor.getSelections()
+        range = s.insertText(text, autoIndent: true)
+        s.cursor.moveLeft() unless range.isEmpty()
     super

 class Substitute extends Change


fix line numbers for function calls

fix line numbers for function calls

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -81,7 +81,7 @@
       else
         res.push ""

-    comments = builder.comments_not_done_to(1E10)
+    comments = builder.comments_not_done_to(1e10)
     if comments
       res.push comments

@@ -97,7 +97,7 @@
     @transformer = new Transformer
     #@options = options

-  # `build()`
+  # `build()`
   # The main entry point.

   # This finds the appropriate @builder function for `node` based on it's type,
@@ -163,7 +163,7 @@

     if node.parenthesized then paren(out) else out

-  # `transform()`
+  # `transform()`
   # Perform a transformation on the node, if a transformation function is
   # available.

@@ -173,12 +173,12 @@
   # `body()`
   # Works like `@build()`, and is used for code blocks. It cleans up the returned
   # code block by removing any extraneous spaces and such.
-
+
   body: (node, opts={}) ->
     str = @build(node, opts)
     str = blockTrim(str)
     str = unshift(str)
-
+
     if str.length > 0 then str else ""

   # ## The builders
@@ -188,7 +188,7 @@
   #
   # These are invoked using the main entry point, `Builder#build()`.

-  # `script`
+  # `script`
   # This is the main entry point.

   'script': (n, opts={}) ->
@@ -201,7 +201,7 @@
     c.toString()

-  # `property_identifier`
+  # `property_identifier`
   # A key in an object literal.

   'property_identifier': (n) ->
@@ -216,7 +216,7 @@
     else
       @l(n)+strEscape str

-  # `identifier`
+  # `identifier`
   # Any object identifier like a variable name.

   'identifier': (n) ->
@@ -236,7 +236,7 @@
     else
       @l(n)+unreserve n

-  # `id_param`
+  # `id_param`
   # Function parameters. Belongs to `list`.

   'id_param': (n) ->
@@ -245,7 +245,7 @@
     else
       @l(n)+@id n

-  # `return`
+  # `return`
   # A return statement. Has `n.value` of type `id`.

   'return': (n) ->
@@ -255,7 +255,7 @@
     else
       @l(n)+"return #{@build(n.value)}\n"

-  # `;` (aka, statement)
+  # `;` (aka, statement)
   # A single statement.

   ';': (n) ->
@@ -277,7 +277,7 @@
     else
       @build(n.expression) + "\n"

-  # `new` + `new_with_args`
+  # `new` + `new_with_args`
   # For `new X` and `new X(y)` respctively.

   'new': (n) -> @l(n)+"new #{@build n.left()}"
@@ -382,7 +382,7 @@
     else
       @l(n)+"#{sign}#{@build n.left()}"

-  # `=` (aka, assignment)
+  # `=` (aka, assignment)
   # For `a = b` (but not `var a = b`: that's `var`).

   '=': (n) ->
@@ -393,14 +393,14 @@

     @l(n)+"#{@build n.left()} #{sign} #{@build n.right()}"

-  # `,` (aka, comma)
+  # `,` (aka, comma)
   # For `a = 1, b = 2'

   ',': (n) ->
     list = _.map n.children, (item) => @l(item)+@build(item) + "\n"
     list.join('')

-  # `regexp`
+  # `regexp`
   # Regular expressions.

   'regexp': (n) ->
@@ -425,17 +425,17 @@
   'string': (n) ->
     @l(n)+ strEscape n.value

-  # `call`
+  # `call`
   # A Function call.
   # `n.left` is an `id`, and `n.right` is a `list`.

   'call': (n) ->
     if n.right().children.length == 0
-      @l(n)+"#{@build n.left()}()"
-    else
-      @l(n)+"#{@build n.left()}(#{@build n.right()})"
-
-  # `call_statement`
+      "#{@build n.left()}()"+@l(n)
+    else
+      "#{@build n.left()}(#{@build n.right()})"+@l(n)
+
+  # `call_statement`
   # A `call` that's on it's own line.

   'call_statement': (n) ->
@@ -449,11 +449,11 @@
     left = paren(left)  if n.left().isA('function')

     if n.right().children.length == 0
-      @l(n)+"#{left}()"
-    else
-      @l(n)+"#{left} #{@build n.right()}"
-
-  # `list`
+      "#{left}()"+@l(n)
+    else
+      "#{left} #{@build n.right()}"+@l(n)
+
+  # `list`
   # A parameter list.

   'list': (n) ->
@@ -469,7 +469,7 @@
     ids = ids.join(', ')
     @l(n)+"delete #{ids}\n"

-  # `.` (scope resolution?)
+  # `.` (scope resolution?)
   # For instances such as `object.value`.

   '.': (n) ->
@@ -523,7 +523,7 @@
     c.scope @body(n.block)
     @l(n)+c

-  # `?` (ternary operator)
+  # `?` (ternary operator)
   # For `a ? b : c`. Note that these will always be parenthesized, as (I
   # believe) the order of operations in JS is different in CS.

@@ -623,7 +623,7 @@
           c.add @l(item)+", #{@build item.caseLabel}\n"
         else
           c.add @l(item)+"  when #{@build item.caseLabel}"
-
+
       if @body(item.statements).length == 0
         fall_through = true
       else
@@ -644,7 +644,7 @@
     else
       @l(n)+"[ #{@list n} ]"

-  # `property_init`
+  # `property_init`
   # Belongs to `object_init`;
   # left is a `identifier`, right can be anything.

@@ -654,7 +654,7 @@
     right.is_property_value = true
     "#{@property_identifier left}: #{@build right}"

-  # `object_init`
+  # `object_init`
   # An object initializer.
   # Has many `property_init`.

@@ -673,7 +673,7 @@
       c = "{#{c}}"  if options.brackets?
       c

-  # `function`
+  # `function`
   # A function. Can be an anonymous function (`function () { .. }`), or a named
   # function (`function name() { .. }`).

@@ -700,7 +700,8 @@
     else
       c.add "\n"

-    @l(n)+c
+    #@l(n)+c
+    c

   'var': (n) ->
     list = _.map n.children, (item) =>
@@ -725,7 +726,7 @@
   'block': (args...) ->
     @script.apply @, args

-  # `unsupported()`
+  # `unsupported()`
   # Throws an unsupported error.
   'unsupported': (node, message) ->
     throw new UnsupportedError("Unsupported: #{message}", node)


Add cpu count and total memory

Add cpu count and total memory

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -1,3 +1,4 @@
+os = require 'os'
 request = require 'request'

 module.exports =
@@ -12,6 +13,8 @@
           screen_resolution: screen.width + "x" + screen.height
           pixel_ratio: window.devicePixelRatio
           version: atom.getVersion()
+          cpus: os.cpus()?.length ? 0
+          memory: os.totalmem() ? 0
         context:
           packages: @getPackageData()
           themes: @getThemeData()


Added require('fs'), require('path') and fixed path.join syntax.

--- lib/resolver.coffee
+++ lib/resolver.coffee
@@ -1,4 +1,6 @@
 {exists} = require './utils'
+path = require 'path'
+fs = require 'fs'

 # criteria for whether a require string is relative, rather than absolute
 # absolute require strings will scan on the defined require paths (@domains)
@@ -83,7 +85,7 @@
     continue if noTryFolder # already done this test
     return [found, dom, false] if found = @finder(@domains[dom], absReq + '/index')

-    packageJSONPath = path.join(absReq + '/package.json')
+    packageJSONPath = path.join(absReq, 'package.json')
     if @finder(@domains[dom], packageJSONPath)
       json = JSON.parse(fs.readFileSync(path.join(@domains[dom], packageJSONPath)))
       mainPath = path.join(absReq, json.main)


Don’t treat as loaded if the element was removed

--- concerns/loads-assets.coffee
+++ concerns/loads-assets.coffee
@@ -20,8 +20,8 @@

 	# Set loaded to true immediately if loaded before the load event fires
 	mounted: ->
-		@imageLoaded = true if @$refs?.image?.complete
-		@videoLoaded = true if @$refs?.video?.readyState > 3
+		@imageLoaded = true if @$refs.image?.complete
+		@videoLoaded = true if @$refs.video?.readyState > 3

 	computed:

@@ -58,8 +58,12 @@

 	methods:

-		# Handle an asset being loaded
-		onAssetLoad: (assetType) -> @["#{assetType}Loaded"] = true
+		# Handle an asset being loaded. If the element was removed before the load
+		# finished, don't continue.  I've seen this in edge cases but not sure
+		# what scenario actually lead to that case.
+		onAssetLoad: (assetType) ->
+			return unless @$refs[assetType]
+			@["#{assetType}Loaded"] = true

 		# Manually start loading
 		load: -> @shouldLoad = true


GenerateKonfig: update config

GenerateKonfig: update config

--- config/generateKonfig.coffee
+++ config/generateKonfig.coffee
@@ -251,8 +251,8 @@
     uri                           : { address: options.customDomain.public }
     misc                          : { claimGlobalNamesForUsers: no , debugConnectionErrors: yes, updateAllSlugs: false }
     # TODO: average request count per hour for a user should be measured and a reasonable limit should be set
-    nodejsRateLimiter             : { enabled: no,  guestRules: [{ interval: 3600, limit: 5000 }], userRules: [{ interval: 3600, limit: 10000 }] } # limit: request limit per rate limit window, interval: rate limit window duration in seconds
-    nodejsRateLimiterForApi       : { enabled: yes, guestRules: [{ interval: 60,   limit: 10 }],   userRules: [{ interval: 60,   limit: 300 }] }   # limit: request limit per rate limit window, interval: rate limit window duration in seconds
+    nodejsRateLimiter             : { enabled: no, guestRules: [{ interval: 3600, limit: 5000 }], userRules: [{ interval: 3600, limit: 10000 }] } # limit: request limit per rate limit window, interval: rate limit window duration in seconds
+    nodejsRateLimiterForApi       : { enabled: no, guestRules: [{ interval: 60,   limit: 10 }],   userRules: [{ interval: 60,   limit: 300 }] }   # limit: request limit per rate limit window, interval: rate limit window duration in seconds
     webserver                     : { port: 8080, useCacheHeader: no }
     social                        : { port: 3030, login: "#{credentials.rabbitmq.login}", queueName: options.socialQueueName, kitePort: 8760, kiteKey: "#{credentials.kiteHome}/kite.key" }
     boxproxy                      : { port: parseInt(options.publicPort, 10) }


Add postgres configuration

Add postgres configuration

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateKonfig.coffee
+++ config/generateKonfig.coffee
@@ -169,6 +169,7 @@
     redis                         : credentials.redis.url
     monitoringRedis               : credentials.monitoringRedis.url
     mongo                         : credentials.mongo
+    postgres                      : credentials.postgres
     mq                            : credentials.rabbitmq
     terraformer                   : credentials.terraformer
     recurly                       : credentials.recurly


:art: consolidate sharable config for Search and AtomScan provider

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -109,7 +109,7 @@
     focusOnStartCondition:
       default: 'always'
       description: "[Experiment] In-eval if this is really useful( I don't use this )"
-      enum: ['never', 'always', 'no-input']
+      enum: ['always', 'never', 'no-input']
     negateNarrowQueryByEndingExclamation: false
     autoPreview: true
     autoPreviewOnQueryChange: true
@@ -122,6 +122,22 @@
     collapsed: true
     properties: complimentField(properties, true)
   }
+
+newProviderConfigForSearchAndAtomScan = (otherProperties={}) ->
+  properties =
+    revealOnStartCondition:
+      default: 'always'
+      enum: ['always', 'never']
+    focusOnStartCondition:
+      default: 'always'
+      enum: ['always', 'never']
+    caseSensitivityForSearchTerm:
+      default: 'smartcase'
+      enum: ['smartcase', 'sensitive', 'insensitive']
+    rememberIgnoreCaseForByHandSearch: false
+    rememberIgnoreCaseForByCurrentWordSearch: false
+    searchWholeWord: false
+  newProviderConfig(_.extend(properties, otherProperties))

 providerSettings =
   Scan: newProviderConfig(
@@ -137,13 +153,7 @@
       enum: ['smartcase', 'sensitive', 'insensitive']
       description: "Search term is first word of query, is used as search term"
   )
-  Search: newProviderConfig(
-    caseSensitivityForSearchTerm:
-      default: 'smartcase'
-      enum: ['smartcase', 'sensitive', 'insensitive']
-    rememberIgnoreCaseForByHandSearch: false
-    rememberIgnoreCaseForByCurrentWordSearch: false
-    searchWholeWord: false
+  Search: newProviderConfigForSearchAndAtomScan(
     searcher:
       default: 'ag'
       enum: ['ag', 'rg']
@@ -157,14 +167,7 @@
         You can toggle this value by command `narrow:toggle-search-start-by-double-click`
         """
   )
-  AtomScan: newProviderConfig(
-    caseSensitivityForSearchTerm:
-      default: 'smartcase'
-      enum: ['smartcase', 'sensitive', 'insensitive']
-    rememberIgnoreCaseForByHandSearch: false
-    rememberIgnoreCaseForByCurrentWordSearch: false
-    searchWholeWord: false
-  )
+  AtomScan: newProviderConfigForSearchAndAtomScan()
   Symbols: newProviderConfig(revealOnStartCondition: 'on-input')
   GitDiffAll: newProviderConfig()
   Fold: newProviderConfig(revealOnStartCondition: 'on-input')


:lipstick: Extract setBuffer/buildDisplayBuffer methods

--- src/app/edit-session.coffee
+++ src/app/edit-session.coffee
@@ -41,30 +41,33 @@
       project.editSessions.push(this)
       @state = optionsOrState
       {tabLength, softTabs, @softWrap} = @state.toObject()
-      @buffer = project.bufferForId(@state.get('bufferId'))
-
-      @displayBuffer = new DisplayBuffer(@buffer, { tabLength })
-      @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
-
-      for marker in @findMarkers(@getSelectionMarkerAttributes())
-        @addSelection(marker)
+      @setBuffer(project.bufferForId(@state.get('bufferId')))
+      @buildDisplayBuffer({tabLength})
+      @addSelection(marker) for marker in @findMarkers(@getSelectionMarkerAttributes())
       @setScrollTop(@state.get('scrollTop'))
       @setScrollLeft(@state.get('scrollLeft'))
     else
-      {@buffer, tabLength, softTabs, @softWrap} = optionsOrState
+      {buffer, tabLength, softTabs, @softWrap} = optionsOrState
       @state = telepath.Document.create
         deserializer: 'EditSession'
         version: @constructor.version
         scrollTop: 0
         scrollLeft: 0
-
-      @displayBuffer = new DisplayBuffer(@buffer, { tabLength })
-      @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
+      @setBuffer(buffer)
+      @buildDisplayBuffer({tabLength})
       @addCursorAtScreenPosition([0, 0])

+    @state.on 'changed', ({key, newValue}) =>
+      switch key
+        when 'scrollTop'
+          @trigger 'scroll-top-changed', newValue
+        when 'scrollLeft'
+          @trigger 'scroll-left-changed', newValue
+
+  setBuffer: (@buffer) ->
+    @buffer.retain()
     @languageMode = new LanguageMode(this, @buffer.getExtension())
     @softTabs = @buffer.usesSoftTabs() ? softTabs ? true
-    @buffer.retain()
     @subscribe @buffer, "path-changed", =>
       project.setPath(path.dirname(@getPath())) unless project.getPath()?
       @trigger "title-changed"
@@ -74,17 +77,12 @@
     @subscribe @buffer, "modified-status-changed", => @trigger "modified-status-changed"
     @preserveCursorPositionOnBufferReload()

+  buildDisplayBuffer: ({tabLength}) ->
+    @displayBuffer = new DisplayBuffer(@buffer, { tabLength })
+    @subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
     @subscribe @displayBuffer, "changed", (e) =>
       @trigger 'screen-lines-changed', e
-
-    @displayBuffer.on 'grammar-changed', => @handleGrammarChange()
-
-    @state.on 'changed', ({key, newValue}) =>
-      switch key
-        when 'scrollTop'
-          @trigger 'scroll-top-changed', newValue
-        when 'scrollLeft'
-          @trigger 'scroll-left-changed', newValue
+    @subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()

   getViewClass: ->
     require 'editor'


Fix DOM.serializeForm: handle checkbox elements

--- src/marbles/dom.js.coffee
+++ src/marbles/dom.js.coffee
@@ -240,6 +240,7 @@
         DOM.formElementValue(el)

       continue if el.type is 'radio' && !el.checked
+      continue if el.type is 'checkbox' && !el.checked

       if options.expand_nested && el.name.match(/^([^\[]+)\[([^\[]+)\]/)
         name = RegExp.$1


change_wordfragments_add_sc

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -345,7 +345,7 @@
     'prefix': 'wordguess'
     'body': """
       [TYPE WordGuess]
-      Audio: $1
+      Pic: $1
       Text: $2
       TR: $3
     """
@@ -353,19 +353,28 @@
     'prefix': 'chooseall'
     'body': """
       [TYPE ChooseAllWords]
-      Opts:
-        Text(checked=true): $1
+      Text: $1
+      Opts:
         Text(checked=true): $2
-        Text: $3
+        Text(checked=true): $3
         Text: $4
         Text: $5
         Text: $6
+        Text: $7
     """
   'WordFragments':
-    'prefix': 'wordfragmentsguess'
-    'body': """
-      [TYPE WordGuess]
-      Pic: $1
-      Audio: $2
-      Text: $3
-    """
+    'prefix': 'wordfragments'
+    'body': """
+      [TYPE wordfragments]
+      Pic: $1
+      Audio: $2
+      Text: $3
+    """
+  'SentenceCompletion':
+    'prefix': 'SentenceCompletion'
+    'body': """
+      [TYPE SentenceCompletion]
+      Audio: $1
+      Pic: $2
+      Text: $3
+    """


Fix DOM.serializeForm to handle file inputs correctly: value is the FileList

--- src/marbles/dom.js.coffee
+++ src/marbles/dom.js.coffee
@@ -216,9 +216,11 @@
   serializeForm: (form) ->
     params = {}
     for el in DOM.querySelectorAll('[name]', form)
-      name = el.name
-      value = DOM.formElementValue(el)
-      params[name] = value
+      value = if el.type == 'file'
+        el.files
+      else
+        DOM.formElementValue(el)
+      params[el.name] = value
     params

   setElementValue: (el, val) ->


Use params instead of loadSettings

Use params instead of loadSettings

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -100,12 +100,12 @@

   # Call .loadOrCreate instead
   constructor: (params={}) ->
-    {@applicationDelegate, @window, @document} = params
+    {@applicationDelegate, @window, @document, configDirPath} = params

     @state = {version: @constructor.version}

     @loadTime = null
-    {devMode, safeMode, resourcePath, configDirPath} = @getLoadSettings()
+    {devMode, safeMode, resourcePath} = @getLoadSettings()

     @emitter = new Emitter
     @disposables = new CompositeDisposable


Layer

Layer

- Fixed "use_element_width_as_min_width": Really set "min-width" on layer
  if this option is used.

--- src/base/Layer/Layer.coffee
+++ src/base/Layer/Layer.coffee
@@ -403,9 +403,6 @@
 		if @__element
 			dim_element = CUI.DOM.getDimensions(@__element)

-			if @_use_element_width_as_min_width
-				dim_layer.borderBoxWidth = Math.max(dim_layer.borderBoxWidth, dim_element.borderBoxWidth)
-
 		else if @_show_at_position
 			dim_element =
 				viewportTop: @_show_at_position.top
@@ -442,12 +439,6 @@
 			vp.dim_element = dim_element
 			vp.dim_pointer = dim_pointer[placement]

-			if @_use_element_width_as_min_width
-				vp.set_width = false
-			else
-				vp.set_width = true
-
-			#

 			switch placement
 				when "c"
@@ -548,9 +539,6 @@
 			layer_pos = vp.layer_pos = {}
 			pointer_pos = vp.pointer_pos = {}

-			# layer_pos.set_width = false
-			# layer_pos.set_height = false
-
 			# number of times we need to cut the layer
 			# to make it fit into the viewport
 			vp.cuts = 0
@@ -562,16 +550,12 @@
 				when "both"
 					layer_pos.width = vp.width
 					layer_pos.height = vp.height
-					# layer_pos.set_height = true
-					# layer_pos.set_width = true
 				when "vertical"
 					layer_pos.height = vp.height
 					layer_pos.width = dim_layer.borderBoxWidth
-					# layer_pos.set_height = true
 				when "horizontal"
 					layer_pos.width = vp.width
 					layer_pos.height = dim_layer.borderBoxHeight
-					# layer_pos.set_width = true
 				else
 					layer_pos.width = dim_layer.borderBoxWidth
 					layer_pos.height = dim_layer.borderBoxHeight
@@ -869,12 +853,18 @@
 		if vp.layer_pos.width < 10 or vp.layer_pos.height < 10
 			console.warn("Layer: DIM is very small, layer might not be visible. Placement:", placement, "Dimensions:", vp, "Layer:", @__layer.DOM)

+		if @_element and @_use_element_width_as_min_width
+			minWidth = dim_element.borderBoxWidth
+		else
+			minWidth = undefined
+
 		# set layer
 		CUI.DOM.setStyle @__layer.DOM,
 			top: vp.layer_pos.top
 			left: vp.layer_pos.left
-			width: if vp.set_width then vp.layer_pos.width else ""
+			width: vp.layer_pos.width
 			height: vp.layer_pos.height
+			minWidth: minWidth
 			# maxWidth: vp.width + vp.overlap_width
 			# maxHeight: vp.height + vp.overlap_height
 			margin: 0


Use hash (too) for local marks.

Use hash (too) for local marks.

First, I *very rarely* use local marks.  They just don't seem as useful
in a browser as they are in a text editor.

However, on a page like GMail, I do often want to jump back and forward
between my labels.  These are different locations hashes (anchors).

The idea here is to make local marks useful for this case.

If the scroll positions (X and Y) are both 0, and the hash (anchor) is
present in the mark and non-empty, then change the hash instead of
scrolling.

On first tests, this appears to work nicely for changing labels in
GMail.  I'm sure there are other uses.

This change shouldn't interfere with use cases where the user has
scrolled within the page.

--- content_scripts/marks.coffee
+++ content_scripts/marks.coffee
@@ -15,7 +15,7 @@
     "vimiumMark|#{window.location.href.split('#')[0]}|#{keyChar}"

   getMarkString: ->
-    JSON.stringify scrollX: window.scrollX, scrollY: window.scrollY
+    JSON.stringify scrollX: window.scrollX, scrollY: window.scrollY, hash: window.location.hash

   setPreviousPosition: ->
     markString = @getMarkString()
@@ -84,7 +84,10 @@
               if markString?
                 @setPreviousPosition()
                 position = JSON.parse markString
-                window.scrollTo position.scrollX, position.scrollY
+                if position.hash and position.scrollX == 0 and position.scrollY == 0
+                  window.location.hash = position.hash
+                else
+                  window.scrollTo position.scrollX, position.scrollY
                 @showMessage "Jumped to local mark", keyChar
               else
                 @showMessage "Local mark not set", keyChar


use observe instead of always check config

--- lib/atom-ternjs-documentation.coffee
+++ lib/atom-ternjs-documentation.coffee
@@ -6,6 +6,7 @@
   documentation: null
   disposables: []
   orientation: null
+  position: null

   constructor: (state = {}) ->
     @documentation = new DocumentationView()
@@ -15,18 +16,15 @@
     @registerEvents()

   setPosition: ->
-
-    position = atom.config.get('atom-ternjs.docsPosition')
-
-    if position is 'force top'
+    if @position is 'force top'
       @documentation.classList.add('top')
       return

-    if position is 'force bottom'
+    if @position is 'force bottom'
       @documentation.classList.add('bottom')
       return

-    if position is 'force middle'
+    if @position is 'force middle'
       @documentation.classList.add('middle')
       return

@@ -46,7 +44,10 @@
     @documentation.classList.remove('bottom', 'top', 'middle')

   registerEvents: ->
-    @disposables.push atom.config.observe('atom-ternjs.docsPosition', => @removeClasses())
+    @disposables.push atom.config.observe('atom-ternjs.docsPosition', =>
+      @position = atom.config.get('atom-ternjs.docsPosition')
+      @removeClasses()
+    )

   set: (data) ->
     return unless data.word and data.label


digitメソッドを追加

digitメソッドを追加

--- src/coffee/MathUtils.coffee
+++ src/coffee/MathUtils.coffee
@@ -4,4 +4,13 @@
 			_randomNum = Math.random() * (maxNum - minNum) + minNum
 		else
 			_randomNum = Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum
-		return _randomNum+		return _randomNum
+
+#	数値を桁揃えして文字列で返却
+	@digit: (num, disitNum) ->
+		_string = num.toString()
+		_digitCount = disitNum - num.toString().length
+		if (_digitCount > 0)
+			for i in [0.._digitCount - 1]
+				_string = "0" + _string
+		return _string


fixed weekly, daily, and one time events in week view

--- drm-calendar.coffee
+++ drm-calendar.coffee
@@ -67,6 +67,10 @@

             self.hours = [
                 {
+                    name: 'All Day Event'
+                    time: null
+                }
+                {
                     name: '12am'
                     time: 0
                 }
@@ -564,7 +568,12 @@
             @events.push obj
             @addEventsToCalendar @events[obj.id]

-        addNewCalendarEvent: (events, calendarItem) =>
+        addNewCalendarEvent: (events, dates) =>
+            calendarInner = @calendar.find "div.#{@calendarInnerClass}"
+            if @view is 'month'
+                calendarItem = calendarInner.find "td[data-date=#{dates}]"
+            else if @view is 'week'
+                calendarItem = calendarInner.find "td[data-date=#{dates}][data-hour='All Day Event']"
             eventList = calendarItem.find "ul.#{@eventClass}"
             length = eventList.length

@@ -721,7 +730,7 @@
             else
                 parseInt(dayNum, 10) - 1

-        addYearlyEvents: (events, eventDays) =>
+        addYearlyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
@@ -731,7 +740,6 @@
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
             month = $.inArray events.month, self.months
-            eventDate = []

             # add yearly events
             if events.day
@@ -741,13 +749,11 @@

                     if currentMonth is month
                         eventWeek = if dayShift <= day then eventWeek = weeks.eq eventWeekNum else eventWeek = weeks.eq eventWeekNum + 1
-                        eventDate.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
+                        eventDates.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
             else
-                eventDate.push parseInt(events.eventDate, 10)
-            $.each eventDate, (key, value) ->
-                eventDays.push calendarInner.find "td.drm-date[data-date=#{value}]"
+                eventDates.push parseInt(events.eventDate, 10)

-        addMonthlyEvents: (events, eventDays) =>
+        addMonthlyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
@@ -756,7 +762,6 @@
             firstDay = self.getDayOfWeek currentMonth, currentYear, 1
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
-            eventDate = []

             # add monthly events
             if events.day
@@ -764,20 +769,16 @@
                     day = $.inArray value, self.days
                     eventWeekNum = self.getEventWeekNum events.dayNum, day, numberDays, dayShift
                     eventWeek = if dayShift <= day then eventWeek = weeks.eq eventWeekNum else eventWeek = weeks.eq eventWeekNum + 1
-                    eventDate.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
+                    eventDates.push eventWeek.find("td.drm-date[data-day=#{day}]").data 'date'
             else
-                eventDate.push parseInt(events.eventDate, 10)
-
-            $.each eventDate, (key, value) ->
-                eventDays.push calendarInner.find "td.drm-date[data-date=#{value}]"
-
-        addBiWeeklyEvents: (events, eventDays) =>
+                eventDates.push parseInt(events.eventDate, 10)
+
+        addBiWeeklyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
             currentYear = calendarInner.data 'year'
             weeks = if self.view is 'month' then calendarInner.find('tbody').find "tr.drm-week" else calendarInner.find "tbody.drm-week"
-            eventDate = []

             # events that occur every 2 weeks
             if events.day
@@ -791,12 +792,9 @@
                     $.each eventWeeks, (key, value) ->
                         that = $ value
                         weekLength = that.find("td:not(.#{self.classes.muted})").length
-                        eventDate.push that.find("td.drm-date[data-day=#{day}]").data('date')
-
-            $.each eventDate, (key, value) ->
-                eventDays.push calendarInner.find "td.drm-date[data-date=#{value}]"
-
-        addWeeklyEvents: (events, eventDays) =>
+                        eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+
+        addWeeklyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
             currentMonth = calendarInner.data 'month'
@@ -804,7 +802,6 @@
             firstDay = self.getDayOfWeek currentMonth, currentYear, 1
             numberDays = self.getDaysInMonth (currentMonth + 1), currentYear
             dayShift = if firstDay is self.daysPerWeek then 0 else firstDay
-            eventDate = []
             weeks = if self.view is 'month' then calendarInner.find('tbody').find 'tr.drm-week' else calendarInner.find 'tbody.drm-week'

             # weekly events
@@ -814,29 +811,29 @@
                     length = weeks.length
                     $.each weeks, (key, value) ->
                         that = $ value
-                        weekLength = that.find("td:not(.#{self.classes.muted})").length
-                        if key is 0
-                            if dayShift <= day then eventDate.push that.find("td.drm-date[data-day=#{day}]").data('date')
-                        else if key is (length - 1)
-                            if day < weekLength then eventDate.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                        days = if self.view is 'month' then that.find "td.drm-date" else that.find "td.drm-date[data-hour='All Day Event']"
+                        weekLength = days.length
+                        if key is 0 and length isnt 1
+                            if dayShift <= day then eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+                        else if key is (length - 1) and length isnt 1
+                            if day < weekLength then eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
                         else
-                            eventDate.push that.find("td.drm-date[data-day=#{day}]").data('date')
-
-            $.each eventDate, (key, value) ->
-                eventDays.push calendarInner.find "td.drm-date[data-date=#{value}]"
-
-        addDailyEvents: (events, eventDays) =>
+                            eventDates.push that.find("td.drm-date[data-day=#{day}]").data('date')
+
+            eventDates
+
+        addDailyEvents: (events, eventDates) =>
             self = @
             calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
-            days = calendarInner.find('tbody').find "td.drm-date"
+            days = if self.view is 'month' then calendarInner.find "tbody td.drm-date" else calendarInner.find "td.drm-date[data-hour='All Day Event']"
             days.each ->
-                eventDays.push $(@)
-
-        addOneTimeEvents: (events, eventDays) =>
-            self = @
-            calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
-            eventDate = parseInt(events.eventDate, 10)
-            eventDays.push calendarInner.find "td.drm-date[data-date=#{eventDate}]"
+                eventDates.push $(@).data 'date'
+
+        addOneTimeEvents: (events, eventDates) =>
+            self = @
+            calendarInner = self.calendar.find "div.#{@calendarInnerClass}"
+            eventDates.push parseInt(events.eventDate, 10)
+            eventDates

         addEventsToCalendar: (events) =>
             self = @
@@ -844,25 +841,25 @@
             currentMonth = calendarInner.data 'month'
             currentYear = calendarInner.data 'year'
             month = $.inArray events.month, self.months
-            eventDays = []
+            eventDates = []

             # add yearly events
             if events.recurrance is 'yearly' and currentMonth is month
-                self.addYearlyEvents events, eventDays
+                self.addYearlyEvents events, eventDates
             else if events.recurrance is 'monthly'
-                self.addMonthlyEvents events, eventDays
+                self.addMonthlyEvents events, eventDates
             else if events.recurrance is 'biweekly'
-                self.addBiWeeklyEvents events, eventDays
+                self.addBiWeeklyEvents events, eventDates
             else if events.recurrance is 'weekly'
-                self.addWeeklyEvents events, eventDays
+                self.addWeeklyEvents events, eventDates
             else if events.recurrance is 'daily'
-                self.addDailyEvents events, eventDays
+                self.addDailyEvents events, eventDates
             else if events.recurrance is 'none' and currentMonth is month and currentYear is parseInt(events.year, 10)
-                self.addOneTimeEvents events, eventDays
-            if eventDays.length > 0
-                $.each eventDays, (key, calendarItem) ->
+                self.addOneTimeEvents events, eventDates
+            if eventDates.length > 0
+                $.each eventDates, (key, date) ->
                     # add css classes here
-                    self.addNewCalendarEvent events, calendarItem
+                    self.addNewCalendarEvent events, date

         highlightWeekends: =>
             self = @
@@ -1109,6 +1106,7 @@
             weekInfo = self.getDatesInWeek currentMonth, currentDate, currentYear
             datesInWeek = weekInfo.datesInWeek
             weekNumber = self.getWeekNumber currentMonth, currentDate, currentYear
+            weekClass = if weekNumber % 2 is 0 then 'even-week' else 'odd-week'

             getDates = (datesInWeek, key) ->
                 dates = {}
@@ -1143,20 +1141,15 @@
                 weekdays += "#{value}
#{self.months[dates.month]} #{dates.date}	"
             weekdays += '

'

-            week = "
"
-            week += "
All Day Events	"
-
-            $.each self.days, (key, value) ->
-                dates = getDates datesInWeek, key
-                week += "	"
-            week += "
"
+            week = "
"

             $.each self.hours, (key, value) ->
+                hour = value.name
                 week += '
'
-                week += "#{value.name}	"
+                week += "#{hour}	"
                 $.each self.days, (key, value) ->
                     dates = getDates datesInWeek, key
-                    week += "	"
+                    week += "	"
                 week += '
'
             week += '

'

@@ -1179,7 +1172,6 @@
             $("div.#{self.calendarInnerClass}").find("tbody td[data-month=#{nextMonth}]").addClass(self.classes.muted).removeClass 'drm-date'
             self.highlightCurrentDay()
             self.highlightWeekends()
-            self.addWeekNumbers()

             $.each self.events, (key, value) ->
                 self.addEventsToCalendar value
@@ -1193,8 +1185,6 @@
         createDateView: (currentMonth, currentDate, currentYear) =>
             self = @
             self.calendarInnerClass = "drm-calendar-#{self.view}-view"
-
-            console.log "#{currentMonth} #{currentDate}, #{currentYear}"

             calendar = $ '

',
                 class: self.calendarInnerClass


Persist state machine

Persist state machine

--- bin/heyu-run.coffee
+++ bin/heyu-run.coffee
@@ -30,8 +30,10 @@
   # state_machines
   list = []
   for obj in state_machines
-    for name,v of obj
-      appl = new HeyuAppliance(name, states: v[1])
+    for name,values of obj
+      [state,states] = values
+      appl = new HeyuAppliance(name, states: states)
+      appl.changeState('any',state)
     list.push appl
   list

@@ -76,7 +78,7 @@
   appl2.switchOff()
   appl2.display_state()
   appl.display_state()
-  print appl.currentState()
+  print '#',appl.currentState()
   assert((-> appl.currentState() is "ON"),appl.name,appl.currentState())
   assert((-> appl2.currentState() is "OFF"),appl2.name,appl2.currentState())
   write_json("myfile.txt",[appl,appl2])
@@ -86,6 +88,9 @@
   assert((-> list[1].name is "light2"),"read_json",list[0].name)
   assert((-> list[0].currentState() is "ON"),"read_json",list[0].currentState())
   assert((-> list[1].currentState() is "OFF"),"read_json",list[0].currentState())
+  print "# persistent:"
+  list[0].display_state()
+  list[1].display_state()
   print 'Tests passed'

 # ---- Parse the command line


prevent using array if undefined to fix #13

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -446,6 +446,9 @@
   if endsWith(file, 'COMMIT_EDITMSG') or endsWith(file, 'PULLREQ_EDITMSG') or endsWith(file, 'MERGE_MSG') or endsWith(file, 'TAG_EDITMSG')
     return true
   patterns = atom.config.get('wakatime.ignore')
+  if not patterns?
+    return true
+
   ignore = false
   for pattern in patterns
     re = new RegExp(pattern, 'gi')


implement asset uploading

implement asset uploading

--- bin/upsyncer.coffee
+++ bin/upsyncer.coffee
@@ -87,18 +87,60 @@
 processAsset = (fileName, next)->
   fullPath = "#{assetsKV[fileName]}/#{fileName}"

+  content = fs.readFileSync fullPath
+
+  sumLocal = checksum content.toString('hex')
+
+  client.downloadFile fileName, (err, status, data) ->
+
+    logger.log "[upsyncer::processAsset::downloadFile] err:#{err}, status:#{status}"
+
+    switch status
+      when 404
+        # file not on cdn, so upload it
+        client.uploadFile fileName, content, (err, status, data)->
+          if err? or status isnt 200
+            logger.error "[upsyncer::processAsset::upload] failed. err:#{err}, status:#{status}"
+            next null,
+              'status' : status
+              'fileName' : fileName
+              'action' : 'upload'
+              'err' : err
+          else
+            logger.info "[upsyncer::processAsset::upload] put asset:#{fileName}"
+            next null
+          return
+
+      when 200
+        # file exist on cdn, skip
+        logger.log "[upsyncer::processAsset::upload] skip asset:#{fileName}"
+        next null
+      else
+        # unrecoginsed status code
+        next("unrecoginsed status code:#{status}")
+
+    return
+
+
   # check local sum
-  checksum.file fullPath, (err, sumLocal)->
-    if err?
-      next("[upsyncer::processAsset] fail to check local sum. fullPath:#{fullPath}, err:#{err}")
-      return
+  #checksum.file fullPath, (err, sumLocal)->
+    #if err?
+      #next("[upsyncer::processAsset] fail to check local sum. fullPath:#{fullPath}, err:#{err}")
+      #return

-    logger.log "[upsyncer::processAsset] fileName:#{fileName}, fullPath:#{fullPath}, local sum:#{sumLocal}"
+    #logger.log "[upsyncer::processAsset] fileName:#{fileName}, fullPath:#{fullPath}, local sum:#{sumLocal}"

-    client.downloadFile fileName, (err, status, data) ->
-      logger.log "[upsyncer::processAsset::downloadFile] err:#{err}, status:#{status}, data:#{data}"
-      next(null, sumLocal)
-      return
+    #client.downloadFile fileName, (err, status, data) ->
+
+      #switch status
+        #when 404
+          ## file not on cdn, so upload it
+          #client.
+
+
+      #logger.log "[upsyncer::processAsset::downloadFile] err:#{err}, status:#{status}, data:#{data}"
+      #next(null, sumLocal)
+      #return

   return


Update wstunnel.coffee

Update wstunnel.coffee

Fixing the issue that make client always listen on loop back even if we declare other IP to listen on

--- bin/wstunnel.coffee
+++ bin/wstunnel.coffee
@@ -61,7 +61,7 @@
       if host && port
         client.start(localport, wsHost, "#{host}:#{port}", {'x-wstclient': machineId} )
       else
-        client.start(localport, wsHost, undefined, {'x-wstclient': machineId} )
+        client.start(argv.t, wsHost, undefined, {'x-wstclient': machineId} )
   else
     return console.log(optimist.help());