Skip to content

Commit 51c68c8

Browse files
committed
Use around advice to deal with skipped post-command-hook
When a command uses the minibuffer and the user aborts, then `post-command-hook' is not run after that command. See bug#61176. We can recover from that because if a command uses the minibuffer, then `post-command-hook' is also/instead run when the minibuffer is first uses. We can distinguish this premature run because `this-command-keys-vector' returns an empty vector in this case. The previous approach already took advantage of this. The premature `post-command-hook' run delayed work until a later `post-command-hook' run of *another* command, namely the command that exits the minibuffer. That relied on heuristics and was unreliable. The new approach still uses `post-command-hook'. If the command does not use the minibuffer, it still takes care of all the work that has to happen after the command has run. But the premature run that is causes by the use of the minibuffer, now redirects work to an around advice instead of to another run of the hook. The advice has to be put in place before the command is called, so it is done on `pre-command-hook'. It also has to take care of removing itself once the command is done running. We use an around advice that wraps both the command body and its interactive form with `unwind-protect'. The advice always takes care of removing itself, and if the command does not use the minibuffer, then that is all it does. However, if a premature `post-command-hook' run happens, then that instructs the advice, to also perform the work usually done in the hook. This is done by setting the `unwind-suffix' slot of the prefix object to the function that performs the appropriate cleanup. If the command has third-party after advices, then those run after that has happened and therefore such advices to not have access to transient variables, provided the command also uses the minibuffer. The implementation in this commit requires Emacs 30, or more precisely c39c26e33f6bb45479bbd1a80df8c97cf750a56a, which fixes bug#61179. The next commit changes it to also work in older Emacs versions.
1 parent 327409a commit 51c68c8

File tree

1 file changed

+66
-80
lines changed

1 file changed

+66
-80
lines changed

lisp/transient.el

Lines changed: 66 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,8 @@ If `transient-save-history' is nil, then do nothing."
633633
(transient-non-suffix :initarg :transient-non-suffix :initform nil)
634634
(incompatible :initarg :incompatible :initform nil)
635635
(suffix-description :initarg :suffix-description)
636-
(variable-pitch :initarg :variable-pitch :initform nil))
636+
(variable-pitch :initarg :variable-pitch :initform nil)
637+
(unwind-suffix :documentation "Internal use." :initform nil))
637638
"Transient prefix command.
638639
639640
Each transient prefix command consists of a command, which is
@@ -1411,17 +1412,6 @@ Usually it remains current while the transient is active.")
14111412

14121413
(defvar transient--history nil)
14131414

1414-
(defvar transient--abort-commands
1415-
'(abort-minibuffers ; (minibuffer-quit-recursive-edit)
1416-
abort-recursive-edit ; (throw 'exit t)
1417-
exit-recursive-edit ; (throw 'exit nil)
1418-
keyboard-escape-quit ; dwim
1419-
keyboard-quit ; (signal 'quit nil)
1420-
minibuffer-keyboard-quit ; (abort-minibuffers)
1421-
minibuffer-quit-recursive-edit ; (throw 'exit (lambda ()
1422-
; (signal 'minibuffer-quit nil)))
1423-
top-level)) ; (throw 'top-level nil)
1424-
14251415
(defvar transient--scroll-commands
14261416
'(transient-scroll-up
14271417
transient-scroll-down
@@ -2078,11 +2068,14 @@ value. Otherwise return CHILDREN as is."
20782068
(not (memq this-command '(transient-quit-one
20792069
transient-quit-all
20802070
transient-help))))
2081-
(setq this-command 'transient-set-level))
2071+
(setq this-command 'transient-set-level)
2072+
(transient--wrap-command))
20822073
(t
20832074
(setq transient--exitp nil)
2084-
(when (eq (transient--do-pre-command) transient--exit)
2085-
(transient--pre-exit))))))
2075+
(let ((exitp (eq (transient--do-pre-command) transient--exit)))
2076+
(transient--wrap-command)
2077+
(when exitp
2078+
(transient--pre-exit)))))))
20862079

