[ This page is part of a larger article that I wrote about my js2-mode setup. ]
This is a minor mode that I wrote on top of js2-mode which highlights all occurrences of the variable under the cursor within its defining scope. I once saw this at a friend who was using Eclipse, and I thought it's pretty cool. I searched EmacsWiki for something like this and found a highlight-symbol mode. It didn't work very well with js2-mode though and also, being regexp-based, it had no idea about variable scopes, so this is why I started my own.
[ NOTE: this doesn't work with the released js2-mode, you will have to use the SVN code as it contains some fixes that my small hack relies upon ]
Save the file in a directory which is in your load-path, then add the following in your ~/.emacs:
The mode is automatically turned on by the following lines in my js2-mode-hook:
(if (featurep 'js2-highlight-vars) (js2-highlight-vars-mode))
When variables are highlighted, you can use the following key bindings:
- M-n or C-<down> — move to the next occurrence
- M-p or C-<up> — move to the previous occurrence
- M-r — rename the variable; this will ask you for confirmation for each occurrence, but you can press "!" to rename all.
For some reason, these bindings work only after a key press (i.e. the first time you press M-n, it says "M-n is undefined", but if you press it again it works). I presume this is a bug in Emacs, therefore I reported it along with a minimal test case. If you have any idea of an work around, I'd love to know it.
Update: I did find a poor solution for this, which I've implemented. It calls (top-level), which results in a message in the minibuffer which you can ignore ("Back to top level"). It also can be a problem when you're using recursive edits, but hopefully you don't use recursive edits. :-p
In any case, it is a bug in Emacs and Stallman himself stated "we should consider that a serious problem".
How it's made
js2-mode exposes a powerful API that allows us to:
figure out what's the token (node) under the cursor: (js2-node-at-point)
figure out if it's a "name node" (i.e. an identifier): (js2-name-node-p node)
retrieve the identifier name: (setq name (js2-name-node-name node))
find the enclosing scope: (setq scope (js2-node-get-enclosing-scope node)). This is the node's enclosing scope, but not necessarily the scope where it was defined. Fortunately, the following gets it.
find the defining scope: (setq scope (js2-get-defining-scope scope name))
finally, we can easily walk the "abstract syntax tree" in order to find identifiers with the same name within the given scope, and highlight them:
(js2-visit-ast scope (lambda (node end-p) (when (and (not end-p) (js2-name-node-p node) (string= name (js2-name-node-name node))) (let* ((beg (js2-node-abs-pos node)) (end (+ beg (js2-node-len node))) (ovl (make-overlay beg end))) (overlay-put ovl 'face 'js2-highlight-vars-face) (overlay-put ovl 'js2-highlight-vars t))) t))
I was able to figure out all this pretty quickly, despite my limited (E)Lisp experience. Steve's code is really great and it's no wonder that the FSF wanted it in the official Emacs repository.