Skip to content
This repository
Browse code

checkpoint

  • Loading branch information...
commit b378f1c4d17ab1b78fdac3c22e187ebe1a30ec1e 1 parent 8cfe76b
Jonathan Soeder authored April 10, 2013
1  Gemfile
@@ -6,7 +6,6 @@ gem "sass"
6 6
 gem "haml"
7 7
 gem "ejs"
8 8
 gem "coffee-script"
9  
-gem "haml_assets"
10 9
 gem "thin"
11 10
 gem "faye"
12 11
 
0  assets/stylesheets/code_sync/editor.css.scss b/lib/assets/stylesheets/code_sync/editor.css.scss
No changes.
32  lib/assets/javascripts/code_sync/client/index.coffee
@@ -3,22 +3,24 @@
3 3
 #= require_self
4 4
 #= require ./util
5 5
 
6  
-root = @
7  
-
8  
-if typeof(exports) isnt "undefined"  
9  
-  CodeSync = exports
10  
-else
11  
-  CodeSync = root.CodeSync = {}
12  
-
13  
-CodeSync.VERSION = "0.0.4"
14  
-
15 6
 class CodeSync.Client
16 7
   constructor: ()->
17  
-    
18 8
     CodeSync.util.loadScript "http://localhost:9295/faye/client.js", ()=>
19  
-      if Faye?
20  
-        @socket = new Faye.Client("http://localhost:9295/faye")
21  
-        console.log "Subscribing on ", @socket
22  
-        @socket.subscribe "/code-sync", ()=>
23  
-          console.log "Received Message", arguments
  9
+      @setupSocket() if Faye?
  10
+
  11
+  setupSocket: ()->
  12
+    @socket = new Faye.Client("http://localhost:9295/faye")
  13
+
  14
+    @socket.subscribe "/code-sync", (notification)=>
  15
+      if notification.name?.match(/\.js$/)
  16
+        @onJavascriptNotification.call(@,notification)
  17
+      if notification.name?.match(/\.css$/)
  18
+        @onStylesheetNotification.call(@, notification)
  19
+
  20
+  onJavascriptNotification: (notification)->
  21
+    console.log "Received Notification of Javascript Change", notification
  22
+
  23
+  onStylesheetNotification: (notification)->
  24
+    console.log "Received Notification Of Stylesheet Change", notification
  25
+
24 26
 
11  lib/assets/javascripts/code_sync/client/util.coffee
... ...
@@ -1,12 +1,11 @@
1 1
 CodeSync.util ||= {}
2 2
 
3  
-
4 3
 loadedScripts = {}
5 4
 scriptTimers = {}
6 5
 
7 6
 CodeSync.util.loadScript = (url, options={}, callback) ->
8  
-  loaded = loadedScripts 
9  
-  timers = scriptTimers 
  7
+  loaded = loadedScripts
  8
+  timers = scriptTimers
10 9
 
11 10
   if _.isFunction(options) and !callback?
12 11
     callback = options
@@ -20,13 +19,13 @@ CodeSync.util.loadScript = (url, options={}, callback) ->
20 19
   that = @
21 20
   onLoad = ()->
22 21
     if typeof(callback) is "function"
23  
-      callback.call(that, url, options, script) 
  22
+      callback.call(that, url, options, script)
24 23
 
25 24
     try
26 25
       head.removeChild(script)
27 26
     catch e
28  
-      true  
29  
-      
  27
+      true
  28
+
30 29
     loaded[url] = true
31 30
 
32 31
   if options.once is true && loaded[url]
369  lib/assets/javascripts/code_sync/dragsort.js
... ...
@@ -0,0 +1,369 @@
  1
+// jQuery List DragSort v0.5.1
  2
+// Website: http://dragsort.codeplex.com/
  3
+// License: http://dragsort.codeplex.com/license
  4
+
  5