20872080
(defun transient--do-pre-command ()
20882081
(if-let ((fn (transient--get-predicate-for this-command)))
@@ -2164,7 +2157,7 @@ value. Otherwise return CHILDREN as is."
21642157
(remove-hook 'pre-command-hook #'transient--pre-command)
21652158
(remove-hook 'post-command-hook #'transient--post-command))
21662159

2167-
(defun transient--resume-override ()
2160+
(defun transient--resume-override (&optional _ignore)
21682161
(transient--debug 'resume-override)
21692162
(when (and transient--showp transient-hide-during-minibuffer-read)
21702163
(transient--show))
@@ -2200,71 +2193,64 @@ value. Otherwise return CHILDREN as is."
22002193
(remove-hook 'minibuffer-exit-hook ,exit)))
22012194
,@body)))
22022195

2203-
(defun transient--post-command-hook ()
2204-
(run-hooks 'transient--post-command-hook))
2205-
2206-
(add-hook 'post-command-hook #'transient--post-command-hook)
2207-
2208-
(defun transient--delay-post-command (&optional abort-only)
2209-
(transient--debug 'delay-post-command)
2210-
(let ((depth (minibuffer-depth))
2211-
(command this-command)
2212-
(delayed (if transient--exitp
2213-
(apply-partially #'transient--post-exit this-command)
2214-
#'transient--resume-override))
2215-
post-command abort-minibuffer)
2216-
(unless abort-only
2217-
(setq post-command
2218-
(lambda () "@transient--delay-post-command"
2219-
(let ((act (and (not (equal (this-command-keys-vector) []))
2220-
(or (eq this-command command)
2221-
;; `execute-extended-command' was
2222-
;; used to call another command
2223-
;; that also uses the minibuffer.
2224-
(equal
2225-
(ignore-errors
2226-
(string-to-multibyte (this-command-keys)))
2227-
(format "\M-x%s\r" this-command))))))
2228-
(transient--debug 'post-command-hook "act: %s" act)
2229-
(when act
2230-
(remove-hook 'transient--post-command-hook post-command)
2231-
(remove-hook 'minibuffer-exit-hook abort-minibuffer)
2232-
(funcall delayed)))))
2233-
(add-hook 'transient--post-command-hook post-command))
2234-
(setq abort-minibuffer
2235-
(lambda () "@transient--delay-post-command"
2236-
(let ((act (and (or (memq this-command transient--abort-commands)
2237-
(equal (this-command-keys) ""))
2238-
(= (minibuffer-depth) depth))))
2239-
(transient--debug
2240-
'abort-minibuffer
2241-
"mini: %s|%s, act %s" (minibuffer-depth) depth act)
2242-
(when act
2243-
(remove-hook 'transient--post-command-hook post-command)
2244-
(remove-hook 'minibuffer-exit-hook abort-minibuffer)
2245-
(funcall delayed)))))
2246-
(add-hook 'minibuffer-exit-hook abort-minibuffer)))
2196+
(defun transient--wrap-command ()
2197+
(letrec ((prefix transient--prefix)
2198+
(suffix this-command)
2199+
(advice (lambda (fn &rest args)
2200+
(interactive
2201+
(lambda (spec)
2202+
(let ((abort t))
2203+
(unwind-protect
2204+
(prog1 (advice-eval-interactive-spec spec)
2205+
(setq abort nil))
2206+
(when abort
2207+
(when-let ((unwind (oref prefix unwind-suffix)))
2208+
(transient--debug 'unwind-interactive)
2209+
(funcall unwind suffix))
2210+
(if (symbolp suffix)
2211+
(advice-remove suffix advice)
2212+
(remove-function suffix advice))
2213+
(oset prefix unwind-suffix nil))))))
2214+
(unwind-protect
2215+
(apply fn args)
2216+
(when-let ((unwind (oref prefix unwind-suffix)))
2217+
(transient--debug 'unwind-command)
2218+
(funcall unwind suffix))
2219+
(if (symbolp suffix)
2220+
(advice-remove suffix advice)
2221+
(remove-function suffix advice))
2222+
(oset prefix unwind-suffix nil)))))
2223+
(if (symbolp suffix)
2224+
(advice-add suffix :around advice '((depth . -99)))
2225+
(add-function :around (var suffix) advice '((depth . -99))))))
2226+
2227+
(defun transient--premature-post-command ()
2228+
(and (equal (this-command-keys-vector) [])
2229+
(= (minibuffer-depth)
2230+
(1+ transient--minibuffer-depth))
2231+
(progn
2232+
(transient--debug 'premature-post-command)
2233+
(transient--suspend-override)
2234+
(oset (or transient--prefix transient-current-prefix)
2235+
unwind-suffix
2236+
(if transient--exitp
2237+
#'transient--post-exit
2238+
#'transient--resume-override))
2239+
t)))
22472240

22482241
(defun transient--post-command ()
2249-
(transient--debug 'post-command)
2250-
(transient--with-emergency-exit
2251-
(cond
2252-
((and (equal (this-command-keys-vector) [])
2253-
(= (minibuffer-depth)
2254-
(1+ transient--minibuffer-depth)))
2255-
(transient--suspend-override)
2256-
(transient--delay-post-command (eq transient--exitp 'replace)))
2257-
(transient--exitp
2258-
(transient--post-exit))
2259-
((eq this-command (oref transient--prefix command)))
2260-
(t
2261-
(let ((old transient--redisplay-map)
2262-
(new (transient--make-redisplay-map)))
2263-
(unless (equal old new)
2264-
(transient--pop-keymap 'transient--redisplay-map)
2265-
(setq transient--redisplay-map new)
2266-
(transient--push-keymap 'transient--redisplay-map)))
2267-
(transient--redisplay)))))
2242+
(unless (transient--premature-post-command)
2243+
(transient--debug 'post-command)
2244+
(transient--with-emergency-exit
2245+
(cond (transient--exitp (transient--post-exit))
2246+
((eq this-command (oref transient--prefix command)))
2247+
((let ((old transient--redisplay-map)
2248+
(new (transient--make-redisplay-map)))
2249+
(unless (equal old new)
2250+
(transient--pop-keymap 'transient--redisplay-map)
2251+
(setq transient--redisplay-map new)
2252+
(transient--push-keymap 'transient--redisplay-map))
2253+
(transient--redisplay)))))))
22682254

22692255
(defun transient--post-exit (&optional command)
22702256
(transient--debug 'post-exit)
@@ -2354,7 +2340,7 @@ value. Otherwise return CHILDREN as is."
23542340
(when transient--debug
23552341
(let ((inhibit-message (not (eq transient--debug 'message))))
23562342
(if (symbolp arg)
2357-
(message "-- %-18s (cmd: %s, event: %S, exit: %s%s)"
2343+
(message "-- %-22s (cmd: %s, event: %S, exit: %s%s)"
23582344
arg
23592345
(or (ignore-errors (transient--suffix-symbol this-command))
23602346
(if (byte-code-function-p this-command)

0 commit comments

Comments
 (0)