[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/transient 7b8a7d718a 08/38: Use a more targeted approac
From: |
Jonas Bernoulli |
Subject: |
[elpa] externals/transient 7b8a7d718a 08/38: Use a more targeted approach to suspending transient override |
Date: |
Tue, 11 Jan 2022 05:37:44 -0500 (EST) |
branch: externals/transient
commit 7b8a7d718ab8d1811d86357988b664f6090f07d0
Author: Jonas Bernoulli <jonas@bernoul.li>
Commit: Jonas Bernoulli <jonas@bernoul.li>
Use a more targeted approach to suspending transient override
There are three distinct kinds of minibuffer usage that occur in
combination with the usage of transient commands. Until now we used
the same two hook functions to suspend the transient state for all of
these but that resulted in unintended behavior.
The three types of minibuffer usage are:
1. An infix command uses the minibuffer to read a new value.
Transient has complete control over the code that does this, but
we did not take full advantage of that.
2. A suffix command uses the minibuffer.
Since any command can be used as a suffix command Transient has no
direct control over it.
3. The minibuffer is already active when a transient prefix command is
invoked.
We were forced to disallow this case because the old implementation
was unable to reliably differentiate this case from the others.
Previously we tried to handle all three cases by potentially
suspending the transient state on `minibuffer-setup-hook' and resuming
it using a function on `minibuffer-exit-hook'. It turned out that
these functions could not reliably determine where in the active
transient's life-cycle the active minibuffer appears, so now we handle
each case separately.
-- No longer permanently add any functions to the minibuffer hooks.
-- Permanently add a function to `post-command-hook', which doesn't do
anything by default.
1. Add the macro `transient--with-suspended-override' to deal with the
first case only. `transient-infix-read' must use this macro around
any code that uses the minibuffer but because the around method for
`transient-infix' object takes care of that, other methods usually
don't have to do so themselves.
This macro adds anonymous functions to the minibuffer hooks and by
using `unwind-protect' it can guarantee that our suspend and resume
functions are called and then removed when done, even in case of an
error. It also guarantees that no recursive minibuffer uses are
affected by making the hook functions no-opts when they are called
on another `minibuffer-depth'.
2. In the second case we cannot use this macro, or something similar,
because the code that uses the minibuffer can be arbitrary third-
party code.
If a command uses the minibuffer, then `post-commmand-hook' is run
twice for that command; once before entering the minibuffer and
then again when the command is actually done running.
The first, premature `post-command-hook' run can be differentiated
from other runs because `this-command-keys-vector' returns an empty
vector in this case only.
We use this first `post-command-hook' run to suspend the transient.
(We cannot just perform the usual post-command work at this time,
before the command has actually finished, because the whole point
is to set some variables before the command runs and to unset them
after it is done running.)
The second `post-command-hook' run only happens if the minibuffer
is exited normally. If the minibuffer is aborted, then the second,
normal run does not happen and `minibuffer-exit-hook' has to be
used instead.
(It would be tempting to deal with this by just always delaying the
post-command work only until `minibuffer-exit-hook', but that would
not be appropriate. The command may of course use `transient-args'
and `transient-current-*' variables after using the minibuffer.)
So instead we have to delay until the second `post-command-hook' if
the minibuffer is exited normally, and until `minibuffer-exit-hook'
if it is aborted.
There is only `minibuffer-exit-hook' for both exited and aborted
uses of the minibuffer, so our function has to determine which it
is. We do so using a heuristic. We check if the command used to
get out of the minibuffer was one of the known abort commands and
because someone might use a non-standard command we otherwise also
check if the used key binding is "C-g".
We cannot add a function to `post-command-hook' during its first
run, expecting it to be called during the second run, since such an
addition does not take affect until the next command. So we add it
to `transient--post-command-hook' and make `post-command-hook'
always run that secondary hook.
3. In the third case, when the minibuffer is already active when a
transient prefix command is invoked, then that finally just works.
---
lisp/transient.el | 160 ++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 112 insertions(+), 48 deletions(-)
diff --git a/lisp/transient.el b/lisp/transient.el
index fb5752609f..4935e2b587 100644
--- a/lisp/transient.el
+++ b/lisp/transient.el
@@ -1319,6 +1319,8 @@ variable instead.")
(defvar transient--stack nil)
+(defvar transient--minibuffer-depth nil)
+
(defvar transient--buffer-name " *transient*"
"Name of the transient buffer.")
@@ -1340,6 +1342,16 @@ Usually it remains current while the transient is
active.")
(defvar transient--history nil)
+(defvar transient--abort-commands
+ '(abort-minibuffers ; (minibuffer-quit-recursive-edit)
+ abort-recursive-edit ; (throw 'exit t)
+ exit-recursive-edit ; (throw 'exit nil)
+ keyboard-quit ; (signal 'quit nil)
+ minibuffer-keyboard-quit ; (abort-minibuffers)
+ minibuffer-quit-recursive-edit ; (throw 'exit (lambda ()
+ ; (signal 'minibuffer-quit nil)))
+ top-level)) ; (throw 'top-level nil)
+
(defvar transient--scroll-commands
'(transient-scroll-up
transient-scroll-down
@@ -1695,8 +1707,6 @@ be nil and PARAMS may be (but usually is not) used to set
e.g. the
This function is also called internally in which case LAYOUT and
EDIT may be non-nil."
(transient--debug 'setup)
- (when (> (minibuffer-depth) 0)
- (user-error "Cannot invoke transient %s while minibuffer is active" name))
(transient--with-emergency-exit
(cond
((not name)
@@ -1724,6 +1734,7 @@ EDIT may be non-nil."
(setq transient--redisplay-map (transient--make-redisplay-map))
(setq transient--original-window (selected-window))
(setq transient--original-buffer (current-buffer))
+ (setq transient--minibuffer-depth (minibuffer-depth))
(transient--redisplay)
(transient--init-transient)
(transient--suspend-which-key-mode)))
@@ -1899,11 +1910,8 @@ value. Otherwise return CHILDREN as is."
(transient--debug 'init-transient)
(transient--push-keymap 'transient--transient-map)
(transient--push-keymap 'transient--redisplay-map)
- (add-hook 'pre-command-hook #'transient--pre-command)
- (add-hook 'minibuffer-setup-hook #'transient--minibuffer-setup)
- (add-hook 'minibuffer-exit-hook #'transient--minibuffer-exit)
- (add-hook 'post-command-hook #'transient--post-command)
- (advice-add 'abort-recursive-edit :after #'transient--minibuffer-exit)
+ (add-hook 'pre-command-hook #'transient--pre-command)
+ (add-hook 'post-command-hook #'transient--post-command)
(when transient--exitp
;; This prefix command was invoked as the suffix of another.
;; Prevent `transient--post-command' from removing the hooks
@@ -1978,12 +1986,17 @@ value. Otherwise return CHILDREN as is."
(defun transient--delete-window ()
(when (window-live-p transient--window)
- (let ((buf (window-buffer transient--window)))
+ (let ((remain-in-minibuffer-window
+ (and (minibuffer-selected-window)
+ (selected-window)))
+ (buf (window-buffer transient--window)))
;; Only delete the window if it never showed another buffer.
(unless (eq (car (window-parameter transient--window 'quit-restore))
'other)
(with-demoted-errors "Error while exiting transient: %S"
(delete-window transient--window)))
- (kill-buffer buf))))
+ (kill-buffer buf)
+ (when remain-in-minibuffer-window
+ (select-window remain-in-minibuffer-window)))))
(defun transient--export ()
(setq transient-current-prefix transient--prefix)
@@ -1991,49 +2004,91 @@ value. Otherwise return CHILDREN as is."
(setq transient-current-suffixes transient--suffixes)
(transient--history-push transient--prefix))
-(defun transient--minibuffer-setup ()
- (transient--debug 'minibuffer-setup)
- (unless (> (minibuffer-depth) 1)
- (unless transient--exitp
- (transient--pop-keymap 'transient--transient-map)
- (transient--pop-keymap 'transient--redisplay-map))
- (remove-hook 'pre-command-hook #'transient--pre-command)
- (remove-hook 'post-command-hook #'transient--post-command)))
-
-(defun transient--minibuffer-exit ()
- (transient--debug 'minibuffer-exit)
- (unless (> (minibuffer-depth) 1)
- (unless transient--exitp
- (transient--push-keymap 'transient--transient-map)
- (transient--push-keymap 'transient--redisplay-map))
- (add-hook 'pre-command-hook #'transient--pre-command)
- (add-hook 'post-command-hook #'transient--post-command)))
-
-(defun transient--suspend-override (&optional minibuffer-hooks)
+(defun transient--suspend-override ()
(transient--debug 'suspend-override)
(transient--pop-keymap 'transient--transient-map)
(transient--pop-keymap 'transient--redisplay-map)
(remove-hook 'pre-command-hook #'transient--pre-command)
- (remove-hook 'post-command-hook #'transient--post-command)
- (when minibuffer-hooks
- (remove-hook 'minibuffer-setup-hook #'transient--minibuffer-setup)
- (remove-hook 'minibuffer-exit-hook #'transient--minibuffer-exit)
- (advice-remove 'abort-recursive-edit #'transient--minibuffer-exit)))
+ (remove-hook 'post-command-hook #'transient--post-command))
-(defun transient--resume-override (&optional minibuffer-hooks)
+(defun transient--resume-override ()
(transient--debug 'resume-override)
(transient--push-keymap 'transient--transient-map)
(transient--push-keymap 'transient--redisplay-map)
(add-hook 'pre-command-hook #'transient--pre-command)
- (add-hook 'post-command-hook #'transient--post-command)
- (when minibuffer-hooks
- (add-hook 'minibuffer-setup-hook #'transient--minibuffer-setup)
- (add-hook 'minibuffer-exit-hook #'transient--minibuffer-exit)
- (advice-add 'abort-recursive-edit :after #'transient--minibuffer-exit)))
+ (add-hook 'post-command-hook #'transient--post-command))
+
+(defmacro transient--with-suspended-override (&rest body)
+ (let ((depth (make-symbol "depth"))
+ (setup (make-symbol "setup"))
+ (exit (make-symbol "exit")))
+ `(if (and transient--transient-map
+ (memq transient--transient-map
+ overriding-terminal-local-map))
+ (let ((,depth (1+ (minibuffer-depth))) ,setup ,exit)
+ (setq ,setup
+ (lambda () "@transient--with-suspended-override"
+ (transient--debug 'minibuffer-setup)
+ (remove-hook 'minibuffer-setup-hook ,setup)
+ (transient--suspend-override)))
+ (setq ,exit
+ (lambda () "@transient--with-suspended-override"
+ (transient--debug 'minibuffer-exit)
+ (when (= (minibuffer-depth) ,depth)
+ (transient--resume-override))))
+ (unwind-protect
+ (progn
+ (add-hook 'minibuffer-setup-hook ,setup)
+ (add-hook 'minibuffer-exit-hook ,exit)
+ ,@body)
+ (remove-hook 'minibuffer-setup-hook ,setup)
+ (remove-hook 'minibuffer-exit-hook ,exit)))
+ ,@body)))
+
+(defun transient--post-command-hook ()
+ (run-hooks 'transient--post-command-hook))
+
+(add-hook 'post-command-hook 'transient--post-command-hook)
+
+(defun transient--delay-post-command ()
+ (let ((depth (minibuffer-depth))
+ (command this-command)
+ (delayed (if transient--exitp
+ #'transient--post-exit
+ #'transient--resume-override))
+ post-command abort-minibuffer)
+ (setq post-command
+ (lambda () "@transient--delay-post-command"
+ (let ((act (and (eq this-command command)
+ (not (eq (this-command-keys-vector) [])))))
+ (transient--debug 'post-command-hook "act: %s" act)
+ (when act
+ (remove-hook 'transient--post-command-hook post-command)
+ (remove-hook 'minibuffer-exit-hook abort-minibuffer)
+ (funcall delayed)))))
+ (setq abort-minibuffer
+ (lambda () "@transient--delay-post-command"
+ (let ((act (and (or (memq this-command transient--abort-commands)
+ (equal (this-command-keys) ""))
+ (= (minibuffer-depth) depth))))
+ (transient--debug
+ 'abort-minibuffer
+ "mini: %s|%s, act %s" (minibuffer-depth) depth act)
+ (when act
+ (remove-hook 'transient--post-command-hook post-command)
+ (remove-hook 'minibuffer-exit-hook abort-minibuffer)
+ (funcall delayed)))))
+ (add-hook 'transient--post-command-hook post-command)
+ (add-hook 'minibuffer-exit-hook abort-minibuffer)))
(defun transient--post-command ()
(transient--debug 'post-command)
(cond
+ ((and (eq (this-command-keys-vector) [])
+ (= (minibuffer-depth)
+ (1+ transient--minibuffer-depth)))
+ (transient--suspend-override)
+ (transient--delay-post-command))
(transient--exitp
(transient--post-exit))
((eq this-command (oref transient--prefix command)))
@@ -2051,9 +2106,6 @@ value. Otherwise return CHILDREN as is."
;; but decided not to call `transient-setup'.
(prog1 nil (transient--stack-zap))))
(remove-hook 'pre-command-hook #'transient--pre-command)
- (remove-hook 'minibuffer-setup-hook #'transient--minibuffer-setup)
- (remove-hook 'minibuffer-exit-hook #'transient--minibuffer-exit)
- (advice-remove 'abort-recursive-edit #'transient--minibuffer-exit)
(remove-hook 'post-command-hook #'transient--post-command))
(setq transient-current-prefix nil)
(setq transient-current-command nil)
@@ -2063,6 +2115,7 @@ value. Otherwise return CHILDREN as is."
(setq transient--exitp nil)
(setq transient--helpp nil)
(setq transient--editp nil)
+ (setq transient--minibuffer-depth nil)
(run-hooks 'transient-exit-hook)
(when resume
(transient--stack-pop))))
@@ -2523,13 +2576,24 @@ on the previous value.")
(cl-defmethod transient-infix-read :around ((obj transient-infix))
"Highlight the infix in the popup buffer.
-Also arrange for the transient to be exited in case of an error
-because otherwise Emacs would get stuck in an inconsistent state,
-which might make it necessary to kill it from the outside."
+This also wraps the call to `cl-call-next-method' with two
+macros.
+
+`transient--with-suspended-override' is necessary to allow
+reading user input using the minibuffer.
+
+`transient--with-emergency-exit' arranges for the transient to
+be exited in case of an error because otherwise Emacs would get
+stuck in an inconsistent state, which might make it necessary to
+kill it from the outside.
+
+If you replace this method, then you must make sure to always use
+the latter macro and most likely also the former."
(let ((transient--active-infix obj))
(transient--show))
(transient--with-emergency-exit
- (cl-call-next-method obj)))
+ (transient--with-suspended-override
+ (cl-call-next-method obj))))
(cl-defmethod transient-infix-read ((obj transient-infix))
"Read a value while taking care of history.
@@ -3632,9 +3696,9 @@ search instead."
(transient--debug 'edebug--recursive-edit)
(if (not transient--prefix)
(funcall fn arg-mode)
- (transient--suspend-override t)
+ (transient--suspend-override)
(funcall fn arg-mode)
- (transient--resume-override t)))
+ (transient--resume-override)))
(advice-add 'edebug--recursive-edit :around
#'transient--edebug--recursive-edit)
- [elpa] externals/transient 714e348296 18/38: No longer always suspend when handle-switch-frame is called, (continued)
- [elpa] externals/transient 714e348296 18/38: No longer always suspend when handle-switch-frame is called, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient c19ff84355 19/38: manual: Rearrange and group options, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 5762bd9a06 22/38: transient-hide-during-minibuffer-read: New option, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 8d8ed1965f 26/38: Hide infix commands from execute-extended-command, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 6103f168aa 28/38: transient--describe-function: Deal with anonymous infix arguments, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 2c9cef1f5a 33/38: Bump copyright years, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient e77d16dd02 03/38: transient--post-command: Avoid needlessly recreating redisplay map, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 79c999d263 06/38: transient--post-exit: New function, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 2e33f96cf0 07/38: transient--post-command: Cosmetics, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 76b77e01ac 09/38: magit--{pre, post}-command: Add emergency exits, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 7b8a7d718a 08/38: Use a more targeted approach to suspending transient override,
Jonas Bernoulli <=
- [elpa] externals/transient 09b436fad0 10/38: transient--debug: Ignore error in transient--suffix-symbol, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient f2e0dfcc4b 11/38: transient--get-predicate-for: Ignore error in transient--suffix-symbol, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 3c78b10f52 14/38: transient--redisplay: Don't redisplay during mouse-drag-region, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 4a36b1d922 17/38: Interpret t and nil for sub-prefixes in define-transient-prefix, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 1cdadfddf8 21/38: manual: Use source block, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 777a84d26b 20/38: manual: Document all options, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 51585b8dd7 25/38: transient-reset: New command, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 6c9ae1f46a 27/38: manual: Replace some inaccurate information, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient de5a325616 30/38: Re-align debug declarations, Jonas Bernoulli, 2022/01/11
- [elpa] externals/transient 12097b72d7 32/38: manual: Regenerate, Jonas Bernoulli, 2022/01/11