+(function($) {
  6
+
  7
+  $.fn.dragsort = function(options) {
  8
+    if (options == "destroy") {
  9
+      $(this.selector).trigger("dragsort-uninit");
  10
+      return;
  11
+    }
  12
+
  13
+    var opts = $.extend({}, $.fn.dragsort.defaults, options);
  14
+    var lists = [];
  15
+    var list = null, lastPos = null;
  16
+
  17
+    this.each(function(i, cont) {
  18
+
  19
+      //if list container is table, the browser automatically wraps rows in tbody if not specified so change list container to tbody so that children returns rows as user expected
  20
+      if ($(cont).is("table") && $(cont).children().size() == 1 && $(cont).children().is("tbody"))
  21
+        cont = $(cont).children().get(0);
  22
+
  23
+      var newList = {
  24
+        draggedItem: null,
  25
+        placeHolderItem: null,
  26
+        pos: null,
  27
+        offset: null,
  28
+        offsetLimit: null,
  29
+        scroll: null,
  30
+        container: cont,
  31
+
  32
+        init: function() {
  33
+          //set options to default values if not set
  34
+          var tagName = $(this.container).children().size() == 0 ? "li" : $(this.container).children(":first").get(0).tagName.toLowerCase();
  35
+          if (opts.itemSelector == "")
  36
+            opts.itemSelector = tagName;
  37
+          if (opts.dragSelector == "")
  38
+            opts.dragSelector = tagName;
  39
+          if (opts.placeHolderTemplate == "")
  40
+            opts.placeHolderTemplate = "<" + tagName + ">&nbsp;</" + tagName + ">";
  41
+
  42
+          //listidx allows reference back to correct list variable instance
  43
+          $(this.container).attr("data-listidx", i).mousedown(this.grabItem).bind("dragsort-uninit", this.uninit);
  44
+          this.styleDragHandlers(true);
  45
+        },
  46
+
  47
+        uninit: function() {
  48
+          var list = lists[$(this).attr("data-listidx")];
  49
+          $(list.container).unbind("mousedown", list.grabItem).unbind("dragsort-uninit");
  50
+          list.styleDragHandlers(false);
  51
+        },
  52
+
  53
+        getItems: function() {
  54
+          return $(this.container).children(opts.itemSelector);
  55
+        },
  56
+
  57
+        styleDragHandlers: function(cursor) {
  58
+          this.getItems().map(function() { return $(this).is(opts.dragSelector) ? this : $(this).find(opts.dragSelector).get(); }).css("cursor", cursor ? "pointer" : "");
  59
+        },
  60
+
  61
+        grabItem: function(e) {
  62
+          //if not left click or if clicked on excluded element (e.g. text box) or not a moveable list item return
  63
+          if (e.which != 1 || $(e.target).is(opts.dragSelectorExclude) || $(e.target).closest(opts.dragSelectorExclude).size() > 0 || $(e.target).closest(opts.itemSelector).size() == 0)
  64
+            return;
  65
+
  66
+          //prevents selection, stops issue on Fx where dragging hyperlink doesn't work and on IE where it triggers mousemove even though mouse hasn't moved,
  67
+          //does also stop being able to click text boxes hence dragging on text boxes by default is disabled in dragSelectorExclude
  68
+          e.preventDefault();
  69
+
  70
+          //change cursor to move while dragging
  71
+          var dragHandle = e.target;
  72
+          while (!$(dragHandle).is(opts.dragSelector)) {
  73
+            if (dragHandle == this) return;
  74
+            dragHandle = dragHandle.parentNode;
  75
+          }
  76
+          $(dragHandle).attr("data-cursor", $(dragHandle).css("cursor"));
  77
+          $(dragHandle).css("cursor", "move");
  78
+
  79
+          //on mousedown wait for movement of mouse before triggering dragsort script (dragStart) to allow clicking of hyperlinks to work
  80
+          var list = lists[$(this).attr("data-listidx")];
  81
+          var item = this;
  82
+          var trigger = function() {
  83
+            list.dragStart.call(item, e);
  84
+            $(list.container).unbind("mousemove", trigger);
  85
+          };
  86
+          $(list.container).mousemove(trigger).mouseup(function() { $(list.container).unbind("mousemove", trigger); $(dragHandle).css("cursor", $(dragHandle).attr("data-cursor")); });
  87
+        },
  88
+
  89
+        dragStart: function(e) {
  90
+          if (list != null && list.draggedItem != null)
  91
+            list.dropItem();
  92
+
  93
+          list = lists[$(this).attr("data-listidx")];
  94
+          list.draggedItem = $(e.target).closest(opts.itemSelector);
  95
+
  96
+          //record current position so on dragend we know if the dragged item changed position or not
  97
+          list.draggedItem.attr("data-origpos", $(this).attr("data-listidx") + "-" + list.getItems().index(list.draggedItem));
  98
+
  99
+          //calculate mouse offset relative to draggedItem
  100
+          var mt = parseInt(list.draggedItem.css("marginTop"));
  101
+          var ml = parseInt(list.draggedItem.css("marginLeft"));
  102
+          list.offset = list.draggedItem.offset();
  103
+          list.offset.top = e.pageY - list.offset.top + (isNaN(mt) ? 0 : mt) - 1;
  104
+          list.offset.left = e.pageX - list.offset.left + (isNaN(ml) ? 0 : ml) - 1;
  105
+
  106
+          //calculate box the dragged item can't be dragged outside of
  107
+          if (!opts.dragBetween) {
  108
+            var containerHeight = $(list.container).outerHeight() == 0 ? Math.max(1, Math.round(0.5 + list.getItems().size() * list.draggedItem.outerWidth() / $(list.container).outerWidth())) * list.draggedItem.outerHeight() : $(list.container).outerHeight();
  109
+            list.offsetLimit = $(list.container).offset();
  110
+            list.offsetLimit.right = list.offsetLimit.left + $(list.container).outerWidth() - list.draggedItem.outerWidth();
  111
+            list.offsetLimit.bottom = list.offsetLimit.top + containerHeight - list.draggedItem.outerHeight();
  112
+          }
  113
+
  114
+          //create placeholder item
  115
+          var h = list.draggedItem.height();
  116
+          var w = list.draggedItem.width();
  117
+          if (opts.itemSelector == "tr") {
  118
+            list.draggedItem.children().each(function() { $(this).width($(this).width()); });
  119
+            list.placeHolderItem = list.draggedItem.clone().attr("data-placeholder", true);
  120
+            list.draggedItem.after(list.placeHolderItem);
  121
+            list.placeHolderItem.children().each(function() { $(this).css({ borderWidth:0, width: $(this).width() + 1, height: $(this).height() + 1 }).html("&nbsp;"); });
  122
+          } else {
  123
+            list.draggedItem.after(opts.placeHolderTemplate);
  124
+            list.placeHolderItem = list.draggedItem.next().css({ height: h, width: w }).attr("data-placeholder", true);
  125
+          }
  126
+
  127
+          if (opts.itemSelector == "td") {
  128
+            var listTable = list.draggedItem.closest("table").get(0);
  129
+            $("<table id='" + listTable.id + "' style='border-width: 0px;' class='dragSortItem " + listTable.className + "'><tr></tr></table>").appendTo("body").children().append(list.draggedItem);
  130
+          }
  131
+
  132
+          //style draggedItem while dragging
  133
+          var orig = list.draggedItem.attr("style");
  134
+          list.draggedItem.attr("data-origstyle", orig ? orig : "");
  135
+          list.draggedItem.css({ position: "absolute", opacity: 0.8, "z-index": 999, height: h, width: w });
  136
+
  137
+          //auto-scroll setup
  138
+          list.scroll = { moveX: 0, moveY: 0, maxX: $(document).width() - $(window).width(), maxY: $(document).height() - $(window).height() };
  139
+          list.scroll.scrollY = window.setInterval(function() {
  140
+            if (opts.scrollContainer != window) {
  141
+              $(opts.scrollContainer).scrollTop($(opts.scrollContainer).scrollTop() + list.scroll.moveY);
  142
+              return;
  143
+            }
  144
+            var t = $(opts.scrollContainer).scrollTop();
  145
+            if (list.scroll.moveY > 0 && t < list.scroll.maxY || list.scroll.moveY < 0 && t > 0) {
  146
+              $(opts.scrollContainer).scrollTop(t + list.scroll.moveY);
  147
+              list.draggedItem.css("top", list.draggedItem.offset().top + list.scroll.moveY + 1);
  148
+            }
  149
+          }, 10);
  150
+          list.scroll.scrollX = window.setInterval(function() {
  151
+            if (opts.scrollContainer != window) {
  152
+              $(opts.scrollContainer).scrollLeft($(opts.scrollContainer).scrollLeft() + list.scroll.moveX);
  153
+              return;
  154
+            }
  155
+            var l = $(opts.scrollContainer).scrollLeft();
  156
+            if (list.scroll.moveX > 0 && l < list.scroll.maxX || list.scroll.moveX < 0 && l > 0) {
  157
+              $(opts.scrollContainer).scrollLeft(l + list.scroll.moveX);
  158
+              list.draggedItem.css("left", list.draggedItem.offset().left + list.scroll.moveX + 1);
  159
+            }
  160
+          }, 10);
  161
+
  162
+          //misc
  163
+          $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
  164
+          list.setPos(e.pageX, e.pageY);
  165
+          $(document).bind("mousemove", list.swapItems);
  166
+          $(document).bind("mouseup", list.dropItem);
  167
+          if (opts.scrollContainer != window)
  168
+            $(window).bind("DOMMouseScroll mousewheel", list.wheel);
  169
+        },
  170
+
  171
+        //set position of draggedItem
  172
+        setPos: function(x, y) {
  173
+          //remove mouse offset so mouse cursor remains in same place on draggedItem instead of top left corner
  174
+          var top = y - this.offset.top;
  175
+          var left = x - this.offset.left;
  176
+
  177
+          //limit top, left to within box draggedItem can't be dragged outside of
  178
+          if (!opts.dragBetween) {
  179
+            top = Math.min(this.offsetLimit.bottom, Math.max(top, this.offsetLimit.top));
  180
+            left = Math.min(this.offsetLimit.right, Math.max(left, this.offsetLimit.left));
  181
+          }
  182
+
  183
+          //adjust top, left calculations to parent element instead of window if it's relative or absolute
  184
+          this.draggedItem.parents().each(function() {
  185
+            if ($(this).css("position") != "static" && (!$.browser.mozilla || $(this).css("display") != "table")) {
  186
+              var offset = $(this).offset();
  187
+              top -= offset.top;
  188
+              left -= offset.left;
  189
+              return false;
  190
+            }
  191
+          });
  192
+
  193
+          //set x or y auto-scroll amount
  194
+          if (opts.scrollContainer == window) {
  195
+            y -= $(window).scrollTop();
  196
+            x -= $(window).scrollLeft();
  197
+            y = Math.max(0, y - $(window).height() + 5) + Math.min(0, y - 5);
  198
+            x = Math.max(0, x - $(window).width() + 5) + Math.min(0, x - 5);
  199
+          } else {
  200
+            var cont = $(opts.scrollContainer);
  201
+            var offset = cont.offset();
  202
+            y = Math.max(0, y - cont.height() - offset.top) + Math.min(0, y - offset.top);
  203
+            x = Math.max(0, x - cont.width() - offset.left) + Math.min(0, x - offset.left);
  204
+          }
  205
+
  206
+          list.scroll.moveX = x == 0 ? 0 : x * opts.scrollSpeed / Math.abs(x);
  207
+          list.scroll.moveY = y == 0 ? 0 : y * opts.scrollSpeed / Math.abs(y);
  208
+
  209
+          //move draggedItem to new mouse cursor location
  210
+          this.draggedItem.css({ top: top, left: left });
  211
+        },
  212
+
  213
+        //if scroll container is a div allow mouse wheel to scroll div instead of window when mouse is hovering over
  214
+        wheel: function(e) {
  215
+          if (($.browser.safari || $.browser.mozilla) && list && opts.scrollContainer != window) {
  216
+            var cont = $(opts.scrollContainer);
  217
+            var offset = cont.offset();
  218
+            if (e.pageX > offset.left && e.pageX < offset.left + cont.width() && e.pageY > offset.top && e.pageY < offset.top + cont.height()) {
  219
+              var delta = e.detail ? e.detail * 5 : e.wheelDelta / -2;
  220
+              cont.scrollTop(cont.scrollTop() + delta);
  221
+              e.preventDefault();
  222
+            }
  223
+          }
  224
+        },
  225
+
  226
+        //build a table recording all the positions of the moveable list items
  227
+        buildPositionTable: function() {
  228
+          var pos = [];
  229
+          this.getItems().not([list.draggedItem[0], list.placeHolderItem[0]]).each(function(i) {
  230
+            var loc = $(this).offset();
  231
+            loc.right = loc.left + $(this).outerWidth();
  232
+            loc.bottom = loc.top + $(this).outerHeight();
  233
+            loc.elm = this;
  234
+            pos[i] = loc;
  235
+          });
  236
+          this.pos = pos;
  237
+        },
  238
+
  239
+        dropItem: function() {
  240
+          if (list.draggedItem == null)
  241
+            return;
  242
+
  243
+          //list.draggedItem.attr("style", "") doesn't work on IE8 and jQuery 1.5 or lower
  244
+          //list.draggedItem.removeAttr("style") doesn't work on chrome and jQuery 1.6 (works jQuery 1.5 or lower)
  245
+          var orig = list.draggedItem.attr("data-origstyle");
  246
+          list.draggedItem.attr("style", orig);
  247
+          if (orig == "")
  248
+            list.draggedItem.removeAttr("style");
  249
+          list.draggedItem.removeAttr("data-origstyle");
  250
+
  251
+          list.styleDragHandlers(true);
  252
+
  253
+          list.placeHolderItem.before(list.draggedItem);
  254
+          list.placeHolderItem.remove();
  255
+
  256
+          $("[data-droptarget], .dragSortItem").remove();
  257
+
  258
+          window.clearInterval(list.scroll.scrollY);
  259
+          window.clearInterval(list.scroll.scrollX);
  260
+
  261
+          //if position changed call dragEnd
  262
+          if (list.draggedItem.attr("data-origpos") != $(lists).index(list) + "-" + list.getItems().index(list.draggedItem))
  263
+            opts.dragEnd.apply(list.draggedItem);
  264
+          list.draggedItem.removeAttr("data-origpos");
  265
+
  266
+          list.draggedItem = null;
  267
+          $(document).unbind("mousemove", list.swapItems);
  268
+          $(document).unbind("mouseup", list.dropItem);
  269
+          if (opts.scrollContainer != window)
  270
+            $(window).unbind("DOMMouseScroll mousewheel", list.wheel);
  271
+          return false;
  272
+        },
  273
+
  274
+        //swap the draggedItem (represented visually by placeholder) with the list item the it has been dragged on top of
  275
+        swapItems: function(e) {
  276
+          if (list.draggedItem == null)
  277
+            return false;
  278
+
  279
+          //move draggedItem to mouse location
  280
+          list.setPos(e.pageX, e.pageY);
  281
+
  282
+          //retrieve list and item position mouse cursor is over
  283
+          var ei = list.findPos(e.pageX, e.pageY);
  284
+          var nlist = list;
  285
+          for (var i = 0; ei == -1 && opts.dragBetween && i < lists.length; i++) {
  286
+            ei = lists[i].findPos(e.pageX, e.pageY);
  287
+            nlist = lists[i];
  288
+          }
  289
+
  290
+          //if not over another moveable list item return
  291
+          if (ei == -1)
  292
+            return false;
  293
+
  294
+          //save fixed items locations
  295
+          var children = function() { return $(nlist.container).children().not(nlist.draggedItem); };
  296
+          var fixed = children().not(opts.itemSelector).each(function(i) { this.idx = children().index(this); });
  297
+
  298
+          //if moving draggedItem up or left place placeHolder before list item the dragged item is hovering over otherwise place it after
  299
+          if (lastPos == null || lastPos.top > list.draggedItem.offset().top || lastPos.left > list.draggedItem.offset().left)
  300
+            $(nlist.pos[ei].elm).before(list.placeHolderItem);
  301
+          else
  302
+            $(nlist.pos[ei].elm).after(list.placeHolderItem);
  303
+
  304
+          //restore fixed items location
  305
+          fixed.each(function() {
  306
+            var elm = children().eq(this.idx).get(0);
  307
+            if (this != elm && children().index(this) < this.idx)
  308
+              $(this).insertAfter(elm);
  309
+            else if (this != elm)
  310
+              $(this).insertBefore(elm);
  311
+          });
  312
+
  313
+          //misc
  314
+          $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
  315
+          lastPos = list.draggedItem.offset();
  316
+          return false;
  317
+        },
  318
+
  319
+        //returns the index of the list item the mouse is over
  320
+        findPos: function(x, y) {
  321
+          for (var i = 0; i < this.pos.length; i++) {
  322
+            if (this.pos[i].left < x && this.pos[i].right > x && this.pos[i].top < y && this.pos[i].bottom > y)
  323
+              return i;
  324
+          }
  325
+          return -1;
  326
+        },
  327
+
  328
+        //create drop targets which are placeholders at the end of other lists to allow dragging straight to the last position
  329
+        createDropTargets: function() {
  330
+          if (!opts.dragBetween)
  331
+            return;
  332
+
  333
+          $(lists).each(function() {
  334
+            var ph = $(this.container).find("[data-placeholder]");
  335
+            var dt = $(this.container).find("[data-droptarget]");
  336
+            if (ph.size() > 0 && dt.size() > 0)
  337
+              dt.remove();
  338
+            else if (ph.size() == 0 && dt.size() == 0) {
  339
+              if (opts.itemSelector == "td")
  340
+                $(opts.placeHolderTemplate).attr("data-droptarget", true).appendTo(this.container);
  341
+              else
  342
+                //list.placeHolderItem.clone().removeAttr("data-placeholder") crashes in IE7 and jquery 1.5.1 (doesn't in jquery 1.4.2 or IE8)
  343
+                $(this.container).append(list.placeHolderItem.removeAttr("data-placeholder").clone().attr("data-droptarget", true));
  344
+
  345
+              list.placeHolderItem.attr("data-placeholder", true);
  346
+            }
  347
+          });
  348
+        }
  349
+      };
  350
+
  351
+      newList.init();
  352
+      lists.push(newList);
  353
+    });
  354
+
  355
+    return this;
  356
+  };
  357
+
  358
+  $.fn.dragsort.defaults = {
  359
+    itemSelector: "",
  360
+    dragSelector: "",
  361
+    dragSelectorExclude: "input, textarea",
  362
+    dragEnd: function() { },
  363
+    dragBetween: false,
  364
+    placeHolderTemplate: "",
  365
+    scrollContainer: window,
  366
+    scrollSpeed: 5
  367
+  };
  368
+
  369
+})($);
4  lib/assets/javascripts/code_sync/editor/dependencies.coffee
... ...
@@ -0,0 +1,4 @@
  1
