Clojure
Share your thoughts in the 2024 State of Clojure Survey!

Structural Editing

What is structural editing?

Text editors treat files as lines of characters. Common functionality involves: typing new characters, navigating by line (up/down) or character (right/left), selecting/copying/pasting text, deleting or replacing text, etc. All of these operations operate at either the character or line level.

When we examine Clojure code, we note that it can be viewed as a nested set of forms, where each level of nesting is a collection with beginning and ending delimiters: ( …​ ), [ …​ ], { …​ }, etc. This regular structure forms a tree, and structural editing is a new set of operations for manipulating our code at the node/tree level, instead of the character/line level.

Structural editing has a long history with editing Lisp languages, and is most associated with Emacs and in particular the Emacs paredit mode. But the ideas are more general than either Lisp or Emacs and indeed structural editing can be found in virtually any editor designed to support Clojure.

Similar to character-based editing, structural editing has ways to create new forms, navigate, select, copy/paste, delete, etc but all in terms of nested expressions rather than characters or lines. This page intends to introduce you to some high-level terms and concepts in structural editing, and not to cover any specific editors or keys as the details vary significantly depending on the editor (and are usually customizable as well).

When you get started, focus on memorizing a small set of commands (which are all covered below): kill, slurp forward, barf forward, splice, and raise. As you encounter other scenarios find the appropriate structural editing command and add it to your toolkit.

Balanced forms

A general principle of structural editing is to ensure that all forms are balanced at all times. This is immediately apparent when creating new forms in your code. If you type the beginning delimiter for a collection, your editor will also insert the ending delimiter. If you type (, your editor will insert () with your cursor in the middle so you can keep typing inside the form. Most editors also provide some visual feedback that match beginning and ending delimiters as you move across them, or may even support code folding and unfolding based on this structure.

One common issue you may encounter (especially while learning) is the accidental use of a line editing command that unbalances your code structure. For example, if you have a multi-line nested expression, deleting one line from the middle is highly likely to delete a left paren but not it’s matching right paren on a subsequent line. Don’t panic!

Some common ways to fix this situation:

  • Undo - often you can simply undo that line command and then use a structural editing command instead

  • Turn off structural editing, fix the problem, and re-enable structural editing - in some editors there is a button in the footer or elsewhere to toggle structural editing and this is relatively easy, in others it will harder.

  • Use character editing commands to fix - in some cases, such as a dangling opening paren to delete, you can select the opening paren and use character deletion to delete it (outside structural editing)

  • Use comments and character editing - if you have a dangling open paren to close, you can insert a Clojure comment ;, allowing you to type the ), then select the ; and delete it with character editing

Navigation and selection

Navigation (and selection, which typically rides along as a modifier) is an extension of the navigation and selection you already have for characters and lines. Instead of selecting the next word, you also have options to select the next expression or widen by selecting the containing expression (up the tree). And don’t forget that you can still use your mouse to select if that’s the most comfortable thing for you.

Cut/paste aka kill/yank

Cutting text in Emacs is known as "killing" and pasting is known as "yanking". In Emacs, killing puts the cut block of text into the "kill ring", which is effectively a stack. By default, "yanking" pastes the text at the top of the stack and removes it from the kill ring. Whether your editor supports all of this functionality will depend on the editor, but the important thing is that when you are looking at key bindings, "kill" means "cut".

By default, most structural editors will cut from the current location to the end of the current collection. Most editors have a variety of other options, but simply the default kill and selection are sufficient for most of your work.

Slurping and barfing

The "slurp forward" command slurps the expression after the current collection into the collection as the last item. "Barf forward" does the opposite, moving the last item of the collection outside and after the current collection. Backward variants that operate on the first element of the collection exist as well but are much less common.

Splicing and raising

One common scenario that occurs is to want to splice the contents of your current collection into the parent collection. When doing that you have the option to drop the items before ("splice killing backwards") or after ("splice killing forwards"). You may also want to replace the parent with only the value of the element at the cursor and this is known as "raise".

Parinfer

Several years ago, a new alternative to Paredit was developed called Parinfer. Parinfer links indentation to nesting and thus allows you to insert a child expression simply by indenting your code. Many editors support Parinfer as an alternative to Paredit. If you are new to structural editing, you should try it to see if it makes sense to you! In general, this seems to be a matter of personal preference.

Resources

The resources below may be useful for you as an additional step toward either learning the paredit commands or how to use structural editing in a particular editor. In general, most core command sets are similar and have configurable key bindings. Often, the default key bindings trace back to Paredit in Emacs, but don’t feel constrained by that, it’s common to customize these.

Original author: Alex Miller