Journal

2024-03-02 Some tips about Emacs keyboard macros

Some time ago I had to create some rather repetitive code. These days I often use multiple cursors for such things, but for some reasons this time I decided to go the traditional route and use the built-in keyboard macros. Here’s the catch, though. When you want to use keyboard macros and insert an (incremented) number for every occurrence, you can type f3 (kmacro-start-macro-or-insert-counter) while recording the macro. What I needed, though, was to insert that number twice for every execution of the macro. Typing f3 twice is no good, since every press of it increments the counter. Without giving it much thought, I tried to press C-u f3 during recording of the macro, hoping that it would do what I needed – and lo and behold, it did! As usual, Emacs commands turn out to be well thought-out and intuitive to use (once you get used to the general Emacs philosophy, that is).

After this small success, I decided to go to the manual to learn what other tricks are possible with keyboard macros. I certainly read it – I did read most of the manual, after all, but that was more than two decades ago. Emacs does not stand still, and a lot of functionality has been added since then – and it keeps being added all the time. (For example, when I first learned keyboard macros, you had to use C-x ( and C-x ) to record a macro, C-x e to execute it and C-x C-k C-i to insert the counter. These are not bad bindings, but f3 and f4 are so much simpler!)

And indeed, skimming through the relevant chapter of the manual (and pressing C-x C-k C-h – it turns out that not all macro-related commands are even documented in the manual!) revealed a few gems. One of them (I knew about) is that when you replay the macro, you can give f4 a numeric argument to tell Emacs how many times it should repeat the execution of the macro. That is useful enough, but here’s a bonus: if the argument you give is zero, it runs the macro repeatedly until it encounters an error. This is especially useful if your macro makes some change and moves to the next place to be changed – Emacs movement commands usually signal an error when you try to move past the buffer end, so this is an easy way of telling Emacs to do something “in the rest of the (visible portion of the) buffer”. (Note that giving f3 a negative argument is probably not a good idea – it will repeat the macro indefinitely without any stop condition. As of writing this, it is not documented, I’m not sure if it’s even intended and I found about it the hard way;-).)

One of the coolest thing about Emacs keyboard macros, however, is that you can have more than one of them. Much like kills, they are stored on a “ring”, and when you record a new one, the older ones are pushed down the ring instead of simply forgotten. You can then rotate the ring using C-x C-k C-p and C-x C-k C-n, effectively switching to older or newer macros as the “last macro”. If you just sometimes need the second-to-last entry on the ring and never the earlier ones, you can also use C-x C-k C-t to swap the two entries on the top of the macro ring. Of course, remembering what is in the macro ring and in what order requires some superhuman abilities, but the commands which change the top entry on the macro ring show you that top entry, so it’s not really completely blind. And while there seems to be no command to view the entire macro ring, you can say M-x kmacro-view-macro to see the definition of the macro at the head of the ring and kmacro-view-ring-2nd to see the next one. You can also use the plain old C-h v kmacro-ring to see whole ring – but without the head, which is stored in last-kbd-macro. (Interestingly, C-h v last-kbd-macro is less helpful since it is displayed in a more “internal” format. I have no idea why this works this way, but as part of the experiments while writing this blog post I recorded a macro consisting of two keystrokes, M-c M-f. When it went down the ring, it was displayed – as part of the kmacro-ring variable – this way: #f(kmacro "M-c M-f"); however, when it was at the head, C-h v last-kbd-macro showed this: [134217827 134217830]. Go figure.) And here is another nugget of gold: you can use the command kmacro-view-macro-repeat, bound to C-x C-k C-v, to see the last macro, and if you repeat it more times (and you can press just C-v to do that), Emacs shows you the subsequent macros on the ring – so, to see the third one, you can just press C-x C-k C-v C-v C-v. (Needless to say, it does not change the top entry of the ring.)

What I miss (a bit) is a way to replace the head of the macro ring instead of just appending to it. It’s not a big deal, since it is easy to remove the last macro, using C-x C-k C-d, so that if you make a mistake while recording, you can use that not to pollute the macro ring with incorrect entries. Also, if you expect to need a macro in the future, you can save it in a few ways – give it a name (C-x C-k n, the name will be usable as an M-x command) or a keybinding (C-x C-k b, either as a binding of C-x C-k followed by a digit or a capital letter or any binding – read the manual for the details). You can also save a macro to a register using C-x C-k x – and then, “jumping” to that register will replay that macro. (If you don’t know about Emacs registers, they are a pretty obscure but very useful feature I like a lot.) And if you named your macro, you can open your init file (M-x find-init-file) and say M-x insert-kbd-macro so that Emacs generates Elisp code to define your macro and give it the name you have chosen in every subsequent Emacs session. This way you can extend Emacs without any knowledge of Emacs Lisp! (Of course, if you want to extend Emacs, using Elisp is probably the best thing you can do – as usual, if you want to learn it, I recommend the Introduction to programming in Emacs Lisp by the late Robert J. Chassell and also my book, Hacking your way around in Emacs, which is sort of a “sequel” to it, meaning it covers more advanced material).

Anyway, another cool thing is that if you record a long macro and then find out you’ve made some simple mistake in it, you don’t have to record it again from scratch. You can simply edit the (last) macro, using C-x C-k C-e. Try it out, it’s insanely good! And if you press C-h m while editing a macro, you’ll see a detailed description of the Edit Macro mode.

The last thing I’d like to mention is that you can record a macro which is (sort-of) interactive. I wanted to write more about it, but it turned out that I already did;-). (It is kind of ironic that I ended that post like this: “it may be good to remember that something like this exists. Hopefully I won’t forget about it anymore!”, and indeed I forgot that I wrote that!) And by the way, if you think keyboard macros would be even better with loops and conditionals, you are definitely not alone – and in Emacs Calc they actually have them!

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2024-02-26 A simple trick with URL parsing in plain text emails