+#= require zepto
  2
+#= require codemirror
  3
+#= require codemirror-css
  4
+#= require codemirror-coffeescript
46  lib/assets/javascripts/code_sync/editor/index.coffee
... ...
@@ -0,0 +1,46 @@
  1
+#= require ./dependencies
  2
+#= require_self
  3
+
  4
+class CodeSync.AssetEditor
  5
+
  6
+  @instances: {}
  7
+
  8
+  @getInstance: (options={})->
  9
+    @instances.main ||= new CodeSync.AssetEditor(options)
  10
+
  11
+  visible: false
  12
+
  13
+  constructor: (@options={})->
  14
+    el = @editorElement()
  15
+
  16
+
  17
+    CodeMirror el[0],
  18
+      height: 500
  19
+      width: 500
  20
+      mode: 'coffeescript'
  21
+      theme: 'lesser-dark'
  22
+      value: ''
  23
+      lineNumbers: true
  24
+      autofocus: true
  25
+
  26
+  editorElement: ()->
  27
+    el = $('#codesync-editor-element')
  28
+
  29
+    return el if el.length isnt 0
  30
+
  31
+    $('body').append "<div id='codesync-editor-wrapper'><div id='codesync-editor-element'></div></div>"
  32
+
  33
+    el = $('#codesync-editor-element')
  34
