Endless Parentheses

Ramblings on productivity and technical subjects.

profile for Malabarba on Stack Exchange

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
(advice-add #'forward-page  :after

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?

comments powered by Disqus