Today I only have a very short tip I thought up a few days ago. If
you sometimes send URL via emails (like me), and you absolutely hate
HTML emails (like me), there is a common and annoying problem. If the
URL you send is the last thing in a sentence, and you want to be
correct and end that sentence with a period (or other punctuation), a
lot of email clients will treat that punctuation as part of the URL,
and of course such “modified” URL won’t work for the recipient. I
usually solved that by putting a space between the URL and the period
– not 100% correct, but I could live with that. A few days ago it
occurred to me that there is another, slightly hackish way to solve my
issue. From now on I’m using a hash instead of a space. Assuming
that the website I link to doesn’t have any element with the id~ of a
period (or any other weird thing like an exclamation mark, of a period
followed by a closing parenthesis etc.), the punctuation will be
ignored by the browser, but I won’t need to put any space before the
end-sentence period.

You’re welcome!

CategoryEnglish, CategoryBlog

Comments on this page

2024-02-17 Opening external drives in Dired

I use external drives pretty often – for backups, for moving files between machines, and for storing mp4 files, for example. I’ve been using UDisks for quite some time now. It automounts an external drive under the /run/media/$USER/VolumeName directory (where VolumeName is different for each drive, of course).

I also use Dired as my main file manager. As most Emacsers know, it’s far from shiny, but it’s incredibly powerful, especially combined with some other Emacs features.

One problem I have is that when I insert a drive into one of the USB ports, I’d like to be able to open it in Dired. I could of course create a bookmark pointing to it, but I don’t want to maintain a seperate bookmark for every drive I use. I could also bookmark the /run/media/mbork directory, but then I’d have to press one more key to get to the VolumeName directory. I figured that I could create a function which opens Dired in the root directory of my drive, assuming that it’s the only mounted one. (If there are more mounted drives – which means more directories under /run/media/mbork – the function should allow me to select one of them.) Of course, I could probably go even further, detect that a drive was mounted and open its directory in Dired automatically, but that might be a bit too much. Sometimes I don’t want to do anything with the drive (for example, when I use it only to make my daily backups), and sometimes I might be doing something else (for example reading or writing), and Emacs suddenly switching to another buffer would not be helpful.

Anyway, here’s some Lisp code I wrote in about 10 minutes.

(defcustom automount-directory (format "/run/media/%s" user-login-name)
  "Directory under which drives are automounted.")

(defun automount-open-in-dired ()
  "Open the automounted drive in Dired.
If there is more than one, let the user choose."
  (interactive)
  (let ((dirs (directory-files automount-directory nil "^[^.]")))
    (dired (file-name-concat
            automount-directory 
            (cond ((null dirs)
                   (error "No drives mounted at the moment"))
                  ((= (length dirs) 1)
                   (car dirs))
                  (t
                   (completing-read "Open in dired: " dirs nil t)))))))

And of course, if you want to learn to code your own convenience commands like this, as usual I recommend Introduction to programming in Emacs Lisp by the late Robert J. Chassell as an appetizer, and my book, Hacking your way around in Emacs, as the next course.

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

More...