+
  35
+  toggle: ()->
  36
+    if @visible then @hide() else @show()
  37
+
  38
+  show: ()->
  39
+    $('#codesync-editor-wrapper').show()
  40
+    @visible = true
  41
+    @
  42
+
  43
+  hide: ()->
  44
+    $('#codesync-editor-wrapper').hide()
  45
+    @visible = false
  46
+    @
21  lib/assets/javascripts/code_sync/index.coffee
... ...
@@ -0,0 +1,21 @@
  1
+#= require ./keylauncher.min
  2
+#= require_self
  3
+#= require code_sync/client
  4
+#= require code_sync/editor
  5
+
  6
+root = @
  7
+
  8
+if typeof(exports) isnt "undefined"
  9
+  CodeSync = exports
  10
+else
  11
+  CodeSync = root.CodeSync = {}
  12
+
  13
+CodeSync.VERSION = "0.0.4"
  14
+
  15
+CodeSync.setSequence = (sequence="sync")->
  16
+  KeyLauncher.onSequence sequence, ()->
  17
+    CodeSync.AssetEditor.getInstance().toggle()
  18
+
  19
+CodeSync.setHotKey = (hotkey="command+j")->
  20
+  KeyLauncher.on hotkey, ()->
  21
+    CodeSync.AssetEditor.getInstance().toggle()
1  lib/assets/javascripts/code_sync/keylauncher.min.js
... ...
@@ -0,0 +1 @@
  1
