Emacs: Rapid Buffer Navigation with Imenu
Kris Jenkins wrote a great post last month about
rapidly navigating Clojure files in Emacs using
Helm–definitely check it out. Helm is near the top of my
list of Emacs extensions I need to explore, but in the meantime I
thought I’d point out that’s there actually a great solution for this
use case in a stock Emacs installation–imenu-mode
.
Imenu is a really slick library by Ake Stenhoff and Lars Lindberg which scans the open buffer and creates a menu of locations–usually representing top-level items like functions–that you can rapidly jump to. The mode gets just about everything right: it’s widely supported, easily extensible, uses markers to track buffer locations (so they stay correct even as you edit the file), supports auto-updating, and is available via either the GUI (for you mousey types) or the keyboard. It’s really a stellar example of the flexibility and extensibility of Emacs.
Basic usage involves running the imenu
command (M-x imenu RET
)
and entering the name of the item you want to jump to. Hit TAB
to
see a list of all possible jump targets. (If you’re an ido user,
check out this gist from the always-wonderful
Magnar Sveen to get your integration on.)
But honestly, a lot of the magic is in the GUI menu–even for those of us who usually neglect the mouse.
You’ll need to run the imenu-add-menubar-index
command to get things
up and running but once you do you’ll see a new menu named Index
in
your menu bar:
Clicking an item in the menu will jump you directly to it.
If you want the menu to be available automatically for certain modes, all you have to do is add the trigger function to the respective mode hooks, like so:
(add-hook 'clojure-mode-hook 'imenu-add-menubar-index)
(add-hook 'emacs-lisp-mode-hook 'imenu-add-menubar-index)
One additional tweak that you’ll probably want to add to your
.emacs
file is to automatically rescan the buffer contents so that
new jump targets appear in the menu as they are added:
(setq imenu-auto-rescan t)
Most Emacs modes (clojure-mode
included) already implement Imenu
support, but it’s actually pretty easy to extend Imenu’s functionality
to any kind of content we want to index. In fact, one of my favorite
uses is not even for programming–it’s my extension to Markdown mode,
allowing me to jump to document section headers.
The most full-featured way to implement Imenu for a mode is to
implement imenu-create-index-function
, providing a custom function
which creates a data structure in the format Imenu is expecting (see
imenu.el
for details), but the easiest way is simply to provide a
regular expression for the items we want to match and let Imenu do the
rest.
Here’s what that looks like for markdown-mode
:
(defvar camdez/markdown-imenu-generic-expression
'((nil "^#\\s-+\\(.+\\)$" 1)))
(defun camdez/markdown-imenu-configure ()
(interactive)
(setq imenu-generic-expression camdez/markdown-imenu-generic-expression))
(add-hook 'markdown-mode-hook 'camdez/markdown-imenu-configure)
This pattern will work for most any usage. We define a generic
expression (more on this in a sec), and when the given mode is
activated, we put that expression in imenu-generic-expression
, a
predefined location where Imenu expects to find such a thing (unless
you’ve provided a full imenu-create-index-function
implementation).
The simplest version of this “generic expression”, shown here, is
(MENU-TITLE REGEXP INDEX)
; MENU-TITLE
defines where to nest the
new menu item (or nil
, as we do here, to put it at the top level),
REGEXP
is the regular expression matching the content we want to
index, and INDEX
specifies which piece of the match we want to
insert in the menu. We could pass 0
to include the whole expression
in the menu, but in this case I’ve decide to match strings with look
like # Foo
, but only put the Foo
part into the menu (note the
subpattern offset by \\(
and \\)
). Also notice that
imenu-generic-expression
is a list of lists so that you can provide
multiple patterns to match, each with their own rules on where to nest
and what to display.
That’s really all there is to it. Let me know if you have any really novel uses for Imenu, I’d love to hear about them!