HomeProjectsEmacs JavaScript mode

Emacs JavaScript mode

NOTE: this is obsolete.  I'm not using this mode anymore.  It's a cheap hack and has a lot of problems.  I wrote an article about my js2-mode setup which provides good indentation and other stuff.  Check it out.  Ignore the rest of this page. :-p

Download javascript.el

Here's my update to an old javascript-mode (authors Steven Champeon and Ville Skyttä).  In essence, I was unhappy with indentation, but I also removed some stuff that I don't need and fixed various small things, such as highlighting literal regexps.

The indentation is based on CC Mode, which is almost Good Enough for JavaScript; the original javascript-mode.el (note that I'm talking about the version I had back in 2004; they might have fixed it in between, but I don't think so) had some indentation problems, for examples in cases like this:

obj.method({ foo: "bar",
                        test: "asdf",
                        qwer: "qwer",
                        f: function() {
                        return true;
                }});

It looks quite ugly, and due to CC Mode automatic indentation it was even hard to fix manually. ;-)  One day I started looking into how to make my JS mode not suck, and at the end of the next day I came up with a modified version that Does What I Mean.  To install, download the file in a directory in your LISP path and add the following to your .emacs:

(autoload 'javascript-mode "javascript")
(add-to-list 'auto-mode-alist '("\\.js$" . javascript-mode))

Indentation style

Here are some examples of how this mode indents JS with my preferred settings (c-basic-offset is 8 characters, case-label is "*" (4 characters)).  The sample above would indent like this:

obj.method({ foo: "bar",
             test: "asdf",
             qwer: "qwer",
             f: function() {
                     return true;
             }});

However, if I put the "foo:" on a new line, it would indent like this:

obj.method({
        foo: "bar",
        test: "asdf",
        qwer: "qwer",
        f: function() {
                return true;
        }
});

Other samples:

var a = {
        lorem : "ipsum",
        foo   : "bar"
};

var a = { "lorem" : "ipsum",
          check   : "foo bar",
          bar     : 1234,
          method  : function() {
                  return { foo  : "bar",
                           test : 123
                         }
          }
        };

switch (what) {

    case "foo":
        do_foo();
        break;

    case "bar":
        do_bar();
        break;

}

This is my javascript-mode-hook:

(defun my-js-mode-hook ()
  (require 'cperl-mode)
  (setq tab-width 8
        indent-tabs-mode nil
        c-basic-offset 8)
  (c-toggle-auto-state 0)
  (c-toggle-hungry-state 1)
  (define-key javascript-mode-map [(meta control |)] 'cperl-lineup))

The last keybinding allows me to quickly lineup colons in hashes by placing the caret before the first colon, then selecting the whole hash and pressing M-C-|.

How does it work

This mode uses the comprehensive indentation engine from CC Mode, which means that you should be able to customize it heavily as described in the CC Mode Manual.

Since the C syntax is quite different from JavaScript, I needed to make some fixes (C doesn't support literal hashes, nor does it know about inner anonymous functions, etc.).  In order to add these fixes I advised the c-guess-basic-syntax function from cc-engine.el.  "Function advices" are a cool ELisp concept that allows you to have some code executed along with a function that you can't modify.  The original c-guess-basic-syntax will run first, then our "advice" kicks in and determines if we're in a JS situation that's likely to be misunderstood by CC-Mode.  If that is the case, it will run some analysis and return proper information so that the CC indentation engine will do the Right Thing.

I found that for literal hashes it's best to work with the “arglist-*” classes, that is, it will return:

  • “arglist-intro” if the caret is on a line that starts a hash and the bracket is on the previous line
  • “arglist-cont” if the caret is on a line that continues a hash and the bracket is alone on the first line
  • “arglist-cont-nonempty” if the caret is on a line that continues a hash and the first element starts on the same line as the bracket
  • “arglist-close” if the caret is on a line that ends a hash (contains the ending bracket).

It will also properly detect functions:

  • “defun-open” if the caret is on a line that contains the bracket starting a function, but not the function declaration
  • “defun-block-intro” if the caret is on the first line in a function block (doesn't contain the bracket)
  • “defun-close” if the caret is on a line containing the bracket that ends a function

I also declared the following in javascript.el:

(c-set-offset 'arglist-close '(c-lineup-close-paren))
(c-set-offset 'arglist-cont 0)
(c-set-offset 'arglist-cont-nonempty '(js-lineup-arglist))
(c-set-offset 'arglist-intro '+)
(setq c-special-indent-hook nil)

(but you can override them in javascript-mode-hook if you wish).  Coupled with the declarations above and the advice to c-guess-basic-syntax, I ended up with a mode that Does What I Mean.  I'm happy, end of story. :-)

Other JavaScript modes on the Planet

For a long time I was working with a modified version of Karl Landström's JavaScript mode.  Indentation wasn't as good as I wanted, but it was still the best available for my lazy ass.

I recently discovered js2-mode by Steve Yegge.  That's an amazing piece of work because it contains a full JavaScript parser and can inform you about a lot of potential problems in your code, such as missing semicolons (I simply love this one) or missing the "var" to declare locals, using undeclared variables, etc. etc.  It will set a new standard for editing JavaScript (it seems that it will be included in the official GNU Emacs distribution!).  My main problem with it is that the indentation engine is based on Karl's mode which is not good enough for me...  I'd love it if I could integrate it with this simple advice to CC Mode.

Comments

  • By: trowaJun 30 (11:41) 2008fixed font color §

    .DlHighlight {
      background-color: #f8f8f8;
      border-left: 2px solid #0c0;
      font: 10pt monospace;
      padding: 10px;
      margin-left: 3em;
      color:black;/* this is must be...*/
    }

  • By: tim dJul 05 (00:15) 2008RE: Emacs JavaScript mode §

    On Kubuntu using XEmacs 21.4, so line-number-at-pos was missing.  Hacked a fix via Google searches for compatibility snippets (lifted from Python doctest) -- to use place this at the top of javascript.el and search/replace line-number-at-pos with javascript-line-number (except within the snippet of course)...  didn't need to search/replace looking-back yet, so didn't, but left snippets here:

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;; Emacs Compatibility Functions
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;; Define compatible versions of functions that are defined
    ;; for some versions of emacs but not others.

    ;; Backwards compatibility: looking-back
    (cond ((fboundp 'looking-back) ;; Emacs 22.x
           (defalias 'javascript-looking-back 'looking-back))
          (t
           (defun javascript-looking-back (regexp)
             "Return true if text before point matches REGEXP."
             (save-excursion
               (let ((orig-pos (point)))
                 ;; Search backwards for the regexp.
                 (if (re-search-backward regexp nil t)
                     ;; Check if it ends at the original point.
                     (= orig-pos (match-end 0))))))))

    ;; Backwards compatibility: replace-regexp-in-string
    (cond ((fboundp 'replace-regexp-in-string)
           (defalias 'javascript-replace-regexp-in-string 'replace-regexp-in-string))
          (t ;; XEmacs 21.x or Emacs 20.x
           (defun javascript-replace-regexp-in-string
             (regexp rep string &optional fixedcase literal)
             "Replace all matches for REGEXP with REP in STRING."
             (let ((start 0))
               (while (and (< start (length string))
                           (string-match regexp string start))
                 (setq start (+ (match-end 0) 1))
                 (let ((newtext (if (functionp rep)
                                    (save-match-data
                                      (funcall rep (match-string 0 string)))
                                  rep)))
                   (setq string (replace-match newtext fixedcase
                                               literal string)))))
             string)))

    ;; Backwards compatibility: line-number
    (cond ((fboundp 'line-number) ;; XEmacs
           (defalias 'javascript-line-number 'line-number))
          ((fboundp 'line-number-at-pos) ;; Emacs 22.x
           (defalias 'javascript-line-number 'line-number-at-pos))
          (t ;; Emacs 21.x
           (defun javascript-line-number (&optional pos)
             "Return the line number of POS (default=point)."
             (1+ (count-lines 1
                   (save-excursion (progn (beginning-of-line)
                                          (or pos (point)))))))))

    ;; Backwards compatibility: process-live-p
    (cond ((fboundp 'process-live-p) ;; XEmacs
           (defalias 'javascript-process-live-p 'process-live-p))
          (t ;; Emacs
           (defun javascript-process-live-p (process)
             (and (processp process)
                  (equal (process-status process) 'run)))))

     

  • By: jim uSep 09 (18:25) 2008RE: Emacs JavaScript mode §

    Less than 1/20th the size of js2-mode and the indentation actually works, not to mention indent-region.  Nice job.

    • By: mishooSep 09 (19:48) 2008RE[2]: Emacs JavaScript mode §

      Thanks, but, js2-mode is about a lot more than just indentation.  Indeed I would love it if I could hook in my indentation hack into js2-mode, which is otherwise excellent.

      [ My mode has some indentation bugs, BTW :-.  so far they are supportable so I didn't get to fix them yet. ]

  • By: Deniz DoganSep 23 (16:50) 2008Good, but not flawless §

    Great work, but there are a few nasty flaws. The following snippet does not indent as I expect it to:

    something.call(this, function () {
      // Hello
             // world
             alert('Indentation fault!');
    });

    Passing callback functions as arguments is very common in e.g. JQuery and possibly other JavaScript libraries as well, so this is a pretty big drawback, unfortunately.

    • By: mishooSep 23 (16:53) 2008RE: Good, but not flawless §

      Yeah, I noticed a few nasty glitches; I'm not sure how to fix them but they definitely piss me off so I'll try my best.

      • By: Deniz DoganNov 05 (15:06) 2008RE[2]: Good, but not flawless §

        You don't happen to have some repository where I can get the latest updates for this?

        • By: mishooNov 05 (22:12) 2008RE[3]: Good, but not flawless §

          Nope, I have no public repository yet for this stuff...

          But there's no update either :-.  Sorry about that, I really didn't have the time to look into it.  For the most part I'm happy with it so that's gonna have to wait, unless... some brave folks step in to help. ;-)

  • By: AugustoNov 05 (21:39) 2008RE: Emacs JavaScript mode §

    Godd stuff! Would it be possible to use 4 spaces for indentation rather than 2?

    Thanks!

    • By: mishooNov 05 (22:14) 2008RE[2]: Emacs JavaScript mode §

      You probably need to:

      (setq c-basic-offset 4)

      in your ~/.emacs.  C-mode (which is really the indentation engine behind my JS hack) uses that variable to customize the amount of space for indentation.

  • By: Drew WellsNov 12 (22:41) 2010RE: Emacs JavaScript mode §

    Can this be made to work with tabs.  I tried commenting indent-tabs-mode nil, it still inserts spaces

Page info
Created:
2008/05/25 12:33
Modified:
2009/08/09 20:07
Author:
Mihai Bazon
Comments:
12
Tags:
emacs, programming
See also