+(function(){function o(){if(!i){i=!0;if(s){for(var e=0;e<s.length;e++)s[e].call(window,[]);s=[]}}}function u(e){var t=window.onload;typeof window.onload!="function"?window.onload=e:window.onload=function(){t&&t(),e()}}function a(){if(r)return;r=!0,document.addEventListener&&!n.opera&&document.addEventListener("DOMContentLoaded",o,!1),n.msie&&window==top&&function(){if(i)return;try{document.documentElement.doScroll("left")}catch(e){setTimeout(arguments.callee,0);return}o()}(),n.opera&&document.addEventListener("DOMContentLoaded",function(){if(i)return;for(var e=0;e<document.styleSheets.length;e++)if(document.styleSheets[e].disabled){setTimeout(arguments.callee,0);return}o()},!1);if(n.safari){var e;(function(){if(i)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return}if(e===undefined){var t=document.getElementsByTagName("link");for(var n=0;n<t.length;n++)t[n].getAttribute("rel")=="stylesheet"&&e++;var r=document.getElementsByTagName("style");e+=r.length}if(document.styleSheets.length!=e){setTimeout(arguments.callee,0);return}o()})()}u(o)}var e=window.DomReady={},t=navigator.userAgent.toLowerCase(),n={version:(t.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(t),opera:/opera/.test(t),msie:/msie/.test(t)&&!/opera/.test(t),mozilla:/mozilla/.test(t)&&!/(compatible|webkit)/.test(t)},r=!1,i=!1,s=[];e.ready=function(e,t){a(),i?e.call(window,[]):s.push(function(){return e.call(window,[])})},a()})(),function(e){function t(e,t){var n=e.length;while(n--)if(e[n]===t)return n;return-1}function n(e,n){var r,i,o,u,a;r=e.keyCode,t(w,r)==-1&&w.push(r);if(r==93||r==224)r=91;if(r in m){m[r]=!0;for(o in y)y[o]==r&&(s[o]=!0);return}if(!s.filter.call(this,e))return;if(!(r in v))return;for(u=0;u<v[r].length;u++){i=v[r][u];if(i.scope==n||i.scope=="all"){a=i.mods.length>0;for(o in m)if(!m[o]&&t(i.mods,+o)>-1||m[o]&&t(i.mods,+o)==-1)a=!1;(i.mods.length==0&&!m[16]&&!m[18]&&!m[17]&&!m[91]||a)&&i.method(e,i)===!1&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0))}}}function r(e){var n=e.keyCode,r,i=t(w,n);i>=0&&w.splice(i,1);if(n==93||n==224)n=91;if(n in m){m[n]=!1;for(r in y)y[r]==n&&(s[r]=!1)}}function i(){for(d in m)m[d]=!1;for(d in y)s[d]=!1}function s(e,t,n){var r,i,s,o;n===undefined&&(n=t,t="all"),e=e.replace(/\s/g,""),r=e.split(","),r[r.length-1]==""&&(r[r.length-2]+=",");for(s=0;s<r.length;s++){i=[],e=r[s].split("+");if(e.length>1){i=e.slice(0,e.length-1);for(o=0;o<i.length;o++)i[o]=y[i[o]];e=[e[e.length-1]]}e=e[0],e=b[e]||e.toUpperCase().charCodeAt(0),e in v||(v[e]=[]),v[e].push({shortcut:r[s],scope:t,method:n,key:r[s],mods:i})}}function o(e){if(typeof e=="string"){if(e.length!=1)return!1;e=e.toUpperCase().charCodeAt(0)}return t(w,e)!=-1}function u(){return w}function a(e){var t=(e.target||e.srcElement).tagName;return t!="INPUT"&&t!="SELECT"&&t!="TEXTAREA"}function f(e){g=e||"all"}function l(){return g||"all"}function c(e){var t,n,r;for(t in v){n=v[t];for(r=0;r<n.length;)n[r].scope===e?n.splice(r,1):r++}}function h(e,t,n){e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,function(){n(window.event)})}function p(){var t=e.key;return e.key=E,t}var d,v={},m={16:!1,18:!1,17:!1,91:!1},g="all",y={"⇧":16,shift:16,"⌥":18,alt:18,option:18,"⌃":17,ctrl:17,control:17,"⌘":91,command:91},b={backspace:8,tab:9,clear:12,enter:13,"return":13,esc:27,escape:27,space:32,left:37,up:38,right:39,down:40,del:46,"delete":46,home:36,end:35,pageup:33,pagedown:34,",":188,".":190,"/":191,"`":192,"-":189,"=":187,";":186,"'":222,"[":219,"]":221,"\\":220},w=[];for(d=1;d<20;d++)y["f"+d]=111+d;for(d in y)s[d]=!1;h(document,"keydown",function(e){n(e,g)}),h(document,"keyup",r),h(window,"focus",i);var E=e.key;e.key=s,e.key.setScope=f,e.key.getScope=l,e.key.deleteScope=c,e.key.filter=a,e.key.isPressed=o,e.key.getPressedKeyCodes=u,e.key.noConflict=p,typeof module!="undefined"&&(module.exports=key)}(this),function(){var e,t,n;window.KeyLauncher||(window.KeyLauncher={}),(n=window.KeyLauncher).util||(n.util={}),e={},t={},KeyLauncher.util.loadStylesheet=function(e,t,n){var r;return t==null&&(t={}),r=document.createElement("link"),r.type="text/css",r.rel="stylesheet",r.href=e,document.getElementsByTagName("head")[0].appendChild(r)},KeyLauncher.util.loadScript=function(n,r,i){var s,o,u,a,f,l;r==null&&(r={}),o=e,l=t,typeof r=="function"&&i==null&&(i=r,r={}),s=document.getElementsByTagName("head")[0],a=document.createElement("script"),a.src=n,a.type="text/javascript",f=this,u=function(){typeof i=="function"&&i.call(f,n,r,a);try{s.removeChild(a)}catch(e){!0}return o[n]=!0};if(r.once===!0&&o[n])return!1;s.appendChild(a),a.onreadystatechange=function(){if(a.readyState==="loaded"||a.readyState==="complete")return u()},a.onload=u;if(typeof navigator!="undefined"&&navigator!==null?navigator.userAgent.match(/WebKit/):void 0)return l[n]=setInterval(function(){return u(),clearInterval(l[n])},10)}}.call(this),function(){window.KeyLauncher.Launcher=function(){function e(e){var t,n,r,i;this.options=e!=null?e:{},this.fn=e.fn,this.command=e.command,i=this.options.requires;for(n=0,r=i.length;n<r;n++)t=i[n],this._dependencies[t]={loaded:!1}}return e.prototype._dependencies={},e.prototype.run=function(){var e,t,n,r,i,s=this;if(this.isReady())return this.onReady();n=this._dependencies;for(e in n)t=n[e],t.loaded!==!0&&e.match(/\.css/)&&KeyLauncher.util.loadStylesheet(e,function(e){return s.onDependencyLoad.apply(s,arguments)});r=this._dependencies,i=[];for(e in r)t=r[e],t.loaded!==!0&&e.match(/\.js/)&&i.push(KeyLauncher.util.loadScript(e,function(){return s.onDependencyLoad.apply(s,arguments)}));return i},e.prototype.requires=function(e){return this._dependencies[e]={loaded:!1}},e.prototype.isReady=function(){var e,t,n,r;t=!0,r=this._dependencies;for(e in r)n=r[e],n.loaded===!1&&(t=!1);return t},e.prototype.onDependencyLoad=function(e){this._dependencies[e].loaded=!0;if(this.isReady())return this.onReady()},e.prototype.onReady=function(){return this.called||this.fn.call(window,this),this.called=!0},e}()}.call(this),function(){var e;KeyLauncher.VERSION="0.0.2",KeyLauncher.on=function(e,t,n){var r;n==null&&(n={});if(e==null)throw"Must specify a valid key command";return r=new KeyLauncher.Launcher({command:e,fn:t||function(){},requires:n.requires||[]}),key(e,function(){return r.run.call(r)}),r},e=function(e,t){var n,r,i,s,o;s=KeyLauncher.currentSequence||"",o=t.shortcut,KeyLauncher.currentSequence=""+s+o,i=function(){var e,t;e=KeyLauncher.sequences,t=[];for(n in e)r=e[n],n.match(KeyLauncher.currentSequence)&&t.push(n);return t}(),i.length>0||(KeyLauncher.currentSequence="");if(i.length===1&&(r=KeyLauncher.sequences[KeyLauncher.currentSequence]))return r.run.call(r),KeyLauncher.currentSequence=""},KeyLauncher.onSequence=function(t,n,r){var i,s,o,u,a;r==null&&(r={}),KeyLauncher.sequences||(KeyLauncher.sequences={}),s=KeyLauncher.sequences[t]=new KeyLauncher.Launcher({fn:n||function(){},requires:r.requires||[]}),a=t.split("");for(o=0,u=a.length;o<u;o++)i=a[o],key(i,e);return s}}.call(this);
5  lib/assets/stylesheets/code_sync/index.css
... ...
@@ -0,0 +1,5 @@
  1
