non-infuriating indentation with emacs and js2-mode with require.def asynchronous module definition CommonJS boilerplate

Classic CommonJS modules assume a synchronous execution environment (for the purposes of “require”) with a specialized loader mechanism that evaluates the module in its proper context and takes care of namespacing it.  If you want to use CommonJS modules in the browser you can either:

  • Leave the source code as it is and use an XHR-based loader that uses eval to perform the namespacing trick.  In order to deal with the synchronous require assumption you can use some combination of deferring the evaluation of the module until you think you have all the dependencies and synchronous XHR.  Commonly, regular expressions are used to figure out the dependencies, but one could also use some form of static analysis.  Examples of browser-based CommonJS loaders supporting this are teleport and yabble.
  • Wrap your source code in boilerplate that takes care of the namespacing.  This can be done via a build system or done permanently in the source.  Pretty much every browser-based CommonJS loader supports this, with RequireJS being the only one I’m going to name-check because there are too many of these suckers as is.

The synchronous idiom for module “foo” might look like this:

var bar = require("bar");
var baz = require("baz");
 
exports.doStuff = function() {
  return "awwww yeah.";
};

The asynchronous module definition for “foo” might look like this, noting that there are actually a couple of possible variations on this:

require.def("foo", ["exports", "bar", "baz"], function(exports, bar, baz) {
 
exports.doStuff = function() {
  return "awwww yeah.";
};
 
);

The thing that may jump out at you is that the asynchronous wrapping means that the body of our module actually lives inside a function definition within the argument list of a function call.  Let’s assume you enjoy the finer things in life and are using emacs and js2-mode for your javascript editing.  js2-mode will helpfully suggest indenting 14 characters because that puts us 2 characters in from the enclosing function call’s opening paren.

That indentation could drive a man crazy and was really my only reason for avoiding the asynchronous idiom.  Thankfully, emacs being what it is, I was able to make it do what I roughly what I want:

;; Check if the suggested indentation is due to require.def().  If it is, force
;;  the indentation down to zero.  We detect this case by checking whether the
;;  parse depth is 2 and the last top-level point was preceded by require.def.
(defun require-def-deindent (list index)
  (when (and (eq (nth 0 parse-status) 2)
             (save-excursion
               (let ((tl-point (syntax-ppss-toplevel-pos parse-status)))
                 (goto-char tl-point)
                 (backward-word 2)
                 (equal "require.def" (buffer-substring (point) tl-point))))
             ;; only intercede if they are suggesting what the sexprs suggest
             (let ((suggested-column (js-proper-indentation parse-status)))
               (eq (nth index list) suggested-column))
             )
    (indent-line-to 0)
    't
    ))
;; Uncomment the following to enable the hook if you want tab to always slam you
;;  to column 0 rather than doing the cycle thing.  (With the newline hook in
;;  place, I haven't seen the need yet.)
;(add-hook 'js2-indent-hook 'require-def-deindent)
 
;; Unfortunately, js2-enter-key turns off the bounce indent logic so we need to
;;  intentionally do something to get our helper invoked.  In this case, we use
;;  advice but we could also mess with the keybinding.
;; This assumes js2-enter-indents-newline is enabled / desired.
(defadvice js2-enter-key (around js2-enter-key-around)
  "Trigger require-def-deindent on enter for the newline."
  ad-do-it
  (let ((parse-status (save-excursion
                        (parse-partial-sexp (point-min) (point-at-bol))))
        positions)
    (push (current-column) positions)
    (require-def-deindent positions 0)))
(ad-activate 'js2-enter-key)

If you paste the above into your .emacs and have sufficient emacs karma, hopefully the above will work for you too.

UPDATE (2011/1/1):

The AMD idiom has settled on using “define” instead of “require.def”, so here is the above code modified to this end:

;; --- CommonJS AMD define() compensation
 
;; Check if the suggested indentation is due to define().  If it is, force
;;  the indentation down to zero.  We detect this case by checking whether the
;;  parse depth is 2 and the last top-level point was preceded by define.
(defun require-def-deindent (list index)
  (when (and (eq (nth 0 parse-status) 2)
             (save-excursion
               (let ((tl-point (syntax-ppss-toplevel-pos parse-status)))
                 (goto-char tl-point)
                 (backward-word 1)
                 (equal "define" (buffer-substring (point) tl-point))))
             ;; only intercede if they are suggesting what the sexprs suggest
             (let ((suggested-column (js-proper-indentation parse-status)))
               (eq (nth index list) suggested-column))
             )
    (indent-line-to 0)
    't
    ))
;; Uncomment the following to enable the hook if you want tab to always slam you
;;  to column 0 rather than doing the cycle thing.  (With the newline hook in
;;  place, I haven't seen the need yet.)
;(add-hook 'js2-indent-hook 'require-def-deindent)
 
;; Unfortunately, js2-enter-key turns off the bounce indent logic so we need to
;;  intentionally do something to get our helper invoked.  In this case, we use
;;  advice but we could also mess with the keybinding.
;; This assumes js2-enter-indents-newline is enabled / desired.
(defadvice js2-enter-key (around js2-enter-key-around)
  "Trigger require-def-deindent on enter for the newline."
  ad-do-it
  (let ((parse-status (save-excursion
                        (parse-partial-sexp (point-min) (point-at-bol))))
        positions)
    (push (current-column) positions)
    (require-def-deindent positions 0)))
(ad-activate 'js2-enter-key)
 
;; (end define compensation)

test-case-mode support for jetpack unit tests in emacs

Use Jetpack?  Occasionally write unit tests so you won’t be a complete hypocrite when criticizing other people’s code?  Think that picture up above looks more useful than this?:

error: fail: list contents ("4,5,6,7" != "3,4,5,6,7")
info: Traceback (most recent call last):
  File "resource://wmsy-jetpack-core-lib/timer.js", line 57, in notify
    this._callback.apply(null, []);
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 257, in anonymous
    timer.setTimeout(function() { onDone(self); }, 0);
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 282, in runNextTest
    self.start({test: test, onDone: runNextTest});
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 300, in start
    this.test.testFunction(this);
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 57, in runTest
    test(runner);
  File "resource://wmsy-wmsy-tests/test-vs-static.js", line 37, in anonymous
    slice.seek(6, 2, 2);
  File "resource://wmsy-wmsy-lib/wmsy/viewslice-static.js", line 51, in anonymous
    this._list.slice(this.bufLow, this.bufHigh));
  File "resource://wmsy-wmsy-tests/test-vs-static.js", line 31, in anonymous
    "list contents");
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 229, in assertEqual
    this.fail(message);
  File "resource://wmsy-jetpack-core-lib/unit-test.js", line 147, in fail
    console.trace();

Then do I have an .el for you!  This lives here.  You mileage may vary and may involve things catching on fire which can, in turn, affect your mileage.

ediosk: an emacs buffer switcher for the rest of us

Emacs users and would-be emacs users, are you tired of those emacs developers in their ivory towers not supporting buffer switching via touch-screen on a computer that’s not running emacs and using modern web browser technology instead of disproven parentheses-based technology?  Be tired no more!*

Thanks to Christopher Wellons and Chunye Wang’s work on emacs-httpd it is a simple matter to expose a JSON representation of the current set of frames/windows/buffers in your emacs session and provide non-REST manipulation mechanisms via a webserver implemented in elisp.

Once you have exposed an API, it is a subsequent simple matter to implement some JavaScript that understands these things and presents a nice UI.  In this case, we have used the Jetpack SDK, wmsy (the Widget Manifesting SYstem, an widgeting framework I am developing), and protovis.

The screenshot basically captures the entire feature-set:

  • A protovis-based visualization that shows the location of all of the emacs “windows” (the things that show buffers).  Emacs reports to us the pixel-space coordinates/sizes of the “frames” (GUI windows) and “windows”, so this all comes magically for free.  The downside is your emacs windows need to be in the same coordinate space, so use of multiple X displays without use of DMX will likely lead to weird overlap.
    • The selected “window” in each “frame” gets a diamond.  The active frame’s diamond gets to be black instead of gray.
    • Clicking on a “window” focuses/raises the containing “frame” and selects the “window”.
  • Buffers grouped by the directory they live in (if they have a backing file).
    • Buffers visible in windows have their background composed of the colors for all the windows they are in.
    • Buffers that are modified have their text colored red.
    • Buffers that have not been freshly displayed in a window recently have their text colored grey.
    • Clicking on a buffer displays it in what the UI believes to be the currently selected frame’s currently selected window.

* This entire paragraph is a joke**; no flaming necessary.

** ediosk is not a joke though.  I seriously have a touch-screen monitor hooked up to my windows build machine to the right of my two monitors hooked up to my linux/primary development machine.  While c-x b (icicle mode) will still be my dominant buffer switching mechanism, I expect ediosk to prove useful/amusing for cases where the number of buffers greatly exceeds my mental stack, when I am switching contexts, or when I am working in multiple branches simultaneously.

emacs compilation-mode support for reviewboard

Let’s say you’ve suckered someone into performing their review in my review board install (more info).  It looks pretty, no?  As the Amish say: ‘Tis a fine web page, but sure ’tis no emacs buffer, English.

Get your reviewboard-viewer.el here (repo here).  It basically just wgets the review with paths in a format the GNU regex will recognize and tells the temporary file to be a compilation-mode buffer with a little help in setting up the file search path.  There are instructions in the file, but the key non-intuitive things that are going to trip you up:

  • You really want to invoke reviewboard-viewer-view-review from a dired buffer showing the root directory of what the patch is against.  This lets us set up the search path to be exactly correct with no heuristics.
  • The argument the above wants is the id that you will see in the “bugzilla-style export” link on the review page.  For example, I had to type the number 72 above.

Other notes to people who are interested/care:

  • There was a dumb bug in the bugzilla export code involving caching.  Previous bugzilla exports likely only showed the selected hunk for the first comment on the file.  I fixed this.
  • OpenID snafus:
    • Weave OpenID support via the Accout Manager doesn’t work.  (Account Manager is where it got refactored to after it got dropped from Weave core).  I feel like it worked recently with 0.0.2, but just now when I tried (with 0.0.2) it pretended it was going to work and then failed to do so.  When I upgraded to 0.0.10 it decided to not even do that.  This is sad because Weave was the whole reason I went with OpenID; I wanted to make it easier than creating another throwaway account for people.
    • I hear that whatever Google OpenID-ish thing exists, it apparently does not work either.  This was not surprising since it was my understanding Google made their implementation correct but not interoperable without some extra work.
    • Verisign’s freebie OpenID thing does work!
    • If someone knows of a django auth-compatible OpenID/OAuth/whatever implementation that will at least fix the Google situation and requires practically no work on my part, I would be happy to change to using that.  Right now I am using django-openid-auth which is old but works.  My reviewboard repo is here if you are feeling especially generous about fixing things.