Endless Parentheses

Concise ramblings on Emacs productivity.

Improving page (section) navigation

If you’ve taken the time to browse some Elisp source files, you’ve no doubt run into that odd little ^L, a.k.a. the form feed character. Emacs uses these white space characters as page delimiters. This makes for a very convenient way to split a file into sections, and quickly navigate between them. I won’t go too deep into them, as Eric James has already written a great crash course on pages that you should go check out.

What I wanted to write about is the way that I do page navigation in Emacs. Firstly, I find the default keys to be nothing short of abhorrent. Take a prefix key with the Control modifier, attach to it a non-modified key, and then make that key be something not-so-easy to hit, like ], and you have the recipe for painful fingers.

(define-key prog-mode-map "\C-x\C-n" #'forward-page)
(define-key prog-mode-map "\C-x\C-p" #'backward-page)

These keys would normally be bound to set-goal-column and mark-page, which I’ve never ever ever used (in fact, the former is disabled by default).

Then there’s a minor peeve. In some corner cases Emacs might leave the cursor at the bottom of the screen after moving. Here we make sure that never happens.

(defun endless/-recenter-advice (&rest _)
  "Recenter to page start."
  (when (called-interactively-p 'any)
    (recenter 5)))

;; Requires Emacs 24.5
(advice-add #'backward-page :after
            #'endless/-recenter-advice)
(advice-add #'forward-page  :after
            #'endless/-recenter-advice)

And then there’s the best part. It turns out you don’t need the form-feed character to delimit pages. That’s important because some languages aren’t that nice about them, and some dev teams might prefer you don’t stick those ^L all over the place. In Clojure, for instance, cljfmt confuses it for a blank line and freaks out a little.

Fortunately, Elisp style already recommends using ;;; to indicate comment sections, and the form feed character is most commonly placed right above these sections. So why not use that instead?

(setq page-delimiter
      (rx bol (or "\f" ";;;")
          (not (any "#")) (* not-newline) "\n"
          (* (* blank) (opt ";" (* not-newline)) "\n")))
;; Expanded regexp:
;; "^;;;[^#].*\n\\(?:[[:blank:]]*\\(?:;.*\\)?\n\\)*"

The regexp above is a bit special. We’re setting the page delimiter to be a ;;; at the start of a line, plus any number of empty lines or comment lines that follow it (that # part is to exclude ;;;###autoload cookies).

Consequently, when we hit C-x C-n or C-x C-p, the point is left right at the start of the first code-line after the ;;;. That’s usually where I want to be, and it works even on buffers without ^L, Clojure and Elisp. No doubt you can extended that to your programming language of choice by replacing the semicolons with the appropriate comment character.

Even better, why not write up a general solution based on the comment-start variable?

Tags: clojure, lisp, navigation, init.el, emacs

Say thanks on Gratipay
comments powered by Disqus