+/*
  2
+ *= require codemirror
  3
+ *= require codemirror-lesserdark
  4
+ *= require_tree .
  5
+*/ 
9  lib/middleman_extension.rb
@@ -4,13 +4,16 @@
4 4
 
5 5
 
6 6
   module CodeSync::MiddlemanExtension
7  
-    class << self  
  7
+    class << self
8 8
       def registered app
9  
-        puts "CodeSync"
10  
-        app.send :include, InstanceMethods
  9
+        app.after_configuration do
  10
+
  11
+        end
11 12
       end
12 13
     end
  14
+
13 15
     module InstanceMethods
  16
+
14 17
     end
15 18
   end
16 19
 
4  site/Gemfile
@@ -4,9 +4,11 @@ source 'https://rubygems.org'
4 4
 
5 5
 gem "haml"
6 6
 gem "sass"
  7
+gem "haml_assets"
  8
+
7 9
 gem "coffee-script"
8 10
 gem "middleman-sprockets"
9 11
 gem "pry"
10 12
 gem "code_sync", path: "/Users/jonathan/Projects/code_sync"
11 13
 
12  
-gem "middleman", "~>3.0.12"
  14
+gem "middleman"
44  site/Gemfile.lock
@@ -10,11 +10,11 @@ PATH
10 10
 GEM
11 11
   remote: https://rubygems.org/
12 12
   specs:
13  
-    activesupport (3.2.12)
14  
-      i18n (~> 0.6)
  13
+    activesupport (3.2.13)
  14
+      i18n (= 0.6.1)
15 15
       multi_json (~> 1.0)
16 16
     addressable (2.3.3)
17  
-    chunky_png (1.2.7)
  17
+    chunky_png (1.2.8)
18 18
     coderay (1.0.9)
19 19
     coffee-script (2.2.0)
20 20
       coffee-script-source
@@ -47,23 +47,26 @@ GEM
47 47
     faye-websocket (0.4.7)
48 48
       eventmachine (>= 0.12.0)
49 49
     fssm (0.2.10)
50  
-    haml (4.0.0)
  50
+    haml (4.0.2)
  51
+      tilt
  52
+    haml_assets (0.2.1)
  53
+      haml
51 54
       tilt
52  
-    hike (1.2.1)
  55
+    hike (1.2.2)
53 56
     http_parser.rb (0.5.3)
54 57
     http_router (0.10.2)
55 58
       rack (>= 1.0.0)
56 59
       url_mount (~> 0.2.1)
57  
-    i18n (0.6.4)
  60
+    i18n (0.6.1)
58 61
     listen (0.7.3)
59 62
     maruku (0.6.1)
60 63
       syntax (>= 1.0.0)
61 64
     method_source (0.8.1)
62  
-    middleman (3.0.12)
63  
-      middleman-core (= 3.0.12)
64  
-      middleman-more (= 3.0.12)
  65
+    middleman (3.0.13)
  66
+      middleman-core (= 3.0.13)
  67
+      middleman-more (= 3.0.13)
65 68
       middleman-sprockets (~> 3.0.8)
66  
-    middleman-core (3.0.12)
  69
+    middleman-core (3.0.13)
67 70
       activesupport (~> 3.2.6)
68 71
       bundler (~> 1.1)
69 72
       listen (~> 0.7.3)
@@ -71,24 +74,24 @@ GEM
71 74
       rack-test (~> 0.6.1)
72 75
       rb-fsevent (~> 0.9.3)
73 76
       thor (~> 0.15.4)
74  
-      tilt (~> 1.3.1)
75  
-    middleman-more (3.0.12)
  77
+      tilt (~> 1.3.6)
  78
+    middleman-more (3.0.13)
76 79
       coffee-script (~> 2.2.0)
77 80
       coffee-script-source (~> 1.3.3)
78 81
       compass (>= 0.12.2)
79 82
       execjs (~> 1.4.0)
80 83
       haml (>= 3.1.6)
81  
-      i18n (~> 0.6.0)
  84
+      i18n (~> 0.6.0, < 0.6.2)
82 85
       maruku (~> 0.6.0)
83  
-      middleman-core (= 3.0.12)
  86
+      middleman-core (= 3.0.13)
84 87
       padrino-helpers (= 0.10.7)
85 88
       sass (>= 3.1.20)
86 89
       uglifier (~> 1.2.6)
87  
-    middleman-sprockets (3.0.9)
  90
+    middleman-sprockets (3.0.10)
88 91
       middleman-more (>= 3.0.11)
89 92
       sprockets (~> 2.1, < 2.5)
90 93
       sprockets-sass (~> 0.9.1)
91  
-    multi_json (1.6.1)
  94
+    multi_json (1.7.2)
92 95
     padrino-core (0.10.7)
93 96
       activesupport (~> 3.2.0)
94 97
       http_router (~> 0.10.2)
@@ -103,13 +106,13 @@ GEM
103 106
       method_source (~> 0.8)
104 107
       slop (~> 3.4)
105 108
     rack (1.4.5)
106  
-    rack-protection (1.4.0)
  109
+    rack-protection (1.5.0)
107 110
       rack
108 111
     rack-test (0.6.2)
109 112
       rack (>= 1.0)
110 113
     rb-fsevent (0.9.3)
111 114
     sass (3.2.7)
112  
-    sinatra (1.3.5)
  115
+    sinatra (1.3.6)
113 116
       rack (~> 1.4)
114 117
       rack-protection (~> 1.3)
115 118
       tilt (~> 1.3, >= 1.3.3)
@@ -128,7 +131,7 @@ GEM
128 131
       eventmachine (>= 0.12.6)
