Incrementing Macros in Emacs

Emacs macros are amazing. I was editing a document today, which started off as a list of sentences which I wanted to convert into numbered headlines, followed by some boilerplate text. Let's suppose the list looked like this:

this is the first thing
this is the second thing
this is the third thing

What I wanted to do was convert this to:

1) this is the first thing

- why does it matter?
- who cares anyway?
- will anyone ever read this?

2) this is the second thing

- why does it matter?
- who cares anyway?
- will anyone ever read this?

3) this is the third thing

- why does it matter?
- who cares anyway?
- will anyone ever read this?

But there's a catch. I didn't want to start at 1... I wanted to start at 3. And the list was pretty long. Of course, I could have just done it with pointing and clicking and copying and pasting. But this is the sort of thing that emacs macros are perfect for.

An introduction to Emacs macros

Emacs macros are pretty simple. Think of them as like a tape recorder. You record something, and then you can play them back as many times as you like. Let's do a really simple one. To start a macro, we use C-x (, then we do some stuff (which is recorded), and then we use C-x ) to signal that we've finished recording our macro. To use the macro, we simply do C-x e, which runs the function call-last-kbd-macro.

Let's begin with the text:

Stephen is awesome
Patrick is awesome
Lindsay is awesome

We'd like to insert the adjective 'really', and append an exclamation mark. We can record a macro for this:

C-x (
really <space> C-e !
C-x )

So we press C-x (, then type really, followed by a space, followed by C-e (end of line), followed by !, then C-x ). To use the macro, we move the point to just before 'awesome', and then press C-x e. We repeat for each line. The result is:

Stephen is really awesome!
Patrick is really awesome!
Lindsay is really awesome!

Applying a macro to a region

Running a macro a bunch of times is a bit of a bore. You can run it a set number of times, by prefacing C-x e with C-u and a number. So C-u 3 C-x e will execute your macro three times. In my case I would have needed to count the number of lines, and then ensure my macro advanced to the next line, and handled the last case, and and. Yuck. Thankfully Emacs allows a macro to be applied to all the lines in a region. Simply mark the region in the usual manner (C-space, move point to end of region), and then run C-x C-k r (apply-macro-to-region-lines). Naturally this forces us to rethink how to create our macro. In the case of the really awesome line, one approach would be to use incremental search to find the place to start to insert really. So now our macro becomes:

C-x (
C-s is a <RET> really <space> C-e !
C-x )

Another would be to go to the end of the line with C-e, and then back a word with M-b. On reflection I prefer the second, but the use of incremental search is a handy trick when building macros, so I'll leave it here as an option.

At this stage you're probably realising that when you record your macro, you end up having to change some text. I tend to make a copy of the first line, so I can use that for recording the macro, and then remove it at the end.

Using counters

The final step in my macro was to work out how to increment numbers. There are a couple of ways to do this. Emacs macros support the notion of registers, on which you can perform functions. This proved to be tricky, because I couldn't work out how to handle the case where the counter started to take up two characters rather than one. Additionally, we can always drop into Emacs LISP to produce a counting sequence, but I wasn't sure how to apply that expression to a region. Eventually I settled on using the built-in counters in macros.

When a keyboard macro is recorded, Emacs maintains a counter which increments every time the macro is used. The macro starts at zero, when you define the counter, so will be at 1 on the first use. We have access to the value of the counter within the macro by the function C-x C-k C-i (kmacro-insert-counter). Let's show this in action with our awesome macro:

C-x (
C-s is a <RET> really <space>
C-e ! (counter is: C-x C-k C-i)
C-x )

Applying this to our list gives:

Stephen is really awesome! (counter is: 1)
Patrick is really awesome! (counter is: 2)
Lindsay is really awesome! (counter is: 3)

So we're nearly there. The only snag is that I wanted to start at 3. That's easily accommodated with the kmacro-set-counter function. Prefacing this with M-2 will set the counter at 2, for the first go, i.e. the recording session. The next time the macro is run the counter will be at 3. So my macro looked like this:

M-2 C-x C-k C-c ;; NB we need to set this outside of the macro, or it'll reset each time
C-x (
C-a
C-x C-k C-i ) <space>
C-e
<enter>
- why does it matter? <enter>
- who cares anyway? <enter>
- will anyone ever read this? <enter>
<enter>
C-x )

Let's try this with our awesome macro, so we get:

1) Stephen is really awesome! (counter is: 1)
2) Patrick is really awesome! (counter is: 2)
3) Lindsay is really awesome! (counter is: 3)

We don't need to worry about setting the counter - we're happy with zero. So:

C-x (
C-a C-x C-k C-i ) <space> 
C-e M-b really <space>
C-e (counter is: C-a C-x C-i)
C-x )

At this stage you've probably realised something's awry.... as you record the macro, you probably saw something like:

0) Stephen is really awesome! (counter is: 1)

And if you persevered, you might have seen:

2) Stephen is really awesome! (counter is: 3)
4) Patrick is really awesome! (counter is: 5)
6) Lindsay is really awesome! (counter is: 7)

How do we fix this? The answer is to insert a C-u the second time we use the counter, to reset it. So the final macro (for our awesome text) is:

C-x (
C-a
C-x C-k C-i ) <space>
C-e
M-b
really <space>
C-e
<space> (counter is C-u C-x C-k C-i)
C-x )

And our end result is:

1) Stephen is really awesome! (Counter is: 1)
2) Patrick is really awesome! (Counter is: 2)
3) Lindsay is really awesome! (Counter is: 3)

Conclusion

Emacs macros are amazingly powerful. You can get immediate benefit from them right away, and with a bit of creative thought you can accomplish some pretty remarkable things. Hopefully this captures your imagination, and sets you to thinking about how you can make use of them.

Show Comments