As previously mentioned, I recently switched from maintaining a multi-thousand line Emacs configuration, to just a handful of lines using Spacemacs as a base. I never truly learned Vim keybindings, so using the Spacemacs default evil-mode Vim-like keybindings was a big change of pace for me, and forced me to learn a few little tricks to help editing Lisp code easier for me.
I am going to document just a non-exhaustive subset of the tiny little tricks I've learned adapting to the Vim way of editing Lisp code. Below I will introduce some tricks that may be relevant to editing Lisp code and may increase your efficiency.
Do not stay in insert state
It may occur to you that because Vim is modal, and the main objective is to insert text into a document, that you should be switching to insert state whenever you can. I disagree with this, and I try to stay in normal state as much as possible for reasons mentioned throughout the rest of this article.
If you were to insert quite a bit of text in insert state, then decide you want to undo a subset of that change, the Vim model will not know what you are thinking. An insertion of any amount of text is considered one atomic change — an undo command will undo all of it, which may or may not be what you intended. Therefor, it may be more desirable to exit insert state when you are done with a particular change, and to utilize the Vim-style
operators accompanied by
text objects, a few of which I will briefly introduce further below.
By default, Spacemacs has the smartparens package installed and configured nicely. What exactly is smartparens? It is a structured code manipulation library for your editor — similar to paredit — which many Lisp programmers are already familiar with. The major difference is that it works with user-definable pairs — even those consisting of multiple characters — rather than just the usual brackets and quotes, but it can also do many operations paredit lacks.
What is unique about Spacemacs coupled with smartparens is that it offers a dedicated “lisp state” for performing operations on Lisp code using a single keystroke for each, until exiting the state. To invoke this special state, type
SPC k followed by a key for the operation you want corresponding to the on-screen legend. The operation will be applied, and you will now be within “lisp state”, as indicated by the change in color of the mode line to pink. When you are done, simply return to normal state with
Escape. This is a great way to quickly apply a number of structural changes to Lisp forms.
Given the following example form:
(foo (bar baz) "qux blah")
You can quickly change specific text you want, without having to enter insert state, delete what you do not want, and finally typing your replacement. Instead, you can stay in normal state for most of the task, for reasons explained in the beginning of this article.
Let's assume you want to change the inner form
(bar baz) entirely to something else. To do so, you can place the cursor anywhere inside the inner form, and type
ca), followed by whatever you want to replace it with. To save one extra key (the shift modifier to enter the
) character), you can type the equivalent operation,
You can also “change around” other objects besides parentheses. For example, you can use
caw (“change around word”), or
ca" (“change around string”), too. To see the full list of objects you can operate on, just leave off the object and enter
ca, and a legend will present itself to guide you.
Occasionally you may want to just delete the object, rather than replacing it with other text. The “delete around” operation will accomplish just that, and never even entering insert state. To use it, position the cursor where you want, and just issue
da followed by whichever object you wish to delete.
Just like you can “change around”, the same operation can be applied to just the inside. Given our original example code:
(foo (bar baz) "qux blah")
With your cursor positioned in the
"qux blah" string, typing
ci" (“change inner string”), followed by some text, will leave the surrounding double quote characters intact, and only replace the contents of the string. Likewise, to replace
bar baz while keeping the surrounding parentheses, you can type
cib and some new text.
Below are some common recipes for manipulating Lisp code using the above operations (and some bonus recipes).
- Replace word:
ciw(remember, words in Lisp can be part of a larger symbol separated by hyphens).
- Replace string contents:
- Replace string with new object:
- Replace containing s-expression contents:
- Replace containing s-expression with new object:
- Replace surrounding parentheses with square brackets:
cs)](possibly useful for Scheme, Racket, and Clojure).
- Delete a word:
- Delete string contents:
- Delete string entirely:
- Re-indent a paragraph (or toplevel form separated by a newline):
- Delete 3 lines:
- Select the inner contents of an s-expression:
- Select an s-expression, including the parentheses:
- Select an s-expression and its parent s-expression:
- Select an s-expression, its parent, and its grandparent:
- Select a top-level form:
All of these recipes operate on Vim “text objects”. It is also possible to operate on “motions”, which takes the current cursor position into consideration — useful for example, if you wanted to delete part of a word, or replace text until a certain target character. I strongly urge you to read the Vim and Spacemacs documentation to become more familiar with operating on text objects and motions.
As you can see, Spacemacs coupled with Vim-style commands can make editing Lisp, or any text for that matter, pretty easy, and change how you work with and think about code completely. The above tips and tricks are by no means exhaustive, but still show that complex or otherwise tedious edits can be performed rather painlessly. If you have any more tips or tricks for editing Lisp code, let me know in the comments below, and be sure to read more about Spacemacs and Vim operations.