Update 5/ii/2021: It turns out that C-x z
not repeating complex commands
was an upstream bug introduced in Emacs 22, and is now fixed in the
development branch of Emacs 28. So the following code is only interesting to
users of Emacs 22 to 27.
In Emacs, you can use C-x z
to repeat the last command you input, and
subsequently you can keep tapping the ‘z’ key to execute that command again
and again. If the command took minibuffer input, however, you’ll be asked for
that input again. For example, suppose you type M-z :
to delete through the
next colon character. If you want to keep going and delete through the next
few colons, you would need to use C-x z : z : z :
etc. which is pretty
inconvenient. So there’s also C-x ESC ESC RET
or C-x M-: RET
, which will
repeat the last command which took minibuffer input, as if you’d given it the
same minibuffer input. So you could use M-z : C-x M-: RET C-x M-: RET
etc.,
but then you might as well just keep typing M-z :
over and over. It’s also
quite inconvenient to have to remember whether you need to use C-x z
or C-x
M-: RET
.
I wanted to come up with a single command which would choose the correct repetition method. It turns out it’s a bit involved, but here’s what I came up with. You can use this under the GPL-3 or any later version published by the FSF. Assumes lexical binding is turned on for the file you have this in.
;; Adapted from `repeat-complex-command' as of November 2020
(autoload 'repeat-message "repeat")
(defun spw/repeat-complex-command-immediately (arg)
"Like `repeat-complex-command' followed immediately by RET."
(interactive "p")
(if-let ((newcmd (nth (1- arg) command-history)))
(progn
(add-to-history 'command-history newcmd)
(repeat-message "Repeating %S" newcmd)
(apply #'funcall-interactively
(car newcmd)
(mapcar (lambda (e) (eval e t)) (cdr newcmd))))
(if command-history
(error "Argument %d is beyond length of command history" arg)
(error "There are no previous complex commands to repeat"))))
(let (real-last-repeatable-command)
(defun spw/repeat-or-repeat-complex-command-immediately ()
"Call `repeat' or `spw/repeat-complex-command-immediately' as appropriate.
Note that no prefix argument is accepted because this has
different meanings for `repeat' and for
`spw/repeat-complex-command-immediately', so that might cause surprises."
(interactive)
(if (eq last-repeatable-command this-command)
(setq last-repeatable-command real-last-repeatable-command)
(setq real-last-repeatable-command last-repeatable-command))
(if (eq last-repeatable-command (caar command-history))
(spw/repeat-complex-command-immediately 1)
(repeat nil))))
;; `suspend-frame' is bound to both C-x C-z and C-z
(global-set-key "\C-z" #'spw/repeat-or-repeat-complex-command-immediately)