camdez.com

Rule #1: There are no rules.

Emacs: Rapid Buffer Navigation With Imenu

| Comments

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:

The eponymous Imenu menu.

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:

1
2
(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:

1
(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:

1
2
3
4
5
6
7
8
(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!

Comments