Editing Lisp Code with Emacs

Keyboard tips

  • Use the Standard US keyboard layout. The US keyboard's layout (unlike the western European keyboard layouts) makes typing the punctuation characters you need in programming languages easy.
  • Learn to touch-type.
  • Invest in a quality keyboard.

Key Swapping

  • Alt key for Meta, not Escape.
  • Turn Capslock into Control.

Here's a complete keyboard layout suggested by a photo of a Lisp Machine keyboard:

  • Swap Capslock and Backspace (Rubout in LM).
  • Swap parentheses and brackets (US layout).
  • Alt and Altgr are now Control.
  • Win keys are now Meta
  • Control keys are now Alt
  • Menu key is Compose (or Altgr).
  • Alt+arrows works like C-M-f, C-M-b, C-M-u, C-M-d, etc. (like PLT DrScheme)
  • Using a European PC 105 keyboard, the less-than (<) key to the left of the Z key can be used as Escape (good for Viper Emacs mode).

(I'm am using lately the layout: Alt/AltGr -> Control, Win -> Meta+Control, Control -> Meta. It works for Emacs and most applications. But I have problems with Xterm (Control does not work anymore), Rxvt, etc. Outside Gnome and KDE, I must use Eterm (or ansi-term within Emacs).)

Some xmodmap files that describe the Lisp machine keyboard on the picture above reasonably faithfully can be found here.

The xkeycaps program takes care of this on X, MacOS users can use uControl, Windows users can use XKeymacs.

In just about every programming language I've ever used (C, Java, Perl, bash and LISP), and in normal prose, parentheses occur far more often than the square bracket characters. So it's important to make typing the parenthesis easy:

(keyboard-translate ?\( ?\[)
(keyboard-translate ?\[ ?\()
(keyboard-translate ?\) ?\])
(keyboard-translate ?\] ?\))

This assumes that the [ and ] are easy to reach unshifted chars.

The keyboard-translates above can interfere with pasting text using the mouse in emacs. Another way to accomplish the same thing while limiting the effect to Slime is:

(define-key slime-mode-map (kbd "[") 'insert-parentheses)
(define-key slime-mode-map (kbd "]") 'move-past-close-and-reindent)
(define-key slime-mode-map (kbd "(") (lambda () (interactive) (insert "[")))
(define-key slime-mode-map (kbd ")") (lambda () (interactive) (insert "]")))

If using Paredit, this code can be used to swap parentheses and square brackets:

(define-key paredit-mode-map (kbd "[") 'paredit-open-round)
(define-key paredit-mode-map (kbd "]") 'paredit-close-round)
(define-key paredit-mode-map (kbd "M-[") 'paredit-wrap-round)
(define-key paredit-mode-map (kbd "(") 'paredit-open-square)
(define-key paredit-mode-map (kbd ")") 'paredit-close-square)

Structured editing

What is structured editing?

Structured editing is a way of editing source code which attempts, as much as is possible, to keep the source code consistently valid. This means, among other things, that the inserting of an open parenthesis should insert the closing one as well (similarly for ").

Taylor R. Campbell (Riastradh on #lisp) has written a package of lisp editing code called paredit.

There also is the newer Parinfer, which helps to keep both indentation and parens balanced. It is easy to get started and yet it provides advanced features à la Paredit, so you should definitely take a look.

The rest of this page uses functions defined in Paredit.

Start with paredit-open-list it inserts both the open and the closing parenthesis and then leaves the cursor between the two (unless we're in a comment or string, in which case it inserts a lone ( character). After filling in the LISP form, use the paredit-close-list command which moves past the closing parenthesis and reindents the line. Here's my setup:

(define-key slime-mode-map [(?\()] 'paredit-open-list)
(define-key slime-mode-map [(?\))] 'paredit-close-list)
(define-key slime-mode-map [(return)] 'paredit-newline)

Unstructured editing

So how do you insert a simple, standalone "(" or ")" character? I happen to have the "=" character directly below ")" and the "\" character just to the left of that; so here's what I use:

(define-key slime-mode-map [(control ?\=)] (lambda () (interactive) (insert "(")))
(define-key slime-mode-map [(control ?\\)] (lambda () (interactive) (insert ")")))

Choose something that makes sense to use and which requires at most one key press (though it will probably be modified). Even with structured editing you'll still need to insert a lone parenthesis every now and then. Note: You can also just as easily do C-q ( and C-q ); personally, I insert lone parentheses often enough that that's too many keypresses for my tastes.

Splicing, Dicing and Moving Around

When editing LISP code, you shouldn't think of the source code as text, but as program structure (SEXPs). With parenthesis-aware editors, we get the same functionality used in IntelliSense (otherwise a separate IDE component in other languages). The basic lists/program manipulation commands (basic EMACS bindings) are:

  • transpose-sexps (C-M-t) - swap the sexp before point with the sexp after point.
  • backward-sexp (C-M-b) - move to before the preceding sexp.
  • forward-sexp (C-M-f) - move after the following sexp.
  • kill-sexp (C-M-k) - kill the following sexp.
  • backward-up-list (C-M-u) - move point to the start of the enclosing list.
  • up-list (not bound by default) - move forward out of the current enclosing list.
  • down-list (C-M-d) - move the just after the open paren of the next list.
  • mark-sexp (C-M-@) - Keep the cursor at the start of an s-exp and executing this key strokes highlight the entire S-expression.

All of these functions take prefix args which specify how many sexps to move around. backward-up-list can take a negative arg which moves out of the enclosing parens but forwards, basically simulating up-list.

When editing LISP code you will use these the forward/backward/transpose-sexp functions far more often the the char versions, I'd suggest swapping them so that, for example, C-M-t (transpose-sexp) is C-t and C-t (transpose-char) is C-M-t:

(define-key slime-mode-map (kbd "C-t") 'transpose-sexps)
(define-key slime-mode-map (kbd "C-M-t") 'transpose-chars)
(define-key slime-mode-map (kbd "C-b") 'backward-sexp)
(define-key slime-mode-map (kbd "C-M-b") 'backward-char)
(define-key slime-mode-map (kbd "C-f") 'forward-sexp)
(define-key slime-mode-map (kbd "C-M-f") 'forward-char)

Use the standard keybindings for a bit and after you've figured out how they work and how often you use them, chose some keybindings based on your preferences. For example, I have a Dvorak keyboard where the H, T, and N keys are located directly under the right hand, so I've put the forward functions on then N key, the transpose functions on the T key, and the backward functions on the H key.

In addition, paredit provides the following commands:

  • paredit-open-list (() - Inserts a balanced parenthesis pair.
  • paredit-close-list (M-)) - Moves past one closing parenthesis and reindents.
  • paredit-close-list-and-newline ()) - Moves past one closing delimiter, adds a newline, and reindents.
  • paredit-splice-sexp (M-s) - Splices the list that the point is on by removing its delimiters.
  • paredit-backward-delete (DEL) - Deletes a character backward or moves backward over a delimiter.
  • paredit-forward-slurp-sexp (C-)) - Adds the S-expression following the current list into that list by moving the closing delimiter.
  • paredit-backward-slurp-sexp (C-() - Adds the S-expression preceding the current list into that list by moving the closing ((sic) opening?) delimiter.
  • paredit-forward-barf-sexp (C-}) - Removes the last S-expression in the current list from that list by moving the closing delimiter.
  • paredit-backward-barf-sexp (C-{) - Removes the first S-expression in the current list from that list by moving the closing ((sic) opening?) delimiter.
  • paredit-wrap-sexp (M-() - Wraps the following S-expression in a list.

For example, leaving point inside the Sexp (b c), slurping (a (b c) d e f) forward would result in (a (b c d) e f), then (a (b c d e) f), and finally (a (b c d e f)). Barfing reverses/undoes slurps. Refer to the paredit.el package for more functions that are similar to text editing functions, but additionally respect parentheses (and even quotes) around SEXPs.

Indentation

A simple, consistent way to indent lisp code is using C-M-q at the begining of an s-exp. Alternatively, use C-M-\ to re-indent the selected region.

Learning Emacs commands

Key Quiz is a quiz game that teaches you Emacs key-bindings.

Paredit Cheat Sheet for paredit commands beyond the scope of this document.