Hey, I was working on this one off and on for a few days after briefly trying out skeleton-mode, yasnippet, and some other stuff, and not really being too happy with them. I find that I have a lot of repetitive editing tasks where I need to do something to a small block of code a lot, but in the process change some names or values in a way that’s just a little bit different each time. Normally this is where people would start to reach for yasnippet and auto-yasnippet, which is fine if that works for them, but personally that’s just a bit more heavyweight and powerful than what I normally need. What I wanted was just a way to enhance a regular Emacs keyboard macro to support that sort of thing, so I wrote this. If it helps you too, wonderful!
To use, just press C-x Q (that’s a capital Q, not a lowercase q) during keyboard macro recording, and press your normal enter/return/minibuffer-exit when you’re done. I went through a lot of trouble figuring out how to make the minibuffer exit also exit the sub-macro recording!
;; Keyboard macro enhancement. If you call this, instead of
;; kbd-macro-query, it will prompt the user for a value. This value
;; will then be inserted into the buffer. Every time you call the
;; macro, you can provide a different value.
;;
;; Alternatively, you can call this with a prefix argument. If you do
;; this, you will be prompted for a symbol name. Instead of the value
;; being inserted into the buffer, it will be saved in the symbol
;; variable. You can then manipulate it or do whatever you want with
;; that symbol as part of the keyboard macro. Just, when you do this,
;; make sure you don't use minibuffer history at all when defining the
;; macro, or you can get some unexpected behavior if you save your
;; macro for later use and try it a few hours later!
(defun config:macro-query (symbol)
(interactive
(list (when current-prefix-arg
(intern (read-from-minibuffer "symbol: ")))))
(cl-flet ((internal-exit ()
(interactive)
(exit-recursive-edit)))
(let ((making-macro defining-kbd-macro) ;; Save value.
(temp-map (make-sparse-keymap)))
;; Temporarily bind what is normally C-M-c (exit-recursive-edit)
;; to RET, so RET will work in the spawned minibuffer.
(set-keymap-parent temp-map minibuffer-local-map)
(substitute-key-definition 'exit-minibuffer #'internal-exit temp-map)
(let ((exit-fn (set-transient-map temp-map (-const t))))
(cl-flet ((also-quit-minibuffer ()
;; When this is called (advice after
;; recursive-edit), this-command should be
;; whatever was just used to exit the recursive
;; edit / minibuffer. Usually RET. Push that onto
;; the unread commands, and it will immediately
;; get picked up and executed. We also want to use
;; this moment to turn off the transient map.
(funcall exit-fn)
(when making-macro
(setq unread-command-events
(nconc (listify-key-sequence (this-command-keys))
unread-command-events)))))
(advice-add 'recursive-edit :after #'also-quit-minibuffer)
(unwind-protect
(let ((input (minibuffer-with-setup-hook
(lambda ()
(kbd-macro-query t))
(read-from-minibuffer "Value: "))))
(if symbol
(set symbol input)
(insert input)))
;; Ensure that the advice and minibuffer map goes back to
;; normal.
(advice-remove 'recursive-edit #'also-quit-minibuffer)
(funcall exit-fn)))))))
(global-set-key (kbd "C-x Q") 'config:macro-query)