129 132
       rack (>= 1.0.0)
130 133
     thor (0.15.4)
131  
-    tilt (1.3.5)
  134
+    tilt (1.3.7)
132 135
     uglifier (1.2.7)
133 136
       execjs (>= 0.3.0)
134 137
       multi_json (~> 1.3)
@@ -143,7 +146,8 @@ DEPENDENCIES
143 146
   code_sync!
144 147
   coffee-script
145 148
   haml
146  
-  middleman (~> 3.0.12)
  149
+  haml_assets
  150
+  middleman
147 151
   middleman-sprockets
148 152
   pry
149 153
   sass
7  site/config.rb
... ...
@@ -1,6 +1,6 @@
1 1
 require 'haml'
2 2
 require 'middleman-sprockets'
3  
-require "code_sync"
  3
+
4 4
 
5 5
 # Per-page layout changes:
6 6
 #
@@ -34,16 +34,15 @@
34 34
 #   end
35 35
 # end
36 36
 
37  
-set :css_dir, 'assets/stylesheets'
  37
+set :css_dir, 'app/assets/stylesheets'
38 38
 
39  
-set :js_dir, 'assets/javascripts'
  39
+set :js_dir, 'app/assets/javascripts'
40 40
 
41 41
 set :images_dir, 'assets/images'
42 42
 
43 43
 # Use relative URLs
44 44
 activate :relative_assets
45 45
 activate :sprockets
46  
-activate :code_sync
47 46
 
48 47
 # Build-specific configuration
49 48
 configure :build do
4  site/source/app/assets/javascripts/all.js
... ...
@@ -1 +1,3 @@
1  
-//= require 'code_sync/client'
  1
+//= require code_sync
  2
+
  3
+CodeSync.setSequence('sync')
2  site/source/layouts/layout.haml
@@ -12,6 +12,8 @@
12 12
     %meta{:content => "Development utility for live reloading of your asset pipeline", :name => "description"}
13 13
     
14 14
     %meta{:content => "width=device-width", :name => "viewport"}
  15
+
  16
+    = stylesheet_link_tag 'code_sync'
15 17
     
16 18
   %body
17 19
     = javascript_include_tag "all"
346  vendor/assets/javascripts/codemirror-coffeescript.js
... ...
@@ -0,0 +1,346 @@
  1
+/**
  2
+ * Link to the project's GitHub page:
  3
+ * https://github.com/pickhardt/coffeescript-codemirror-mode
  4
+ */
  5
+CodeMirror.defineMode('coffeescript', function(conf) {
  6
+    var ERRORCLASS = 'error';
  7
+
  8
+    function wordRegexp(words) {
  9
+        return new RegExp("^((" + words.join(")|(") + "))\\b");
  10
+    }
  11
+
  12
+    var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]");
  13
+    var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]');
  14
+    var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))");
  15
+    var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
  16
+    var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))");
  17
+    var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*");
  18
+    var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*");
  19
+
  20
+    var wordOperators = wordRegexp(['and', 'or', 'not',
  21
+                                    'is', 'isnt', 'in',
  22
+                                    'instanceof', 'typeof']);
  23
+    var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else',
  24
+                          'switch', 'try', 'catch', 'finally', 'class'];
  25
+    var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete',
  26
+                          'do', 'in', 'of', 'new', 'return', 'then',
  27
+                          'this', 'throw', 'when', 'until'];
  28
+
  29
+    var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
  30
+
  31
+    indentKeywords = wordRegexp(indentKeywords);
  32
+
  33
+
  34
+    var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])");
  35
+    var regexPrefixes = new RegExp("^(/{3}|/)");
  36
+    var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no'];
  37
+    var constants = wordRegexp(commonConstants);
  38
+
  39
+    // Tokenizers
  40
+    function tokenBase(stream, state) {
  41
+        // Handle scope changes
  42
+        if (stream.sol()) {
  43
+            var scopeOffset = state.scopes[0].offset;
  44
+            if (stream.eatSpace()) {
  45
+                var lineOffset = stream.indentation();
  46
+                if (lineOffset > scopeOffset) {
  47
+                    return 'indent';
  48
+                } else if (lineOffset < scopeOffset) {
  49
+                    return 'dedent';
  50
+                }
  51
+                return null;
  52
+            } else {
  53
+                if (scopeOffset > 0) {
  54
+                    dedent(stream, state);
  55
+                }
  56
+            }
  57
+        }
  58
+        if (stream.eatSpace()) {
  59
+            return null;
  60
+        }
  61
+
  62
+        var ch = stream.peek();
  63
+
  64
+        // Handle docco title comment (single line)
  65
+        if (stream.match("####")) {
  66
+            stream.skipToEnd();
  67
+            return 'comment';
  68
+        }
  69
+
  70
+        // Handle multi line comments
  71
+        if (stream.match("###")) {
  72
+            state.tokenize = longComment;
  73
+            return state.tokenize(stream, state);
  74
+        }
  75
+
  76
+        // Single line comment
  77
+        if (ch === '#') {
  78
+            stream.skipToEnd();
  79
+            return 'comment';
  80
+        }
  81
+
  82
+        // Handle number literals
  83
+        if (stream.match(/^-?[0-9\.]/, false)) {
  84
+            var floatLiteral = false;
  85
+            // Floats
  86
+            if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
  87
+              floatLiteral = true;
  88
+            }
  89
+            if (stream.match(/^-?\d+\.\d*/)) {
  90
+              floatLiteral = true;
  91
+            }
  92
+            if (stream.match(/^-?\.\d+/)) {
  93
+              floatLiteral = true;
  94
+            }
  95
+
  96
+            if (floatLiteral) {
  97
+                // prevent from getting extra . on 1..
  98
+                if (stream.peek() == "."){
  99
+                    stream.backUp(1);
  100
+                }
  101
+                return 'number';
  102
+            }
  103
+            // Integers
  104
+            var intLiteral = false;
  105
+            // Hex
  106
+            if (stream.match(/^-?0x[0-9a-f]+/i)) {
  107
+              intLiteral = true;
  108
+            }
  109
+            // Decimal
  110
+            if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
  111
+                intLiteral = true;
  112
+            }
  113
+            // Zero by itself with no other piece of number.
  114
+            if (stream.match(/^-?0(?![\dx])/i)) {
  115
+              intLiteral = true;
  116
+            }
  117
+            if (intLiteral) {
  118
+                return 'number';
  119
+            }
  120
+        }
  121
+
  122
+        // Handle strings
  123
+        if (stream.match(stringPrefixes)) {
  124
+            state.tokenize = tokenFactory(stream.current(), 'string');
  125
+            return state.tokenize(stream, state);
  126
+        }
  127
+        // Handle regex literals
  128
