2021-02-20 Using keyboard macros to emulate query replace

As you might have noticed, I hardly ever write about TeX anymore. This is because I use it much less these days – apart from my work for Wiadomości Matematyczne (which will end in a few weeks, too) I don’t really need it that much anymore. (Well, I will use it from time to time, of course, for larger writing projects, and there are a few of them on the horizon.)

A few days ago, however, I needed to do something I consider really blogworthy, even though the tip I’m going to share today is (surprise, surprise!) very Emacs-centric.

I was editing a paper which used italics (with the \emph LaTeX macro) really a lot (more than 100 times). Very many (but not all) of its occurrences were to be changed to quotes, so that e.g. \emph{petrichor} should be changed to ,,petrichor’’ etc.

I could do that with query-replace-regexp, of course, assuming that there were no braces within the italic text. As we know, regular expressions are not the right tool if we want to do delimiter pairing. Hence I decided to go another route and use keyboard macros – Emacs has the very useful forward-sexp (and backward-sexp) commands which take nested delimiters into account.

So, I first wanted to have a sequence of keystrokes that would find the next occurrence of \emph and wait for my input. This is easy enough, so I just needed to record the sequence C-s \emph RET C-x q. When playing the macro back, Emacs would stop there and let me press SPC or y to continue or DEL or n to skip the rest of the macro (which is a lot like query-replace). If I want to do the replacing in this particular place, there is one minor gotcha: I can’t replace the opening brace with the opening quote, since then forward-sexp won’t have the correct “starting point”. This means that I want to somehow “store” the position of the opening brace, jump (with forward-sexp) to the closing one, change it into a closing quote, jump back (without backward-sexp, which wouldn’t work now as there is no closing brace anymore) and change the opening brace into an opening quote. Fortunately, Emacs has exactly what I need to store a position in the buffer and jump back to it – the mark. So, here is another sequence of keystrokes to add to my macro:

M-DEL			;; kill the "emph"
DEL			;; delete the backslash
2*C-SPC			;; set mark and deactivate mark (no need for region!)
M-C-f			;; move to the closing brace...
DEL			;; ...and delete it
2*'			;; type the closing quote
C-u C-SPC		;; go back to the opening brace...
C-d			;; ...and delete it
2*,			;; type the closing brace

and that’s it, I finished it with <f4>. Notice that I used double C-SPC so that the region doesn’t get activated – this wasn’t really necessary, single C-SPC would do, too.

And now replacing the italics with quotes is easy: just press <f4> and then y or n, then <f4> again etc.

But wait, that’s not all! I streamlined this process even more. Emacs keyboard macros have this very useful feature where you can tell Emacs to play a macro several times by giving a prefix argument to <f4>. And since recording a macro which finds (e.g. using C-s) a place in the buffer, operates on it somehow and then moves on to the next occurrence is quite a common use-case, you can also give a prefix argument of zero to repeat the macro indefinitely until Emacs beeps (more or less, i.e., until you get an error or press C-g). So, I could now say C-u 0 <f4> and just press y or n repeatedly until I got to the last occurrence when Emacs would beep at me and break out of the macro loop.

Well, of course all this can be done in a different way. One other solution to my problem would be to use multiple cursors – although this one has a drawback in that not all instances of the changed string would be visible at once, and editing things I don’t see is something I really don’t like. I could have used query-replace-regexp anyway, since it just so happened that I didn’t have any braces within my \emph macros. If I knew I wanted to replace all instances, I could use Iedit. Finally, I could define a one-parameter TeX macro \quote putting its parameter in quotes and just query-replace the string \emph with \quote, keeping the parameter (with or eithout braces) intact. But I figured that the trick with keyboard macros, even if not the simplest one, is worth sharing – especially that it can be used with things other than simple replacement, like changing the lettercase etc.

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryTeX, CategoryLaTeX