Declarative text editing

May 10, 2016 by Jeroen

Say you are writing a document and want to replace the word under the cursor. Pretend | is the cursor, and brown needs to be changed to red:

The quick br|own fox jumps over the lazy dog.

Ask your average text editor (the person, not the program) how they would accomplish this and you will probably get an explanation like this

  1. Grab the mouse
  2. Place the cursor at the front of the word
  3. Press and hold the left mouse button
  4. Move the cursor to the end of the word
  5. Press backspace (or delete)
  6. Type the new word

A more experienced user will prefer to navigate and select text using the keyboard. The procedure then looks as follows:

  1. Press the left arrow twice
  2. Press and hold down control and shift
  3. Press right
  4. Start typing the new word

Although the steps differ, the outcome is the same. Both solutions are formulated in an imperative style, focussing on *how* the task is accomplished.

A declarative formulation of the solution looks as follows:

  1. Change the text under the cursor to red.

The declarative solution is identical to the problem. This is because declarative formulations focus on the intent of the user, or *what* needs to be done. The instructions given in a declarative approach carry much more meaning.

Because of this, a declarative approach is succinct, noise-free and therefore less error-prone. If you now feel to urge to interject that this method is hardly relevant for your daily text editing, it could mean your text editor (the program) cannot handle declarative solutions, or, better, you haven’t yet discovered that it actually can!

vim, a declarative text editor

Let us now have a look at a text editor that supports declarative text editing: vim. When vim starts, it awaits the first formulation of what needs to be done. This mode is so common that it is known as normal mode. In normal mode keystrokes are not translated into characters on screen, but instead interpreted as descriptions of the desired changes.

For the purpose of this document, I will be talking about change descriptors rather than vim commands. This is because commands in vim actually describe what the desired change is.

A first example

For the intents and purposes of this document, let’s assume we’ve found ourselves in the situation that I started this post with:

The quick br|own fox jumps over the lazy dog.

The change descriptor ciwred<ESC> instructs vim to change the word under the cursor to red. The prefix ciw describes the sort of change, namely that we wish to change the word under the cursor, red obviously is the replacement and <ESC> ends the change descriptor. We end up with the desired result:

The quick re|d fox jumps over the lazy dog.

Let’s break the descriptor. In normal mode pressing c indicates we want to make a change. Next, vim expects a motion that dictates the scope of the change. iw tells vim, we desire the to change all characters in the word. After this, vim drops us into insert mode. Characters entered in this mode are placed in the document. Finally, <ESC> ends the instruction.

One more example

Now I realise the gain of a declarative approach for such a small change seems modest. I hope this second example shows just how convoluted editing becomes in most editors. Consider yourself in the middle of a paragraph wanting to change a single sentence:

Now I realise the gain of a declarative approach 
for such a small change seems modest. I hope this
second |example shows just how convoluted editing 
becomes in most editors. Consider you find yourself
in the middle of a paragraph wanting to change a 
single sentence

Normal text editors require you to detail every step needed to change this sentence. This most likely involves first navigating to either end of the sentence by holding down control (on Windows & Linux) or option (on OS X) and pressing the arrow keys a couple of times. Next, you select all text in the sentence by holding down both shift and control (or option) and the opposite arrow key. Finally, having the entire sentence selected, you can type the replacement text.

In vim, you only state your intent to change all text in the sentence, like so:

cisThis is a replacement text.<ESC>.

No matter where in the sentence you are, the above change descriptor will always yield the same result. This approach is resilient to layout and cursor position.

More motions

While a few primitives suffice in an imperative text editor, such as the arrow-keys to move the cursor in combination with modifier keys such as control and shift, a declarative text editor should support a much more extensive vocabulary with which the user can state his or her intent. That’s why vim supports many more motions than the one we just saw. As a result vim can be pretty intimidating to a novice. However, I hope you’ll find after looking at the table below that motions follow a clear pattern.

Intent Motion Example
all characters in the current word iw
The quick br|own fox jumps over the lazy dog.

ciwred<ESC>

The quick re|d fox jumps over the lazy dog.
all characters in the current sentence is
The quick br|own fox jumps over the lazy dog.

ciwThis is something else.<ESC>

This is something else.
all characters in the current paragraph ip
The quick br|own fox jumps over the lazy 
dog. The lazy dog was sleeping.

ciwThis is something else.<ESC>

This is something else.
all up to excluding character <Char> t<Char>
The quick brown fox jumps over the lazy |dog.

ct.kangaroo<ESC>

The quick brown fox jumps over the lazy kangaro|o.

note: This is equivalent to ciwkangaroo<ESC>

all characters inside parentheses i(
The quick brown fox jumps over the lazy dog
(which w|as also brown).

ci(a labradoodle<ESC>

The quick brown fox jumps over the lazy dog
(a labradoodl|e).
all characters inside curly braces i{
do |{ something }

ci{␣nothing␣<ESC>

do { nothing| }
all characters inside an XML/HTML-tag it
<b|ody>
    <p>
        <table>
            <!-- ... -->
        </table>
    </p>
</body>

cithello<ESC>

<body>hell|o</body>

Deleting text

Of course vim allows you to create change descriptors for deleting characters as well. Rather than using c{motion}, you use d{motion} instead. You’ll probably want to use a different set of motions that extend the scope of the change by one space character. Some are presented in the table below.

delete…

Intent

Motion

Example

the current word

aw

The quick br|own fox jumps over the lazy dog.

daw

The quick |fox jumps over the lazy dog.

the current sentence

as

The quick br|own fox jumps over the lazy dog.
Grumpy wizards make toxic brew for the evil Queen.

das

Grumpy wizards make toxic brew for the evil Queen.

the current paragraph

ap

The quick br|own fox jumps over the lazy 
dog. The lazy dog was sleeping.
                
Grumpy wizards make toxic brew for the evil Queen.

dap

Grumpy wizards make toxic brew for the evil Queen.

everything in and including the parentheses

a(

The quick brown fox jumps over the lazy dog
(which w|as also brown).

da(

The quick brown fox jumps over the lazy dog |.

an XML/HTML-tag

at

<body>
    <p>
        <ta|ble>
            <!-- ... -->
        </table>
    </p>
</body>

dat

<body>
    |<p>
    </p>
</body>

Advantages

By now I hope you have realised how powerful change descriptors are. Besides getting a lot of work done, they have some additional benefits.

Granular undo’s

Clearly stating your intent also leads to sane and predictable undo functionality. Pressing u in normal mode, undoes the last change. Most other text editors coalesce consecutive keystrokes into one undo action. They can never be as accurate as vim however, because they have to guess what your intent was.

Reusability

Another benefit of change descriptors is that they are, by nature, resuable.

Consider the change descriptor ciwcow<ESC>. It will function regardless of the current word’s length, or the position of the cursor within the word.

To repeat the last change, simply press . (the dot key). You can even get meta by changing change descriptors using change descriptors! Simply press q: (q followed by a colon).

Reusability or repeatability is a key concept in Drew Neil’s excellent book Practival Vim.

Rounding off

This turned into a much longer blog post than I intended. Hopefully you’ve seen that vim complements a declarative approach to programming with a similar approach to text editing. Most arguments for vim talk about gains in efficiency, not leaving the home row with your fingers and ignoring the mouse. I second them, but they are all consequences from a very different take on text editing.

I hope the next time you edit a document, you think about what your intent is. You are never just typing, you are always making meaningful discrete changes. So take a moment before you commit a change, formulate your intent, and, if your editor supports it, come up with an expressive change descriptor.

This way you will slowly extend your vocabulary and build your proficiency.