+        if (stream.match(regexPrefixes)) {
  129
+            if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division
  130
+                state.tokenize = tokenFactory(stream.current(), 'string-2');
  131
+                return state.tokenize(stream, state);
  132
+            } else {
  133
+                stream.backUp(1);
  134
+            }
  135
+        }
  136
+
  137
+        // Handle operators and delimiters
  138
+        if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
  139
+            return 'punctuation';
  140
+        }
  141
+        if (stream.match(doubleOperators)
  142
+            || stream.match(singleOperators)
  143
+            || stream.match(wordOperators)) {
  144
+            return 'operator';
  145
+        }
  146
+        if (stream.match(singleDelimiters)) {
  147
+            return 'punctuation';
  148
+        }
  149
+
  150
+        if (stream.match(constants)) {
  151
+            return 'atom';
  152
+        }
  153
+
  154
+        if (stream.match(keywords)) {
  155
+            return 'keyword';
  156
+        }
  157
+
  158
+        if (stream.match(identifiers)) {
  159
+            return 'variable';
  160
+        }
  161
+
  162
+        if (stream.match(properties)) {
  163
+            return 'property';
  164
+        }
  165
+
  166
+        // Handle non-detected items
  167
+        stream.next();
  168
+        return ERRORCLASS;
  169
+    }
  170
+
  171
+    function tokenFactory(delimiter, outclass) {
  172
+        var singleline = delimiter.length == 1;
  173
+        return function(stream, state) {
  174
+            while (!stream.eol()) {
  175
+                stream.eatWhile(/[^'"\/\\]/);
  176
+                if (stream.eat('\\')) {
  177
+                    stream.next();
  178
+                    if (singleline && stream.eol()) {
  179
+                        return outclass;
  180
+                    }
  181
+                } else if (stream.match(delimiter)) {
  182
+                    state.tokenize = tokenBase;
  183
+                    return outclass;
  184
+                } else {
  185
+                    stream.eat(/['"\/]/);
  186
+                }
  187
+            }
  188
+            if (singleline) {
  189
+                if (conf.mode.singleLineStringErrors) {
  190
+                    outclass = ERRORCLASS;
  191
+                } else {
  192
+                    state.tokenize = tokenBase;
  193
+                }
  194
+            }
  195
+            return outclass;
  196
+        };
  197
+    }
  198
+
  199
+    function longComment(stream, state) {
  200
+        while (!stream.eol()) {
  201
+            stream.eatWhile(/[^#]/);
  202
+            if (stream.match("###")) {
  203
+                state.tokenize = tokenBase;
  204
+                break;
  205
+            }
  206
+            stream.eatWhile("#");
  207
+        }
  208
+        return "comment";
  209
+    }
  210
+
  211
+    function indent(stream, state, type) {
  212
+        type = type || 'coffee';
  213
+        var indentUnit = 0;
  214
+        if (type === 'coffee') {
  215
+            for (var i = 0; i < state.scopes.length; i++) {
  216
+                if (state.scopes[i].type === 'coffee') {
  217
+                    indentUnit = state.scopes[i].offset + conf.indentUnit;
  218
+                    break;
  219
+                }
  220
+            }
  221
+        } else {
  222
+            indentUnit = stream.column() + stream.current().length;
  223
+        }
  224
+        state.scopes.unshift({
  225
+            offset: indentUnit,
  226
+            type: type
  227
+        });
  228
+    }
  229
+
  230
+    function dedent(stream, state) {
  231
+        if (state.scopes.length == 1) return;
  232
+        if (state.scopes[0].type === 'coffee') {
  233
+            var _indent = stream.indentation();
  234
+            var _indent_index = -1;
  235
+            for (var i = 0; i < state.scopes.length; ++i) {
  236
+                if (_indent === state.scopes[i].offset) {
  237
+                    _indent_index = i;
  238
+                    break;
  239
+                }
  240
+            }
  241
+            if (_indent_index === -1) {
  242
+                return true;
  243
+            }
  244
+            while (state.scopes[0].offset !== _indent) {
  245
+                state.scopes.shift();
  246
+            }
  247
+            return false;
  248
+        } else {
  249
+            state.scopes.shift();
  250
+            return false;
  251
+        }
  252
+    }
  253
+
  254
+    function tokenLexer(stream, state) {
  255
+        var style = state.tokenize(stream, state);
  256
+        var current = stream.current();
  257
+
  258
+        // Handle '.' connected identifiers
  259
+        if (current === '.') {
  260
+            style = state.tokenize(stream, state);
  261
+            current = stream.current();
  262
+            if (style === 'variable') {
  263
+                return 'variable';
  264
+            } else {
  265
+                return ERRORCLASS;
  266
+            }
  267
+        }
  268
+
  269
+        // Handle scope changes.
  270
+        if (current === 'return') {
  271
+            state.dedent += 1;
  272
+        }
  273
+        if (((current === '->' || current === '=>') &&
  274
+                  !state.lambda &&
  275
+                  state.scopes[0].type == 'coffee' &&
  276
+                  stream.peek() === '')
  277
+               || style === 'indent') {
  278
+            indent(stream, state);
  279
+        }
  280
+        var delimiter_index = '[({'.indexOf(current);
  281
+        if (delimiter_index !== -1) {
  282
+            indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
  283
+        }
  284
+        if (indentKeywords.exec(current)){
  285
+            indent(stream, state);
  286
+        }
  287
+        if (current == 'then'){
  288
+            dedent(stream, state);
  289
+        }
  290
+
  291
+
  292
+        if (style === 'dedent') {
  293
+            if (dedent(stream, state)) {
  294
+                return ERRORCLASS;
  295
+            }
  296
+        }
  297
+        delimiter_index = '])}'.indexOf(current);
  298
+        if (delimiter_index !== -1) {
  299
+            if (dedent(stream, state)) {
  300
+                return ERRORCLASS;
  301
+            }
  302
+        }
  303
+        if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') {
  304
+            if (state.scopes.length > 1) state.scopes.shift();
  305
+            state.dedent -= 1;
  306
+        }
  307
+
  308
+        return style;
  309
+    }
  310
+
  311
+    var external = {
  312
+        startState: function(basecolumn) {
  313
+            return {
  314
+              tokenize: tokenBase,
  315
+              scopes: [{offset:basecolumn || 0, type:'coffee'}],
  316
+              lastToken: null,
  317
+              lambda: false,
  318
+              dedent: 0
  319
+          };
  320
+        },
  321
+
  322
+        token: function(stream, state) {
  323
+            var style = tokenLexer(stream, state);
  324
+
  325
+            state.lastToken = {style:style, content: stream.current()};
  326
+
  327
+            if (stream.eol() && stream.lambda) {
  328
+                state.lambda = false;
  329
+            }
  330
+
  331
+            return style;
  332
+        },
  333
+
  334
+        indent: function(state) {