summaryrefslogtreecommitdiff
path: root/lisp/transient.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/transient.el')
-rw-r--r--lisp/transient.el1839
1 files changed, 1087 insertions, 752 deletions
diff --git a/lisp/transient.el b/lisp/transient.el
index 04dc4756825..c3b9448e2c4 100644
--- a/lisp/transient.el
+++ b/lisp/transient.el
@@ -5,9 +5,7 @@
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; URL: https://github.com/magit/transient
;; Keywords: extensions
-
-;; Package-Version: 0.4.3
-;; Package-Requires: ((emacs "26.1"))
+;; Version: 0.6.0
;; SPDX-License-Identifier: GPL-3.0-or-later
@@ -28,27 +26,9 @@
;;; Commentary:
-;; Taking inspiration from prefix keys and prefix arguments, Transient
-;; implements a similar abstraction involving a prefix command, infix
-;; arguments and suffix commands. We could call this abstraction a
-;; "transient command", but because it always involves at least two
-;; commands (a prefix and a suffix) we prefer to call it just a
-;; "transient".
-
-;; When the user calls a transient prefix command, then a transient
-;; (temporary) keymap is activated, which binds the transient's infix
-;; and suffix commands, and functions that control the transient state
-;; are added to `pre-command-hook' and `post-command-hook'. The
-;; available suffix and infix commands and their state are shown in
-;; the echo area until the transient is exited by invoking a suffix
-;; command.
-
-;; Calling an infix command causes its value to be changed, possibly
-;; by reading a new value in the minibuffer.
-
-;; Calling a suffix command usually causes the transient to be exited
-;; but suffix commands can also be configured to not exit the
-;; transient state.
+;; Transient is the library used to implement the keyboard-driven menus
+;; in Magit. It is distributed as a separate package, so that it can be
+;; used to implement similar menus in other packages.
;;; Code:
@@ -56,7 +36,41 @@
(require 'eieio)
(require 'edmacro)
(require 'format-spec)
+
+(eval-and-compile
+ (when (and (featurep' seq)
+ (not (fboundp 'seq-keep)))
+ (unload-feature 'seq 'force)))
(require 'seq)
+(unless (fboundp 'seq-keep)
+ (display-warning 'transient (substitute-command-keys "\
+Transient requires `seq' >= 2.24,
+but due to bad defaults, Emacs' package manager, refuses to
+upgrade this and other built-in packages to higher releases
+from GNU Elpa, when a package specifies that this is needed.
+
+To fix this, you have to add this to your init file:
+
+ (setq package-install-upgrade-built-in t)
+
+Then evaluate that expression by placing the cursor after it
+and typing \\[eval-last-sexp].
+
+Once you have done that, you have to explicitly upgrade `seq':
+
+ \\[package-upgrade] seq \\`RET'
+
+Then you also must make sure the updated version is loaded,
+by evaluating this form:
+
+ (progn (unload-feature 'seq t) (require 'seq))
+
+Until you do this, you will get random errors about `seq-keep'
+being undefined while using Transient.
+
+If you don't use the `package' package manager but still get
+this warning, then your chosen package manager likely has a
+similar defect.") :emergency))
(eval-when-compile (require 'subr-x))
@@ -65,21 +79,34 @@
(declare-function Man-next-section "man" (n))
(declare-function Man-getpage-in-background "man" (topic))
-(defvar display-line-numbers) ; since Emacs 26.1
(defvar Man-notify-method)
(defvar pp-default-function) ; since Emacs 29.1
-(defmacro transient--with-emergency-exit (&rest body)
+(defmacro static-if (condition then-form &rest else-forms)
+ "A conditional compilation macro.
+Evaluate CONDITION at macro-expansion time. If it is non-nil,
+expand the macro to THEN-FORM. Otherwise expand it to ELSE-FORMS
+enclosed in a `progn' form. ELSE-FORMS may be empty."
+ (declare (indent 2)
+ (debug (sexp sexp &rest sexp)))
+ (if (eval condition lexical-binding)
+ then-form
+ (cons 'progn else-forms)))
+
+(defmacro transient--with-emergency-exit (id &rest body)
(declare (indent defun))
+ (unless (keywordp id)
+ (setq body (cons id body))
+ (setq id nil))
`(condition-case err
(let ((debugger #'transient--exit-and-debug))
,(macroexp-progn body))
((debug error)
- (transient--emergency-exit)
+ (transient--emergency-exit ,id)
(signal (car err) (cdr err)))))
(defun transient--exit-and-debug (&rest args)
- (transient--emergency-exit)
+ (transient--emergency-exit :debugger)
(apply #'debug args))
;;; Options
@@ -198,21 +225,30 @@ If nil, then the buffer has no mode-line. If the buffer is not
displayed right above the echo area, then this probably is not
a good value.
-If `line' (the default), then the buffer also has no mode-line,
-but a thin line is drawn instead, using the background color of
-the face `transient-separator'. Termcap frames cannot display
-thin lines and therefore fallback to treating `line' like nil.
+If `line' (the default) or a natural number, then the buffer
+has no mode-line, but a line is drawn is drawn in its place.
+If a number is used, that specifies the thickness of the line.
+On termcap frames we cannot draw lines, so there `line' and
+numbers are synonyms for nil.
+
+The color of the line is used to indicate if non-suffixes are
+allowed and whether they exit the transient. The foreground
+color of `transient-key-noop' (if non-suffix are disallowed),
+`transient-key-stay' (if allowed and transient stays active), or
+`transient-key-exit' (if allowed and they exit the transient) is
+used to draw the line.
Otherwise this can be any mode-line format.
See `mode-line-format' for details."
:package-version '(transient . "0.2.0")
:group 'transient
- :type '(choice (const :tag "hide mode-line" nil)
- (const :tag "substitute thin line" line)
- (const :tag "name of prefix command"
- ("%e" mode-line-front-space
- mode-line-buffer-identification))
- (sexp :tag "custom mode-line format")))
+ :type '(choice (const :tag "hide mode-line" nil)
+ (const :tag "substitute thin line" line)
+ (number :tag "substitute line with thickness")
+ (const :tag "name of prefix command"
+ ("%e" mode-line-front-space
+ mode-line-buffer-identification))
+ (sexp :tag "custom mode-line format")))
(defcustom transient-show-common-commands nil
"Whether to show common transient suffixes in the popup buffer.
@@ -236,7 +272,7 @@ of this variable use \"C-x t\" when a transient is active."
This only affects infix arguments that represent command-line
arguments. When this option is non-nil, then the key binding
for infix argument are highlighted when only a long argument
-\(e.g. \"--verbose\") is specified but no shor-thand (e.g \"-v\").
+\(e.g., \"--verbose\") is specified but no shorthand (e.g., \"-v\").
In the rare case that a short-hand is specified but does not
match the key binding, then it is highlighted differently.
@@ -285,19 +321,14 @@ using a layout optimized for Lisp.
:group 'transient
:type '(choice (const :tag "Transform no keys (nil)" nil) function))
-(defcustom transient-semantic-coloring nil
- "Whether to color prefixes and suffixes in Hydra-like fashion.
-This feature is experimental.
+(defcustom transient-semantic-coloring t
+ "Whether to use colors to indicate transient behavior.
If non-nil, then the key binding of each suffix is colorized to
-indicate whether it exits the transient state or not. The color
-of the prefix is indicated using the line that is drawn when the
-value of `transient-mode-line-format' is `line'.
-
-For more information about how Hydra uses colors see
-https://github.com/abo-abo/hydra#color and
-https://oremacs.com/2015/02/19/hydra-colors-reloaded."
- :package-version '(transient . "0.3.0")
+indicate whether it exits the transient state or not, and the
+line that is drawn below the transient popup buffer is used to
+indicate the behavior of non-suffix commands."
+ :package-version '(transient . "0.5.0")
:group 'transient
:type 'boolean)
@@ -356,8 +387,8 @@ text and might otherwise have to scroll in two dimensions."
:group 'transient
:type 'boolean)
+(defconst transient--max-level 7)
(defconst transient--default-child-level 1)
-
(defconst transient--default-prefix-level 4)
(defcustom transient-default-level transient--default-prefix-level
@@ -436,22 +467,18 @@ give you as many additional suffixes as you hoped.)"
"Face used for headings."
:group 'transient-faces)
-(defface transient-key '((t :inherit font-lock-builtin-face))
- "Face used for keys."
- :group 'transient-faces)
-
-(defface transient-argument '((t :inherit font-lock-warning-face))
+(defface transient-argument '((t :inherit font-lock-string-face :weight bold))
"Face used for enabled arguments."
:group 'transient-faces)
-(defface transient-value '((t :inherit font-lock-string-face))
- "Face used for values."
- :group 'transient-faces)
-
(defface transient-inactive-argument '((t :inherit shadow))
"Face used for inactive arguments."
:group 'transient-faces)
+(defface transient-value '((t :inherit font-lock-string-face :weight bold))
+ "Face used for values."
+ :group 'transient-faces)
+
(defface transient-inactive-value '((t :inherit shadow))
"Face used for inactive values."
:group 'transient-faces)
@@ -460,28 +487,14 @@ give you as many additional suffixes as you hoped.)"
"Face used for suffixes unreachable from the current prefix sequence."
:group 'transient-faces)
-(defface transient-active-infix '((t :inherit secondary-selection))
- "Face used for the infix for which the value is being read."
- :group 'transient-faces)
-
-(defface transient-unreachable-key '((t :inherit (transient-key shadow)))
- "Face used for keys unreachable from the current prefix sequence."
- :group 'transient-faces)
-
-(defface transient-nonstandard-key '((t :underline t))
- "Face optionally used to highlight keys conflicting with short-argument.
-Also see option `transient-highlight-mismatched-keys'."
- :group 'transient-faces)
-
-(defface transient-mismatched-key '((t :underline t))
- "Face optionally used to highlight keys without a short-argument.
-Also see option `transient-highlight-mismatched-keys'."
- :group 'transient-faces)
-
(defface transient-inapt-suffix '((t :inherit shadow :italic t))
"Face used for suffixes that are inapt at this time."
:group 'transient-faces)
+(defface transient-active-infix '((t :inherit highlight))
+ "Face used for the infix for which the value is being read."
+ :group 'transient-faces)
+
(defface transient-enabled-suffix
'((t :background "green" :foreground "black" :weight bold))
"Face used for enabled levels while editing suffix levels.
@@ -494,63 +507,83 @@ See info node `(transient)Enabling and Disabling Suffixes'."
See info node `(transient)Enabling and Disabling Suffixes'."
:group 'transient-faces)
-(defface transient-higher-level '((t :underline t))
+(defface transient-higher-level
+ `((t :box ( :line-width ,(if (>= emacs-major-version 28) (cons -1 -1) -1)
+ :color ,(let ((color (face-attribute 'shadow :foreground nil t)))
+ (or (and (not (eq color 'unspecified)) color)
+ "grey60")))))
"Face optionally used to highlight suffixes on higher levels.
Also see option `transient-highlight-higher-levels'."
:group 'transient-faces)
-(defface transient-separator
- `((((class color) (background light))
- ,@(and (>= emacs-major-version 27) '(:extend t))
- :background "grey80")
- (((class color) (background dark))
- ,@(and (>= emacs-major-version 27) '(:extend t))
- :background "grey30"))
- "Face used to draw line below transient popup window.
-This is only used if `transient-mode-line-format' is `line'.
-Only the background color is significant."
+(defface transient-delimiter '((t :inherit shadow))
+ "Face used for delimiters and separators.
+This includes the parentheses around values and the pipe
+character used to separate possible values from each other."
:group 'transient-faces)
-(defgroup transient-color-faces
- '((transient-semantic-coloring custom-variable))
- "Faces used by Transient for Hydra-like command coloring.
-These faces are only used if `transient-semantic-coloring'
-\(which see) is non-nil."
+(defface transient-key '((t :inherit font-lock-builtin-face))
+ "Face used for keys."
:group 'transient-faces)
-(defface transient-red
- '((t :inherit transient-key :foreground "red"))
- "Face used for red prefixes and suffixes."
- :group 'transient-color-faces)
+(defface transient-key-stay
+ `((((class color) (background light))
+ :inherit transient-key
+ :foreground "#22aa22")
+ (((class color) (background dark))
+ :inherit transient-key
+ :foreground "#ddffdd"))
+ "Face used for keys of suffixes that don't exit transient state."
+ :group 'transient-faces)
-(defface transient-blue
- '((t :inherit transient-key :foreground "blue"))
- "Face used for blue prefixes and suffixes."
- :group 'transient-color-faces)
+(defface transient-key-noop
+ `((((class color) (background light))
+ :inherit transient-key
+ :foreground "grey80")
+ (((class color) (background dark))
+ :inherit transient-key
+ :foreground "grey30"))
+ "Face used for keys of suffixes that currently cannot be invoked."
+ :group 'transient-faces)
-(defface transient-amaranth
- '((t :inherit transient-key :foreground "#E52B50"))
- "Face used for amaranth prefixes."
- :group 'transient-color-faces)
+(defface transient-key-return
+ `((((class color) (background light))
+ :inherit transient-key
+ :foreground "#aaaa11")
+ (((class color) (background dark))
+ :inherit transient-key
+ :foreground "#ffffcc"))
+ "Face used for keys of suffixes that return to the parent transient."
+ :group 'transient-faces)
-(defface transient-pink
- '((t :inherit transient-key :foreground "#FF6EB4"))
- "Face used for pink prefixes."
- :group 'transient-color-faces)
+(defface transient-key-exit
+ `((((class color) (background light))
+ :inherit transient-key
+ :foreground "#aa2222")
+ (((class color) (background dark))
+ :inherit transient-key
+ :foreground "#ffdddd"))
+ "Face used for keys of suffixes that exit transient state."
+ :group 'transient-faces)
-(defface transient-teal
- '((t :inherit transient-key :foreground "#367588"))
- "Face used for teal prefixes."
- :group 'transient-color-faces)
+(defface transient-unreachable-key
+ '((t :inherit (shadow transient-key) :weight normal))
+ "Face used for keys unreachable from the current prefix sequence."
+ :group 'transient-faces)
-(defface transient-purple
- '((t :inherit transient-key :foreground "#a020f0"))
- "Face used for purple prefixes.
+(defface transient-nonstandard-key
+ `((t :box ( :line-width ,(if (>= emacs-major-version 28) (cons -1 -1) -1)
+ :color "cyan")))
+ "Face optionally used to highlight keys conflicting with short-argument.
+Also see option `transient-highlight-mismatched-keys'."
+ :group 'transient-faces)
-This is an addition to the colors supported by Hydra. It is
-used by suffixes that quit the current prefix but return to
-the previous prefix."
- :group 'transient-color-faces)
+(defface transient-mismatched-key
+ `((t :box ( :line-width ,(if (>= emacs-major-version 28) (cons -1 -1) -1)
+ :color "magenta")))
+ "Face optionally used to highlight keys without a short-argument.
+Also see option `transient-highlight-mismatched-keys'."
+ :group 'transient-faces)
;;; Persistence
@@ -633,9 +666,12 @@ If `transient-save-history' is nil, then do nothing."
(man-page :initarg :man-page :initform nil)
(transient-suffix :initarg :transient-suffix :initform nil)
(transient-non-suffix :initarg :transient-non-suffix :initform nil)
+ (transient-switch-frame :initarg :transient-switch-frame)
+ (refresh-suffixes :initarg :refresh-suffixes :initform nil)
(incompatible :initarg :incompatible :initform nil)
(suffix-description :initarg :suffix-description)
(variable-pitch :initarg :variable-pitch :initform nil)
+ (column-widths :initarg :column-widths :initform nil)
(unwind-suffix :documentation "Internal use." :initform nil))
"Transient prefix command.
@@ -693,12 +729,15 @@ slot is non-nil."
:abstract t)
(defclass transient-suffix (transient-child)
- ((key :initarg :key)
+ ((definition :allocation :class :initform nil)
+ (key :initarg :key)
(command :initarg :command)
(transient :initarg :transient)
(format :initarg :format :initform " %k %d")
(description :initarg :description :initform nil)
+ (face :initarg :face :initform nil)
(show-help :initarg :show-help :initform nil)
+ (inapt-face :initarg :inapt-face :initform 'transient-inapt-suffix)
(inapt :initform nil)
(inapt-if
:initarg :inapt-if
@@ -734,6 +773,12 @@ slot is non-nil."
:documentation "Inapt if major-mode does not derive from value."))
"Superclass for suffix command.")
+(defclass transient-information (transient-suffix)
+ ((format :initform " %k %d")
+ (key :initform " "))
+ "Display-only information.
+A suffix object with no associated command.")
+
(defclass transient-infix (transient-suffix)
((transient :initform t)
(argument :initarg :argument)
@@ -788,8 +833,8 @@ They become the value of this argument.")
((suffixes :initarg :suffixes :initform nil)
(hide :initarg :hide :initform nil)
(description :initarg :description :initform nil)
- (setup-children :initarg :setup-children)
- (pad-keys :initarg :pad-keys))
+ (pad-keys :initarg :pad-keys :initform nil)
+ (setup-children :initarg :setup-children))
"Abstract superclass of all group classes."
:abstract t)
@@ -815,7 +860,6 @@ elements themselves.")
;;; Define
-;;;###autoload
(defmacro transient-define-prefix (name arglist &rest args)
"Define NAME as a transient prefix command.
@@ -907,7 +951,10 @@ ARGLIST. The infix arguments are usually accessed by using
(pcase-let ((`(,class ,slots ,_ ,docstr ,body)
(transient--expand-define-args args arglist)))
`(progn
- (defalias ',name (lambda ,arglist ,@body))
+ (defalias ',name
+ ,(if (and (not body) class (oref-default class definition))
+ `(oref-default ',class definition)
+ `(lambda ,arglist ,@body)))
(put ',name 'interactive-only t)
(put ',name 'function-documentation ,docstr)
(put ',name 'transient--suffix
@@ -932,11 +979,11 @@ explicitly.
The function definitions is always:
- (lambda ()
- (interactive)
- (let ((obj (transient-suffix-object)))
- (transient-infix-set obj (transient-infix-read obj)))
- (transient--show))
+ (lambda ()
+ (interactive)
+ (let ((obj (transient-suffix-object)))
+ (transient-infix-set obj (transient-infix-read obj)))
+ (transient--show))
`transient-infix-read' and `transient-infix-set' are generic
functions. Different infix commands behave differently because
@@ -958,7 +1005,7 @@ keyword.
`(progn
(defalias ',name #'transient--default-infix-command)
(put ',name 'interactive-only t)
- (put ',name 'command-modes (list 'not-a-mode))
+ (put ',name 'completion-predicate #'transient--suffix-only)
(put ',name 'function-documentation ,docstr)
(put ',name 'transient--suffix
(,(or class 'transient-switch) :command ',name ,@slots)))))
@@ -973,41 +1020,70 @@ example, sets a variable, use `transient-define-infix' instead.
\(fn NAME ARGLIST [DOCSTRING] [KEYWORD VALUE]...)")
(defun transient--default-infix-command ()
- "Most transient infix commands are but an alias for this command."
+ ;; Most infix commands are but an alias for this command.
+ "Cannot show any documentation for this transient infix command.
+
+When you request help for an infix command using `transient-help', that
+usually shows the respective man-page and tries to jump to the location
+where the respective argument is being described.
+
+If no man-page is specified for the containing transient menu, then the
+docstring is displayed instead, if any.
+
+If the infix command doesn't have a docstring, as is the case here, then
+this docstring is displayed instead, because technically infix commands
+are aliases for `transient--default-infix-command'.
+
+`describe-function' also shows the docstring of the infix command,
+falling back to that of the same aliased command."
(interactive)
(let ((obj (transient-suffix-object)))
(transient-infix-set obj (transient-infix-read obj)))
(transient--show))
(put 'transient--default-infix-command 'interactive-only t)
-(put 'transient--default-infix-command 'command-modes (list 'not-a-mode))
-
-(defun transient--expand-define-args (args &optional arglist)
- (unless (listp arglist)
- (error "Mandatory ARGLIST is missing"))
- (let (class keys suffixes docstr)
- (when (stringp (car args))
- (setq docstr (pop args)))
- (while (keywordp (car args))
- (let ((k (pop args))
- (v (pop args)))
- (if (eq k :class)
- (setq class v)
- (push k keys)
- (push v keys))))
- (while (let ((arg (car args)))
- (or (vectorp arg)
- (and arg (symbolp arg))))
- (push (pop args) suffixes))
- (list (if (eq (car-safe class) 'quote)
- (cadr class)
- class)
- (nreverse keys)
- (nreverse suffixes)
- docstr
- args)))
+(put 'transient--default-infix-command 'completion-predicate
+ #'transient--suffix-only)
+
+(defun transient--find-function-advised-original (fn func)
+ "Return nil instead of `transient--default-infix-command'.
+When using `find-function' to jump to the definition of a transient
+infix command/argument, then we want to actually jump to that, not to
+the definition of `transient--default-infix-command', which all infix
+commands are aliases for."
+ (let ((val (funcall fn func)))
+ (and val (not (eq val 'transient--default-infix-command)) val)))
+(advice-add 'find-function-advised-original :around
+ #'transient--find-function-advised-original)
+
+(eval-and-compile
+ (defun transient--expand-define-args (args &optional arglist)
+ (unless (listp arglist)
+ (error "Mandatory ARGLIST is missing"))
+ (let (class keys suffixes docstr)
+ (when (stringp (car args))
+ (setq docstr (pop args)))
+ (while (keywordp (car args))
+ (let ((k (pop args))
+ (v (pop args)))
+ (if (eq k :class)
+ (setq class v)
+ (push k keys)
+ (push v keys))))
+ (while (let ((arg (car args)))
+ (or (vectorp arg)
+ (and arg (symbolp arg))))
+ (push (pop args) suffixes))
+ (list (if (eq (car-safe class) 'quote)
+ (cadr class)
+ class)
+ (nreverse keys)
+ (nreverse suffixes)
+ docstr
+ args))))
(defun transient--parse-child (prefix spec)
- (cl-etypecase spec
+ (cl-typecase spec
+ (null (error "Invalid transient--parse-child spec: %s" spec))
(symbol (let ((value (symbol-value spec)))
(if (and (listp value)
(or (listp (car value))
@@ -1016,7 +1092,8 @@ example, sets a variable, use `transient-define-infix' instead.
(transient--parse-child prefix value))))
(vector (and-let* ((c (transient--parse-group prefix spec))) (list c)))
(list (and-let* ((c (transient--parse-suffix prefix spec))) (list c)))
- (string (list spec))))
+ (string (list spec))
+ (t (error "Invalid transient--parse-child spec: %s" spec))))
(defun transient--parse-group (prefix spec)
(setq spec (append spec nil))
@@ -1037,12 +1114,16 @@ example, sets a variable, use `transient-define-infix' instead.
(and (listp val) (not (eq (car val) 'lambda))))
(setq args (plist-put args key (macroexp-quote val))))
((setq args (plist-put args key val))))))
+ (unless (or spec class (not (plist-get args :setup-children)))
+ (message "WARNING: %s: When %s is used, %s must also be specified"
+ 'transient-define-prefix :setup-children :class))
(list 'vector
(or level transient--default-child-level)
- (or class
- (if (vectorp car)
- (quote 'transient-columns)
- (quote 'transient-column)))
+ (cond (class)
+ ((or (vectorp car)
+ (and car (symbolp car)))
+ (quote 'transient-columns))
+ ((quote 'transient-column)))
(and args (cons 'list args))
(cons 'list
(cl-mapcan (lambda (s) (transient--parse-child prefix s))
@@ -1069,8 +1150,9 @@ example, sets a variable, use `transient-define-infix' instead.
(commandp (cadr spec)))
(setq args (plist-put args :description (macroexp-quote pop)))))
(cond
+ ((eq car :info))
((keywordp car)
- (error "Need command, got `%s'" car))
+ (error "Need command or `:info', got `%s'" car))
((symbolp car)
(setq args (plist-put args :command (macroexp-quote pop))))
((and (commandp car)
@@ -1080,15 +1162,19 @@ example, sets a variable, use `transient-define-infix' instead.
(format "transient:%s:%s"
prefix
(let ((desc (plist-get args :description)))
- (if (and desc (or (stringp desc) (symbolp desc)))
+ (if (and (stringp desc)
+ (length< desc 16))
desc
(plist-get args :key)))))))
(setq args (plist-put
args :command
`(prog1 ',sym
(put ',sym 'interactive-only t)
- (put ',sym 'command-modes (list 'not-a-mode))
- (defalias ',sym ,(macroexp-quote cmd)))))))
+ (put ',sym 'completion-predicate #'transient--suffix-only)
+ (defalias ',sym
+ ,(if (eq (car-safe cmd) 'lambda)
+ cmd
+ (macroexp-quote cmd))))))))
((or (stringp car)
(and car (listp car)))
(let ((arg pop)
@@ -1107,7 +1193,7 @@ example, sets a variable, use `transient-define-infix' instead.
args :command
`(prog1 ',sym
(put ',sym 'interactive-only t)
- (put ',sym 'command-modes (list 'not-a-mode))
+ (put ',sym 'completion-predicate #'transient--suffix-only)
(defalias ',sym #'transient--default-infix-command))))
(cond ((and car (not (keywordp car)))
(setq class 'transient-option)
@@ -1123,6 +1209,9 @@ example, sets a variable, use `transient-define-infix' instead.
(val pop))
(cond ((eq key :class) (setq class val))
((eq key :level) (setq level val))
+ ((eq key :info)
+ (setq class 'transient-information)
+ (setq args (plist-put args :description val)))
((eq (car-safe val) '\,)
(setq args (plist-put args key (cadr val))))
((or (symbolp val)
@@ -1142,13 +1231,34 @@ example, sets a variable, use `transient-define-infix' instead.
(and (string-match "\\`\\(-[a-zA-Z]\\)\\(\\'\\|=\\)" arg)
(match-string 1 arg))))
+(defun transient-command-completion-not-suffix-only-p (symbol _buffer)
+ "Say whether SYMBOL should be offered as a completion.
+If the value of SYMBOL's `completion-predicate' property is
+`transient--suffix-only', then return nil, otherwise return t.
+This is the case when a command should only ever be used as a
+suffix of a transient prefix command (as opposed to bindings
+in regular keymaps or by using `execute-extended-command')."
+ (not (eq (get symbol 'completion-predicate) 'transient--suffix-only)))
+
+(defalias 'transient--suffix-only #'ignore
+ "Ignore ARGUMENTS, do nothing, and return nil.
+Also see `transient-command-completion-not-suffix-only-p'.
+Only use this alias as the value of the `completion-predicate'
+symbol property.")
+
+(when (and (boundp 'read-extended-command-predicate) ; since Emacs 28.1
+ (not read-extended-command-predicate))
+ (setq read-extended-command-predicate
+ #'transient-command-completion-not-suffix-only-p))
+
(defun transient-parse-suffix (prefix suffix)
"Parse SUFFIX, to be added to PREFIX.
PREFIX is a prefix command, a symbol.
SUFFIX is a suffix command or a group specification (of
the same forms as expected by `transient-define-prefix').
Intended for use in a group's `:setup-children' function."
- (eval (car (transient--parse-child prefix suffix))))
+ (cl-assert (and prefix (symbolp prefix)))
+ (eval (car (transient--parse-child prefix suffix)) t))
(defun transient-parse-suffixes (prefix suffixes)
"Parse SUFFIXES, to be added to PREFIX.
@@ -1156,6 +1266,7 @@ PREFIX is a prefix command, a symbol.
SUFFIXES is a list of suffix command or a group specification
(of the same forms as expected by `transient-define-prefix').
Intended for use in a group's `:setup-children' function."
+ (cl-assert (and prefix (symbolp prefix)))
(mapcar (apply-partially #'transient-parse-suffix prefix) suffixes))
;;; Edit
@@ -1167,7 +1278,7 @@ Intended for use in a group's `:setup-children' function."
(string suffix)))
(mem (transient--layout-member loc prefix))
(elt (car mem)))
- (setq suf (eval suf))
+ (setq suf (eval suf t))
(cond
((not mem)
(message "Cannot insert %S into %s; %s not found"
@@ -1191,11 +1302,11 @@ Intended for use in a group's `:setup-children' function."
(equal (transient--suffix-predicate suf)
(transient--suffix-predicate conflict)))))
(transient-remove-suffix prefix key))
- (cl-ecase action
- (insert (setcdr mem (cons elt (cdr mem)))
- (setcar mem suf))
- (append (setcdr mem (cons suf (cdr mem))))
- (replace (setcar mem suf)))))))
+ (pcase-exhaustive action
+ ('insert (setcdr mem (cons elt (cdr mem)))
+ (setcar mem suf))
+ ('append (setcdr mem (cons suf (cdr mem))))
+ ('replace (setcar mem suf)))))))
;;;###autoload
(defun transient-insert-suffix (prefix loc suffix &optional keep-other)
@@ -1306,7 +1417,7 @@ See info node `(transient)Modifying Existing Transients'."
(delq (car (transient--group-member loc layout))
(aref layout 3)))
nil)
- (t (transient--group-member loc layout))))
+ ((transient--group-member loc layout))))
(defun transient--group-member (loc group)
(cl-member-if (lambda (suffix)
@@ -1335,7 +1446,7 @@ See info node `(transient)Modifying Existing Transients'."
(plist-get plist :command)))))
(defun transient--command-key (cmd)
- (and-let* ((obj (get cmd 'transient--suffix)))
+ (and-let* ((obj (transient--suffix-prototype cmd)))
(cond ((slot-boundp obj 'key)
(oref obj key))
((slot-exists-p obj 'shortarg)
@@ -1376,11 +1487,15 @@ variable instead.")
(defconst transient--exit nil "Do exit the transient.")
(defvar transient--exitp nil "Whether to exit the transient.")
-(defvar transient--showp nil "Whether the transient is show in a popup buffer.")
+(defvar transient--showp nil "Whether to show the transient popup buffer.")
(defvar transient--helpp nil "Whether help-mode is active.")
(defvar transient--editp nil "Whether edit-mode is active.")
-(defvar transient--active-infix nil "The active infix awaiting user input.")
+(defvar transient--refreshp nil
+ "Whether to refresh the transient completely.")
+
+(defvar transient--all-levels-p nil
+ "Whether temporary display of suffixes on all levels is active.")
(defvar transient--timer nil)
@@ -1392,7 +1507,7 @@ variable instead.")
"Name of the transient buffer.")
(defvar transient--window nil
- "The window used to display the transient popup.")
+ "The window used to display the transient popup buffer.")
(defvar transient--original-window nil
"The window that was selected before the transient was invoked.
@@ -1402,7 +1517,25 @@ Usually it remains selected while the transient is active.")
"The buffer that was current before the transient was invoked.
Usually it remains current while the transient is active.")
-(defvar transient--debug nil "Whether put debug information into *Messages*.")
+(defvar transient--restore-winconf nil
+ "Window configuration to restore after exiting help.")
+
+(defvar transient--shadowed-buffer nil
+ "The buffer that is temporarily shadowed by the transient buffer.
+This is bound while the suffix predicate is being evaluated and while
+drawing in the transient buffer.")
+
+(defvar transient--pending-suffix nil
+ "The suffix that is currently being processed.
+This is bound while the suffix predicate is being evaluated,
+and while functions that return faces are being evaluated.")
+
+(defvar transient--pending-group nil
+ "The group that is currently being processed.
+This is bound while the suffixes are drawn in the transient buffer.")
+
+(defvar transient--debug nil
+ "Whether to put debug information into *Messages*.")
(defvar transient--history nil)
@@ -1414,6 +1547,31 @@ Usually it remains current while the transient is active.")
;;; Identities
+(defun transient-prefix-object ()
+ "Return the current prefix as an object.
+
+While a transient is being setup or refreshed (which involves
+preparing its suffixes) the variable `transient--prefix' can be
+used to access the prefix object. Thus this is what has to be
+used in suffix methods such as `transient-format-description',
+and in object-specific functions that are stored in suffix slots
+such as `description'.
+
+When a suffix command is invoked (i.e., in its `interactive' form
+and function body) then the variable `transient-current-prefix'
+has to be used instead.
+
+Two distinct variables are needed, because any prefix may itself
+be used as a suffix of another prefix, and such sub-prefixes have
+to be able to tell themselves apart from the prefix they were
+invoked from.
+
+Regular suffix commands, which are not prefixes, do not have to
+concern themselves with this distinction, so they can use this
+function instead. In the context of a plain suffix, it always
+returns the value of the appropriate variable."
+ (or transient--prefix transient-current-prefix))
+
(defun transient-suffix-object (&optional command)
"Return the object associated with the current suffix command.
@@ -1425,11 +1583,11 @@ This function is intended to be called by infix commands, which
are usually aliases of `transient--default-infix-command', which
is defined like this:
- (defun transient--default-infix-command ()
- (interactive)
- (let ((obj (transient-suffix-object)))
- (transient-infix-set obj (transient-infix-read obj)))
- (transient--show))
+ (defun transient--default-infix-command ()
+ (interactive)
+ (let ((obj (transient-suffix-object)))
+ (transient-infix-set obj (transient-infix-read obj)))
+ (transient--show))
\(User input is read outside of `interactive' to prevent the
command from being added to `command-history'. See #23.)
@@ -1453,33 +1611,40 @@ probably use this instead:
(get COMMAND \\='transient--suffix)"
(when command
(cl-check-type command command))
- (if (or transient--prefix
- transient-current-prefix)
- (let ((suffixes
- (cl-remove-if-not
- (lambda (obj)
- (eq (oref obj command)
- (or command
- (if (eq this-command 'transient-set-level)
- ;; This is how it can look up for which
- ;; command it is setting the level.
- this-original-command
- this-command))))
- (or transient--suffixes
- transient-current-suffixes))))
- (or (and (cdr suffixes)
- (cl-find-if
- (lambda (obj)
- (equal (listify-key-sequence (transient--kbd (oref obj key)))
- (listify-key-sequence (this-command-keys))))
- suffixes))
- (car suffixes)))
- (when-let* ((obj (get (or command this-command) 'transient--suffix))
- (obj (clone obj)))
- ;; Cannot use and-let* because of debbugs#31840.
- (transient-init-scope obj)
- (transient-init-value obj)
- obj)))
+ (cond
+ (transient--pending-suffix)
+ ((or transient--prefix
+ transient-current-prefix)
+ (let ((suffixes
+ (cl-remove-if-not
+ (lambda (obj)
+ (eq (oref obj command)
+ (or command
+ (if (eq this-command 'transient-set-level)
+ ;; This is how it can look up for which
+ ;; command it is setting the level.
+ this-original-command
+ this-command))))
+ (or transient--suffixes
+ transient-current-suffixes))))
+ (or (and (cdr suffixes)
+ (cl-find-if
+ (lambda (obj)
+ (equal (listify-key-sequence (transient--kbd (oref obj key)))
+ (listify-key-sequence (this-command-keys))))
+ suffixes))
+ (car suffixes))))
+ ((and-let* ((obj (transient--suffix-prototype (or command this-command)))
+ (obj (clone obj)))
+ (progn ; work around debbugs#31840
+ (transient-init-scope obj)
+ (transient-init-value obj)
+ obj)))))
+
+(defun transient--suffix-prototype (command)
+ (or (get command 'transient--suffix)
+ (seq-some (lambda (cmd) (get cmd 'transient--suffix))
+ (function-alias-p command))))
;;; Keymaps
@@ -1570,7 +1735,9 @@ to `transient-predicate-map'. Also see `transient-base-map'."
(if transient-show-common-commands
"Hide common commands"
"Show common permanently")))
- (list "C-x l" "Show/hide suffixes" #'transient-set-level))))))))
+ (list "C-x l" "Show/hide suffixes" #'transient-set-level)
+ (list "C-x a" #'transient-toggle-level-limit)))))
+ t)))
(defvar-keymap transient-popup-navigation-map
:doc "One of the keymaps used when popup navigation is enabled.
@@ -1588,6 +1755,16 @@ See `transient-enable-popup-navigation'."
"<mouse-1>" #'transient-push-button
"<mouse-2>" #'transient-push-button)
+(defvar-keymap transient-resume-mode-map
+ :doc "Keymap for `transient-resume-mode'.
+
+This keymap remaps every command that would usually just quit the
+documentation buffer to `transient-resume', which additionally
+resumes the suspended transient."
+ "<remap> <Man-quit>" #'transient-resume
+ "<remap> <Info-exit>" #'transient-resume
+ "<remap> <quit-window>" #'transient-resume)
+
(defvar-keymap transient-predicate-map
:doc "Base keymap used to map common commands to their transient behavior.
@@ -1623,7 +1800,9 @@ of the corresponding object."
"<transient-update>" #'transient--do-stay
"<transient-toggle-common>" #'transient--do-stay
"<transient-set>" #'transient--do-call
+ "<transient-set-and-exit>" #'transient--do-exit
"<transient-save>" #'transient--do-call
+ "<transient-save-and-exit>" #'transient--do-exit
"<transient-reset>" #'transient--do-call
"<describe-key-briefly>" #'transient--do-stay
"<describe-key>" #'transient--do-stay
@@ -1642,7 +1821,10 @@ of the corresponding object."
;; an unbound key, then Emacs calls the `undefined' command
;; but does not set `this-command', `this-original-command'
;; or `real-this-command' accordingly. Instead they are nil.
- "<nil>" #'transient--do-warn)
+ "<nil>" #'transient--do-warn
+ ;; Bound to the `mouse-movement' event, this command is similar
+ ;; to `ignore'.
+ "<ignore-preserving-kill-region>" #'transient--do-noop)
(defvar transient--transient-map nil)
(defvar transient--predicate-map nil)
@@ -1699,50 +1881,66 @@ of the corresponding object."
map))
(defun transient--make-predicate-map ()
- (let ((map (make-sparse-keymap)))
+ (let* ((default (transient--resolve-pre-command
+ (oref transient--prefix transient-suffix)))
+ (return (and transient--stack (eq default t)))
+ (map (make-sparse-keymap)))
(set-keymap-parent map transient-predicate-map)
- (when (memq (oref transient--prefix transient-non-suffix)
- '(nil transient--do-warn transient--do-noop))
- (keymap-set map "<handle-switch-frame>" #'transient--do-suspend))
+ (when (or (and (slot-boundp transient--prefix 'transient-switch-frame)
+ (transient--resolve-pre-command
+ (not (oref transient--prefix transient-switch-frame))))
+ (memq (transient--resolve-pre-command
+ (oref transient--prefix transient-non-suffix))
+ '(nil transient--do-warn transient--do-noop)))
+ (define-key map [handle-switch-frame] #'transient--do-suspend))
(dolist (obj transient--suffixes)
(let* ((cmd (oref obj command))
- (sub-prefix (and (symbolp cmd) (get cmd 'transient--prefix) t)))
+ (kind (cond ((get cmd 'transient--prefix) 'prefix)
+ ((cl-typep obj 'transient-infix) 'infix)
+ (t 'suffix))))
(cond
((oref obj inapt)
(define-key map (vector cmd) #'transient--do-warn-inapt))
((slot-boundp obj 'transient)
(define-key map (vector cmd)
- (let ((do (oref obj transient)))
- (pcase (list do sub-prefix)
- ('(t t) #'transient--do-recurse)
- ('(t nil) (if (cl-typep obj 'transient-infix)
- #'transient--do-stay
- #'transient--do-call))
- ('(nil t) #'transient--do-replace)
- ('(nil nil) #'transient--do-exit)
- (_ do)))))
+ (pcase (list kind
+ (transient--resolve-pre-command (oref obj transient))
+ return)
+ (`(prefix t ,_) #'transient--do-recurse)
+ (`(prefix nil ,_) #'transient--do-stack)
+ (`(infix t ,_) #'transient--do-stay)
+ (`(suffix t ,_) #'transient--do-call)
+ ('(suffix nil t) #'transient--do-return)
+ (`(,_ nil ,_) #'transient--do-exit)
+ (`(,_ ,do ,_) do))))
((not (lookup-key transient-predicate-map (vector cmd)))
(define-key map (vector cmd)
- (if sub-prefix
- #'transient--do-replace
- (or (oref transient--prefix transient-suffix)
- #'transient--do-exit)))))))
+ (pcase (list kind default return)
+ (`(prefix ,(or 'transient--do-stay 'transient--do-call) ,_)
+ #'transient--do-recurse)
+ (`(prefix t ,_) #'transient--do-recurse)
+ (`(prefix ,_ ,_) #'transient--do-stack)
+ (`(infix ,_ ,_) #'transient--do-stay)
+ (`(suffix t ,_) #'transient--do-call)
+ ('(suffix nil t) #'transient--do-return)
+ (`(suffix nil ,_) #'transient--do-exit)
+ (`(suffix ,do ,_) do)))))))
map))
(defun transient--make-redisplay-map ()
(setq transient--redisplay-key
- (cl-case this-command
- (transient-update
+ (pcase this-command
+ ('transient-update
(setq transient--showp t)
(setq unread-command-events
(listify-key-sequence (this-single-command-raw-keys))))
- (transient-quit-seq
+ ('transient-quit-seq
(setq unread-command-events
(butlast (listify-key-sequence
(this-single-command-raw-keys))
2))
(butlast transient--redisplay-key))
- (t nil)))
+ (_ nil)))
(let ((topmap (make-sparse-keymap))
(submap (make-sparse-keymap)))
(when transient--redisplay-key
@@ -1776,7 +1974,7 @@ the \"scope\" of the transient (see `transient-define-prefix').
This function is also called internally in which case LAYOUT and
EDIT may be non-nil."
(transient--debug 'setup)
- (transient--with-emergency-exit
+ (transient--with-emergency-exit :setup
(cond
((not name)
;; Switching between regular and edit mode.
@@ -1786,7 +1984,7 @@ EDIT may be non-nil."
(setq params (list :scope (oref transient--prefix scope))))
(transient--prefix
;; Invoked as a ":transient-non-suffix 'transient--do-{stay,call}"
- ;; of an outer prefix. Unlike the usual `transient--do-replace',
+ ;; of an outer prefix. Unlike the usual `transient--do-stack',
;; these predicates fail to clean up after the outer prefix.
(transient--pop-keymap 'transient--transient-map)
(transient--pop-keymap 'transient--redisplay-map))
@@ -1797,10 +1995,8 @@ EDIT may be non-nil."
;; Returning from help to edit.
(setq transient--editp t)))
(transient--init-objects name layout params)
+ (transient--init-keymaps)
(transient--history-init transient--prefix)
- (setq transient--predicate-map (transient--make-predicate-map))
- (setq transient--transient-map (transient--make-transient-map))
- (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))
@@ -1817,8 +2013,16 @@ value. Otherwise return CHILDREN as is."
(funcall (oref group setup-children) children)
children))
-(defun transient--init-objects (name layout params)
- (setq transient--prefix (transient--init-prefix name params))
+(defun transient--init-keymaps ()
+ (setq transient--predicate-map (transient--make-predicate-map))
+ (setq transient--transient-map (transient--make-transient-map))
+ (setq transient--redisplay-map (transient--make-redisplay-map)))
+
+(defun transient--init-objects (&optional name layout params)
+ (if name
+ (setq transient--prefix (transient--init-prefix name params))
+ (setq name (oref transient--prefix command)))
+ (setq transient--refreshp (oref transient--prefix refresh-suffixes))
(setq transient--layout (or layout (transient--init-suffixes name)))
(setq transient--suffixes (transient--flatten-suffixes transient--layout)))
@@ -1845,10 +2049,11 @@ value. Otherwise return CHILDREN as is."
(cl-labels ((s (def)
(cond
((stringp def) nil)
+ ((cl-typep def 'transient-information) nil)
((listp def) (cl-mapcan #'s def))
- ((transient-group--eieio-childp def)
+ ((cl-typep def 'transient-group)
(cl-mapcan #'s (oref def suffixes)))
- ((transient-suffix--eieio-childp def)
+ ((cl-typep def 'transient-suffix)
(list def)))))
(cl-mapcan #'s layout)))
@@ -1860,31 +2065,37 @@ value. Otherwise return CHILDREN as is."
(defun transient--init-group (levels spec)
(pcase-let ((`(,level ,class ,args ,children) (append spec nil)))
- (when-let* ((- (transient--use-level-p level))
- (obj (apply class :level level args))
- (- (transient--use-suffix-p obj))
- (suffixes (cl-mapcan (lambda (c) (transient--init-child levels c))
- (transient-setup-children obj children))))
- ;; Cannot use and-let* because of debbugs#31840.
- (oset obj suffixes suffixes)
- (list obj))))
+ (and-let* ((- (transient--use-level-p level))
+ (obj (apply class :level level args))
+ (- (transient--use-suffix-p obj))
+ (suffixes (cl-mapcan (lambda (c) (transient--init-child levels c))
+ (transient-setup-children obj children))))
+ (progn ; work around debbugs#31840
+ (oset obj suffixes suffixes)
+ (list obj)))))
(defun transient--init-suffix (levels spec)
(pcase-let* ((`(,level ,class ,args) spec)
(cmd (plist-get args :command))
- (level (or (alist-get cmd levels) level)))
+ (key (transient--kbd (plist-get args :key)))
+ (level (or (alist-get (cons cmd key) levels nil nil #'equal)
+ (alist-get cmd levels)
+ level)))
(let ((fn (and (symbolp cmd)
(symbol-function cmd))))
(when (autoloadp fn)
(transient--debug " autoload %s" cmd)
(autoload-do-load fn)))
(when (transient--use-level-p level)
- (unless (and cmd (symbolp cmd))
- (error "BUG: Non-symbolic suffix command: %s" cmd))
- (let ((obj (if-let ((proto (get cmd 'transient--suffix)))
- (apply #'clone proto :level level args)
- (apply class :command cmd :level level args))))
- (cond ((commandp cmd))
+ (let ((obj (if (child-of-class-p class 'transient-information)
+ (apply class :level level args)
+ (unless (and cmd (symbolp cmd))
+ (error "BUG: Non-symbolic suffix command: %s" cmd))
+ (if-let ((proto (and cmd (transient--suffix-prototype cmd))))
+ (apply #'clone proto :level level args)
+ (apply class :command cmd :level level args)))))
+ (cond ((not cmd))
+ ((commandp cmd))
((or (cl-typep obj 'transient-switch)
(cl-typep obj 'transient-option))
;; As a temporary special case, if the package was compiled
@@ -1893,7 +2104,8 @@ value. Otherwise return CHILDREN as is."
(defalias cmd #'transient--default-infix-command))
((transient--use-suffix-p obj)
(error "Suffix command %s is not defined or autoloaded" cmd)))
- (transient--init-suffix-key obj)
+ (unless (cl-typep obj 'transient-information)
+ (transient--init-suffix-key obj))
(when (transient--use-suffix-p obj)
(if (transient--inapt-suffix-p obj)
(oset obj inapt t)
@@ -1917,33 +2129,38 @@ value. Otherwise return CHILDREN as is."
(error "No key for %s" (oref obj command))))))
(defun transient--use-level-p (level &optional edit)
- (or (and transient--editp (not edit))
+ (or transient--all-levels-p
+ (and transient--editp (not edit))
(and (>= level 1)
(<= level (oref transient--prefix level)))))
(defun transient--use-suffix-p (obj)
- (transient--do-suffix-p
- (oref obj if)
- (oref obj if-not)
- (oref obj if-nil)
- (oref obj if-non-nil)
- (oref obj if-mode)
- (oref obj if-not-mode)
- (oref obj if-derived)
- (oref obj if-not-derived)
- t))
+ (let ((transient--shadowed-buffer (current-buffer))
+ (transient--pending-suffix obj))
+ (transient--do-suffix-p
+ (oref obj if)
+ (oref obj if-not)
+ (oref obj if-nil)
+ (oref obj if-non-nil)
+ (oref obj if-mode)
+ (oref obj if-not-mode)
+ (oref obj if-derived)
+ (oref obj if-not-derived)
+ t)))
(defun transient--inapt-suffix-p (obj)
- (transient--do-suffix-p
- (oref obj inapt-if)
- (oref obj inapt-if-not)
- (oref obj inapt-if-nil)
- (oref obj inapt-if-non-nil)
- (oref obj inapt-if-mode)
- (oref obj inapt-if-not-mode)
- (oref obj inapt-if-derived)
- (oref obj inapt-if-not-derived)
- nil))
+ (let ((transient--shadowed-buffer (current-buffer))
+ (transient--pending-suffix obj))
+ (transient--do-suffix-p
+ (oref obj inapt-if)
+ (oref obj inapt-if-not)
+ (oref obj inapt-if-nil)
+ (oref obj inapt-if-non-nil)
+ (oref obj inapt-if-mode)
+ (oref obj inapt-if-not-mode)
+ (oref obj inapt-if-derived)
+ (oref obj inapt-if-not-derived)
+ nil)))
(defun transient--do-suffix-p
(if if-not if-nil if-non-nil if-mode if-not-mode if-derived if-not-derived
@@ -1959,13 +2176,15 @@ value. Otherwise return CHILDREN as is."
(if-not-mode (not (if (atom if-not-mode)
(eq major-mode if-not-mode)
(memq major-mode if-not-mode))))
- (if-derived (if (atom if-derived)
+ (if-derived (if (or (atom if-derived)
+ (>= emacs-major-version 30))
(derived-mode-p if-derived)
(apply #'derived-mode-p if-derived)))
- (if-not-derived (not (if (atom if-not-derived)
+ (if-not-derived (not (if (or (atom if-not-derived)
+ (>= emacs-major-version 30))
(derived-mode-p if-not-derived)
(apply #'derived-mode-p if-not-derived))))
- (t default)))
+ (default)))
(defun transient--suffix-predicate (spec)
(let ((plist (nth 2 spec)))
@@ -1996,16 +2215,27 @@ value. Otherwise return CHILDREN as is."
;; that we just added.
(setq transient--exitp 'replace)))
+(defun transient--refresh-transient ()
+ (transient--debug 'refresh-transient)
+ (transient--pop-keymap 'transient--predicate-map)
+ (transient--pop-keymap 'transient--transient-map)
+ (transient--pop-keymap 'transient--redisplay-map)
+ (transient--init-objects)
+ (transient--init-keymaps)
+ (transient--push-keymap 'transient--transient-map)
+ (transient--push-keymap 'transient--redisplay-map)
+ (transient--redisplay))
+
(defun transient--pre-command ()
(transient--debug 'pre-command)
- (transient--with-emergency-exit
+ (transient--with-emergency-exit :pre-command
;; The use of `overriding-terminal-local-map' does not prevent the
;; lookup of command remappings in the overridden maps, which can
;; lead to a suffix being remapped to a non-suffix. We have to undo
;; the remapping in that case. However, remapping a non-suffix to
;; another should remain possible.
- (when (and (transient--get-predicate-for this-original-command 'suffix)
- (not (transient--get-predicate-for this-command 'suffix)))
+ (when (and (transient--get-pre-command this-original-command 'suffix)
+ (not (transient--get-pre-command this-command 'suffix)))
(setq this-command this-original-command))
(cond
((memq this-command '(transient-update transient-quit-seq))
@@ -2029,34 +2259,11 @@ value. Otherwise return CHILDREN as is."
(transient--wrap-command))
(t
(setq transient--exitp nil)
- (let ((exitp (eq (transient--do-pre-command) transient--exit)))
+ (let ((exitp (eq (transient--call-pre-command) transient--exit)))
(transient--wrap-command)
(when exitp
(transient--pre-exit)))))))
-(defun transient--do-pre-command ()
- (if-let ((fn (transient--get-predicate-for this-command)))
- (let ((action (funcall fn)))
- (when (eq action transient--exit)
- (setq transient--exitp (or transient--exitp t)))
- action)
- (if (let ((keys (this-command-keys-vector)))
- (eq (aref keys (1- (length keys))) ?\C-g))
- (setq this-command 'transient-noop)
- (unless (transient--edebug-command-p)
- (setq this-command 'transient-undefined)))
- transient--stay))
-
-(defun transient--get-predicate-for (cmd &optional suffix-only)
- (or (ignore-errors
- (lookup-key transient--predicate-map (vector cmd)))
- (and (not suffix-only)
- (let ((pred (oref transient--prefix transient-non-suffix)))
- (pcase pred
- ('t #'transient--do-stay)
- ('nil #'transient--do-warn)
- (_ pred))))))
-
(defun transient--pre-exit ()
(transient--debug 'pre-exit)
(transient--delete-window)
@@ -2083,13 +2290,14 @@ value. Otherwise return CHILDREN as is."
(when (window-live-p 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)
+ (selected-window))))
+ ;; Only delete the window if it has never shown 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)
+ (when-let ((buffer (get-buffer transient--buffer-name)))
+ (kill-buffer buffer))
(when remain-in-minibuffer-window
(select-window remain-in-minibuffer-window)))))
@@ -2107,7 +2315,10 @@ value. Otherwise return CHILDREN as is."
((and transient--prefix transient--redisplay-key)
(setq transient--redisplay-key nil)
(when transient--showp
- (transient--show))))
+ (if-let ((win (minibuffer-selected-window)))
+ (with-selected-window win
+ (transient--show))
+ (transient--show)))))
(transient--pop-keymap 'transient--transient-map)
(transient--pop-keymap 'transient--redisplay-map)
(remove-hook 'pre-command-hook #'transient--pre-command)
@@ -2162,66 +2373,72 @@ value. Otherwise return CHILDREN as is."
(remove-hook 'minibuffer-exit-hook ,exit)))
,@body)))
-(defun transient--wrap-command ()
- (if (>= emacs-major-version 30)
- (transient--wrap-command-30)
- (transient--wrap-command-29)))
-
-(defun transient--wrap-command-30 ()
- (letrec
- ((prefix transient--prefix)
- (suffix this-command)
- (advice (lambda (fn &rest args)
- (interactive
- (lambda (spec)
- (let ((abort t))
- (unwind-protect
- (prog1 (advice-eval-interactive-spec spec)
- (setq abort nil))
- (when abort
- (when-let ((unwind (oref prefix unwind-suffix)))
- (transient--debug 'unwind-interactive)
- (funcall unwind suffix))
- (advice-remove suffix advice)
- (oset prefix unwind-suffix nil))))))
- (unwind-protect
- (apply fn args)
- (when-let ((unwind (oref prefix unwind-suffix)))
- (transient--debug 'unwind-command)
- (funcall unwind suffix))
- (advice-remove suffix advice)
- (oset prefix unwind-suffix nil)))))
- (advice-add suffix :around advice '((depth . -99)))))
-
-(defun transient--wrap-command-29 ()
- (let* ((prefix transient--prefix)
- (suffix this-command)
- (advice nil)
- (advice-interactive
- (lambda (spec)
- (let ((abort t))
+(static-if (>= emacs-major-version 30) ;transient--wrap-command
+ (defun transient--wrap-command ()
+ (cl-assert
+ (>= emacs-major-version 30) nil
+ "Emacs was downgraded, making it necessary to recompile Transient")
+ (letrec
+ ((prefix transient--prefix)
+ (suffix this-command)
+ (advice
+ (lambda (fn &rest args)
+ (interactive
+ (lambda (spec)
+ (let ((abort t))
+ (unwind-protect
+ (prog1 (let ((debugger #'transient--exit-and-debug))
+ (advice-eval-interactive-spec spec))
+ (setq abort nil))
+ (when abort
+ (when-let ((unwind (oref prefix unwind-suffix)))
+ (transient--debug 'unwind-interactive)
+ (funcall unwind suffix))
+ (advice-remove suffix advice)
+ (oset prefix unwind-suffix nil))))))
+ (unwind-protect
+ (let ((debugger #'transient--exit-and-debug))
+ (apply fn args))
+ (when-let ((unwind (oref prefix unwind-suffix)))
+ (transient--debug 'unwind-command)
+ (funcall unwind suffix))
+ (advice-remove suffix advice)
+ (oset prefix unwind-suffix nil)))))
+ (when (symbolp this-command)
+ (advice-add suffix :around advice '((depth . -99))))))
+
+ (defun transient--wrap-command ()
+ (let* ((prefix transient--prefix)
+ (suffix this-command)
+ (advice nil)
+ (advice-interactive
+ (lambda (spec)
+ (let ((abort t))
+ (unwind-protect
+ (prog1 (let ((debugger #'transient--exit-and-debug))
+ (advice-eval-interactive-spec spec))
+ (setq abort nil))
+ (when abort
+ (when-let ((unwind (oref prefix unwind-suffix)))
+ (transient--debug 'unwind-interactive)
+ (funcall unwind suffix))
+ (advice-remove suffix advice)
+ (oset prefix unwind-suffix nil))))))
+ (advice-body
+ (lambda (fn &rest args)
(unwind-protect
- (prog1 (advice-eval-interactive-spec spec)
- (setq abort nil))
- (when abort
- (when-let ((unwind (oref prefix unwind-suffix)))
- (transient--debug 'unwind-interactive)
- (funcall unwind suffix))
- (advice-remove suffix advice)
- (oset prefix unwind-suffix nil))))))
- (advice-body
- (lambda (fn &rest args)
- (unwind-protect
- (apply fn args)
- (when-let ((unwind (oref prefix unwind-suffix)))
- (transient--debug 'unwind-command)
- (funcall unwind suffix))
- (advice-remove suffix advice)
- (oset prefix unwind-suffix nil)))))
- (setq advice `(lambda (fn &rest args)
- (interactive ,advice-interactive)
- (apply ',advice-body fn args)))
- (advice-add suffix :around advice '((depth . -99)))))
+ (let ((debugger #'transient--exit-and-debug))
+ (apply fn args))
+ (when-let ((unwind (oref prefix unwind-suffix)))
+ (transient--debug 'unwind-command)
+ (funcall unwind suffix))
+ (advice-remove suffix advice)
+ (oset prefix unwind-suffix nil)))))
+ (setq advice `(lambda (fn &rest args)
+ (interactive ,advice-interactive)
+ (apply ',advice-body fn args)))
+ (when (symbolp this-command)
+ (advice-add suffix :around advice '((depth . -99)))))))
(defun transient--premature-post-command ()
(and (equal (this-command-keys-vector) [])
@@ -2240,9 +2457,23 @@ value. Otherwise return CHILDREN as is."
(defun transient--post-command ()
(unless (transient--premature-post-command)
(transient--debug 'post-command)
- (transient--with-emergency-exit
+ (transient--with-emergency-exit :post-command
(cond (transient--exitp (transient--post-exit))
- ((eq this-command (oref transient--prefix command)))
+ ;; If `this-command' is the current transient prefix, then we
+ ;; have already taken care of updating the transient buffer...
+ ((and (eq this-command (oref transient--prefix command))
+ ;; ... but if `prefix-arg' is non-nil, then the values
+ ;; of `this-command' and `real-this-command' are untrue
+ ;; because `prefix-command-preserve-state' changes them.
+ ;; We cannot use `current-prefix-arg' because it is set
+ ;; too late (in `command-execute'), and if it were set
+ ;; earlier, then we likely still would not be able to
+ ;; rely on it and `prefix-command-preserve-state-hook'
+ ;; would have to be used to record that a universal
+ ;; argument is in effect.
+ (not prefix-arg)))
+ (transient--refreshp
+ (transient--refresh-transient))
((let ((old transient--redisplay-map)
(new (transient--make-redisplay-map)))
(unless (equal old new)
@@ -2282,6 +2513,7 @@ value. Otherwise return CHILDREN as is."
(setq transient--exitp nil)
(setq transient--helpp nil)
(setq transient--editp nil)
+ (setq transient--all-levels-p nil)
(setq transient--minibuffer-depth 0)
(run-hooks 'transient-exit-hook)
(when resume
@@ -2292,6 +2524,7 @@ value. Otherwise return CHILDREN as is."
(push (list (oref transient--prefix command)
transient--layout
transient--editp
+ :transient-suffix (oref transient--prefix transient-suffix)
:scope (oref transient--prefix scope))
transient--stack))
@@ -2342,24 +2575,29 @@ value. Otherwise return CHILDREN as is."
(if (symbolp arg)
(message "-- %-22s (cmd: %s, event: %S, exit: %s%s)"
arg
- (or (and (symbolp this-command) this-command)
- (if (byte-code-function-p this-command)
- "#[...]"
- this-command))
+ (if (fboundp 'help-fns-function-name)
+ (help-fns-function-name this-command)
+ (if (byte-code-function-p this-command)
+ "#[...]"
+ this-command))
(key-description (this-command-keys-vector))
transient--exitp
- (cond ((stringp (car args))
+ (cond ((keywordp (car args))
+ (format ", from: %s"
+ (substring (symbol-name (car args)) 1)))
+ ((stringp (car args))
(concat ", " (apply #'format args)))
- (args
+ ((functionp (car args))
(concat ", " (apply (car args) (cdr args))))
- (t "")))
+ ("")))
(apply #'message arg args)))))
-(defun transient--emergency-exit ()
+(defun transient--emergency-exit (&optional id)
"Exit the current transient command after an error occurred.
-When no transient is active (i.e. when `transient--prefix' is
-nil) then do nothing."
- (transient--debug 'emergency-exit)
+When no transient is active (i.e., when `transient--prefix' is
+nil) then do nothing. Optional ID is a keyword identifying the
+exit."
+ (transient--debug 'emergency-exit id)
(when transient--prefix
(setq transient--stack nil)
(setq transient--exitp t)
@@ -2368,6 +2606,37 @@ nil) then do nothing."
;;; Pre-Commands
+(defun transient--call-pre-command ()
+ (if-let ((fn (transient--get-pre-command this-command)))
+ (let ((action (funcall fn)))
+ (when (eq action transient--exit)
+ (setq transient--exitp (or transient--exitp t)))
+ action)
+ (if (let ((keys (this-command-keys-vector)))
+ (eq (aref keys (1- (length keys))) ?\C-g))
+ (setq this-command 'transient-noop)
+ (unless (transient--edebug-command-p)
+ (setq this-command 'transient-undefined)))
+ transient--stay))
+
+(defun transient--get-pre-command (&optional cmd enforce-type)
+ (or (and (not (eq enforce-type 'non-suffix))
+ (symbolp cmd)
+ (lookup-key transient--predicate-map (vector cmd)))
+ (and (not (eq enforce-type 'suffix))
+ (transient--resolve-pre-command
+ (oref transient--prefix transient-non-suffix)
+ t))))
+
+(defun transient--resolve-pre-command (pre &optional resolve-boolean)
+ (cond ((booleanp pre)
+ (if resolve-boolean
+ (if pre #'transient--do-stay #'transient--do-warn)
+ pre))
+ ((string-match-p "--do-" (symbol-name pre)) pre)
+ ((let ((sym (intern (format "transient--do-%s" pre))))
+ (if (functionp sym) sym pre)))))
+
(defun transient--do-stay ()
"Call the command without exporting variables and stay transient."
transient--stay)
@@ -2408,7 +2677,8 @@ If there is no parent prefix, then behave like `transient--do-exit'."
(defun transient--do-leave ()
"Call the command without exporting variables and exit the transient."
- transient--stay)
+ (transient--stack-zap)
+ transient--exit)
(defun transient--do-push-button ()
"Call the command represented by the activated button.
@@ -2423,26 +2693,35 @@ Use that command's pre-command to determine transient behavior."
(posn-point (event-start last-command-event))
(point))
'command)))
- (transient--do-pre-command)))
+ (transient--call-pre-command)))
(defun transient--do-recurse ()
"Call the transient prefix command, preparing for return to active transient.
If there is no parent prefix, then just call the command."
- (transient--do-replace))
+ (transient--do-stack))
(defun transient--setup-recursion (prefix-obj)
(when transient--stack
(let ((command (oref prefix-obj command)))
(when-let ((suffix-obj (transient-suffix-object command)))
- (when (and (slot-boundp suffix-obj 'transient)
- (memq (oref suffix-obj transient)
- (list t #'transient--do-recurse)))
- (oset prefix-obj transient-suffix 'transient--do-return))))))
+ (when (memq (if (slot-boundp suffix-obj 'transient)
+ (oref suffix-obj transient)
+ (oref transient-current-prefix transient-suffix))
+ (list t #'transient--do-recurse))
+ (oset prefix-obj transient-suffix t))))))
+
+(defun transient--do-stack ()
+ "Call the transient prefix command, stacking the active transient.
+Push the active transient to the transient stack."
+ (transient--export)
+ (transient--stack-push)
+ (setq transient--exitp 'replace)
+ transient--exit)
(defun transient--do-replace ()
- "Call the transient prefix command, replacing the active transient."
+ "Call the transient prefix command, replacing the active transient.
+Do not push the active transient to the transient stack."
(transient--export)
- (transient--stack-push)
(setq transient--exitp 'replace)
transient--exit)
@@ -2461,7 +2740,9 @@ If there is no parent prefix, then just call the command."
(setq transient--editp nil)
(transient-setup)
transient--stay)
- (t transient--exit)))
+ (prefix-arg
+ transient--stay)
+ (transient--exit)))
(defun transient--do-quit-all ()
"Exit all transients without saving the transient stack."
@@ -2473,7 +2754,7 @@ If there is no parent prefix, then just call the command."
In that case behave like `transient--do-stay', otherwise similar
to `transient--do-warn'."
(unless transient-enable-popup-navigation
- (setq this-command 'transient-popup-navigation-help))
+ (setq this-command 'transient-inhibit-move))
transient--stay)
(defun transient--do-minus ()
@@ -2484,22 +2765,27 @@ prefix argument and pivot to `transient-update'."
(setq this-command 'transient-update))
transient--stay)
-(put 'transient--do-stay 'transient-color 'transient-red)
-(put 'transient--do-noop 'transient-color 'transient-red)
-(put 'transient--do-warn 'transient-color 'transient-red)
-(put 'transient--do-warn-inapt 'transient-color 'transient-red)
-(put 'transient--do-call 'transient-color 'transient-red)
-(put 'transient--do-return 'transient-color 'transient-purple)
-(put 'transient--do-exit 'transient-color 'transient-blue)
-(put 'transient--do-recurse 'transient-color 'transient-red)
-(put 'transient--do-replace 'transient-color 'transient-blue)
-(put 'transient--do-suspend 'transient-color 'transient-blue)
-(put 'transient--do-quit-one 'transient-color 'transient-blue)
-(put 'transient--do-quit-all 'transient-color 'transient-blue)
-(put 'transient--do-move 'transient-color 'transient-red)
-(put 'transient--do-minus 'transient-color 'transient-red)
+(put 'transient--do-stay 'transient-face 'transient-key-stay)
+(put 'transient--do-noop 'transient-face 'transient-key-noop)
+(put 'transient--do-warn 'transient-face 'transient-key-noop)
+(put 'transient--do-warn-inapt 'transient-face 'transient-key-noop)
+(put 'transient--do-call 'transient-face 'transient-key-stay)
+(put 'transient--do-return 'transient-face 'transient-key-return)
+(put 'transient--do-exit 'transient-face 'transient-key-exit)
+(put 'transient--do-leave 'transient-face 'transient-key-exit)
+
+(put 'transient--do-recurse 'transient-face 'transient-key-stay)
+(put 'transient--do-stack 'transient-face 'transient-key-stay)
+(put 'transient--do-replace 'transient-face 'transient-key-exit)
+(put 'transient--do-suspend 'transient-face 'transient-key-exit)
+
+(put 'transient--do-quit-one 'transient-face 'transient-key-return)
+(put 'transient--do-quit-all 'transient-face 'transient-key-exit)
+(put 'transient--do-move 'transient-face 'transient-key-stay)
+(put 'transient--do-minus 'transient-face 'transient-key-stay)
;;; Commands
+;;;; Noop
(defun transient-noop ()
"Do nothing at all."
@@ -2538,27 +2824,23 @@ prefix argument and pivot to `transient-update'."
(other-window 1)
(display-warning 'transient "Inconsistent transient state detected.
This should never happen.
-Please open an issue and post the shown command log.
-This is a heisenbug, so any additional details might help.
-Thanks!" :error)))
+Please open an issue and post the shown command log." :error)))
-(defun transient-toggle-common ()
- "Toggle whether common commands are always shown."
+(defun transient-inhibit-move ()
+ "Warn the user that popup navigation is disabled."
(interactive)
- (setq transient-show-common-commands (not transient-show-common-commands)))
+ (message "To enable use of `%s', please customize `%s'"
+ this-original-command
+ 'transient-enable-popup-navigation))
-(defun transient-suspend ()
- "Suspend the current transient.
-It can later be resumed using `transient-resume' while no other
-transient is active."
- (interactive))
+;;;; Core
(defun transient-quit-all ()
"Exit all transients without saving the transient stack."
(interactive))
(defun transient-quit-one ()
- "Exit the current transients, possibly returning to the previous."
+ "Exit the current transients, returning to outer transient, if any."
(interactive))
(defun transient-quit-seq ()
@@ -2568,17 +2850,48 @@ transient is active."
(defun transient-update ()
"Redraw the transient's state in the popup buffer."
(interactive)
- (when (equal this-original-command 'negative-argument)
- (setq prefix-arg current-prefix-arg)))
+ (setq prefix-arg current-prefix-arg))
(defun transient-show ()
"Show the transient's state in the popup buffer."
(interactive)
(setq transient--showp t))
-(defvar-local transient--restore-winconf nil)
+(defun transient-push-button ()
+ "Invoke the suffix command represented by this button."
+ (interactive))
+
+;;;; Suspend
-(defvar transient-resume-mode)
+(defun transient-suspend ()
+ "Suspend the current transient.
+It can later be resumed using `transient-resume', while no other
+transient is active."
+ (interactive))
+
+(define-minor-mode transient-resume-mode
+ "Auxiliary minor-mode used to resume a transient after viewing help.")
+
+(defun transient-resume ()
+ "Resume a previously suspended stack of transients."
+ (interactive)
+ (cond (transient--stack
+ (let ((winconf transient--restore-winconf))
+ (kill-local-variable 'transient--restore-winconf)
+ (when transient-resume-mode
+ (transient-resume-mode -1)
+ (quit-window))
+ (when winconf
+ (set-window-configuration winconf)))
+ (transient--stack-pop))
+ (transient-resume-mode
+ (kill-local-variable 'transient--restore-winconf)
+ (transient-resume-mode -1)
+ (quit-window))
+ (t
+ (message "No suspended transient command"))))
+
+;;;; Help
(defun transient-help (&optional interactive)
"Show help for the active transient or one of its suffixes.\n\n(fn)"
@@ -2595,12 +2908,15 @@ transient is active."
transient--prefix
(or (transient-suffix-object)
this-original-command)))
- (setq transient--restore-winconf winconf))
+ (setq-local transient--restore-winconf winconf))
(fit-window-to-buffer nil (frame-height) (window-height))
(transient-resume-mode)
- (message "Type \"q\" to resume transient command.")
+ (message (substitute-command-keys
+ "Type \\`q' to resume transient command."))
t))))
+;;;; Level
+
(defun transient-set-level (&optional command level)
"Set the level of the transient or one of its suffix commands."
(interactive
@@ -2612,10 +2928,9 @@ transient is active."
(list command
(let ((keys (this-single-command-raw-keys)))
(and (lookup-key transient--transient-map keys)
- (string-to-number
- (let ((transient--active-infix
- (transient-suffix-object command)))
- (transient--show)
+ (progn
+ (transient--show)
+ (string-to-number
(transient--read-number-N
(format "Set level for `%s': " command)
nil nil (not (eq command prefix)))))))))))
@@ -2626,32 +2941,64 @@ transient is active."
(level
(let* ((prefix (oref transient--prefix command))
(alist (alist-get prefix transient-levels))
- (sym command))
- (if (eq command prefix)
- (progn (oset transient--prefix level level)
- (setq sym t))
- (oset (transient-suffix-object command) level level))
- (setf (alist-get sym alist) level)
+ (akey command))
+ (cond ((eq command prefix)
+ (oset transient--prefix level level)
+ (setq akey t))
+ (t
+ (oset (transient-suffix-object command) level level)
+ (when (cdr (cl-remove-if-not (lambda (obj)
+ (eq (oref obj command) command))
+ transient--suffixes))
+ (setq akey (cons command (this-command-keys))))))
+ (setf (alist-get akey alist) level)
(setf (alist-get prefix transient-levels) alist))
(transient-save-levels)
(transient--show))
(t
(transient-undefined))))
+(transient-define-suffix transient-toggle-level-limit ()
+ "Toggle whether to temporarily displayed suffixes on all levels."
+ :description
+ (lambda ()
+ (cond
+ ((= transient-default-level transient--max-level)
+ "Always displaying all levels")
+ (transient--all-levels-p
+ (format "Hide suffix %s"
+ (propertize
+ (format "levels > %s" (oref (transient-prefix-object) level))
+ 'face 'transient-higher-level)))
+ ("Show all suffix levels")))
+ :inapt-if (lambda () (= transient-default-level transient--max-level))
+ :transient t
+ (interactive)
+ (setq transient--all-levels-p (not transient--all-levels-p))
+ (setq transient--refreshp t))
+
+;;;; Value
+
(defun transient-set ()
- "Save the value of the active transient for this Emacs session."
+ "Set active transient's value for this Emacs session."
(interactive)
- (transient-set-value (or transient--prefix transient-current-prefix)))
+ (transient-set-value (transient-prefix-object)))
+
+(defalias 'transient-set-and-exit #'transient-set
+ "Set active transient's value for this Emacs session and exit.")
(defun transient-save ()
- "Save the value of the active transient persistenly across Emacs sessions."
+ "Save active transient's value for this and future Emacs sessions."
(interactive)
- (transient-save-value (or transient--prefix transient-current-prefix)))
+ (transient-save-value (transient-prefix-object)))
+
+(defalias 'transient-save-and-exit #'transient-save
+ "Save active transient's value for this and future Emacs sessions and exit.")
(defun transient-reset ()
"Clear the set and saved values of the active transient."
(interactive)
- (transient-reset-value (or transient--prefix transient-current-prefix)))
+ (transient-reset-value (transient-prefix-object)))
(defun transient-history-next ()
"Switch to the next value used for the active transient."
@@ -2678,44 +3025,36 @@ transient is active."
(oset obj value (nth pos hst))
(mapc #'transient-init-value transient--suffixes))))
-(defun transient-scroll-up (&optional arg)
- "Scroll text of transient popup window upward ARG lines.
-If ARG is nil scroll near full screen. This is a wrapper
-around `scroll-up-command' (which see)."
- (interactive "^P")
- (with-selected-window transient--window
- (scroll-up-command arg)))
+;;;; Auxiliary
-(defun transient-scroll-down (&optional arg)
- "Scroll text of transient popup window down ARG lines.
-If ARG is nil scroll near full screen. This is a wrapper
-around `scroll-down-command' (which see)."
- (interactive "^P")
- (with-selected-window transient--window
- (scroll-down-command arg)))
-
-(defun transient-push-button ()
- "Invoke the suffix command represented by this button."
- (interactive))
+(defun transient-toggle-common ()
+ "Toggle whether common commands are permanently shown."
+ (interactive)
+ (setq transient-show-common-commands (not transient-show-common-commands)))
-(defun transient-resume ()
- "Resume a previously suspended stack of transients."
+(defun transient-toggle-debug ()
+ "Toggle debugging statements for transient commands."
(interactive)
- (cond (transient--stack
- (let ((winconf transient--restore-winconf))
- (kill-local-variable 'transient--restore-winconf)
- (when transient-resume-mode
- (transient-resume-mode -1)
- (quit-window))
- (when winconf
- (set-window-configuration winconf)))
- (transient--stack-pop))
- (transient-resume-mode
- (kill-local-variable 'transient--restore-winconf)
- (transient-resume-mode -1)
- (quit-window))
- (t
- (message "No suspended transient command"))))
+ (setq transient--debug (not transient--debug))
+ (message "Debugging transient %s"
+ (if transient--debug "enabled" "disabled")))
+
+(transient-define-suffix transient-echo-arguments (arguments)
+ "Show the transient's active ARGUMENTS in the echo area.
+Intended for use in prefixes used for demonstration purposes,
+such as when suggesting a new feature or reporting an issue."
+ :transient t
+ :description "Echo arguments"
+ :key "x"
+ (interactive (list (transient-args transient-current-command)))
+ (message "%s: %s"
+ (key-description (this-command-keys))
+ (mapconcat (lambda (arg)
+ (propertize (if (string-match-p " " arg)
+ (format "%S" arg)
+ arg)
+ 'face 'transient-argument))
+ arguments " ")))
;;; Value
;;;; Init
@@ -2821,29 +3160,19 @@ user using the reader specified by the `reader' slot (using the
`transient-infix' method described below).
For some infix classes the value is changed without reading
-anything in the minibuffer, i.e. the mere act of invoking the
+anything in the minibuffer, i.e., the mere act of invoking the
infix command determines what the new value should be, based
on the previous value.")
(cl-defmethod transient-infix-read :around ((obj transient-infix))
- "Highlight the infix in the popup buffer.
-
-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
+ "Refresh the transient buffer and call the next method.
+
+Also wrap `cl-call-next-method' with two macros:
+- `transient--with-suspended-override' allows use of minibuffer.
+- `transient--with-emergency-exit' arranges for the transient to
+ be exited in case of an error."
+ (transient--show)
+ (transient--with-emergency-exit :infix-read
(transient--with-suspended-override
(cl-call-next-method obj))))
@@ -2860,7 +3189,7 @@ the lack of history, for example.
Only for very simple classes that toggle or cycle through a very
limited number of possible values should you replace this with a
-simple method that does not handle history. (E.g. for a command
+simple method that does not handle history. (E.g., for a command
line switch the only possible values are \"use it\" and \"don't use
it\", in which case it is pointless to preserve history.)"
(with-slots (value multi-value always-read allow-empty choices) obj
@@ -2871,6 +3200,7 @@ it\", in which case it is pointless to preserve history.)"
(oset obj value nil)
(let* ((enable-recursive-minibuffers t)
(reader (oref obj reader))
+ (choices (if (functionp choices) (funcall choices) choices))
(prompt (transient-prompt obj))
(value (if multi-value (mapconcat #'identity value ",") value))
(history-key (or (oref obj history-key)
@@ -2893,7 +3223,7 @@ it\", in which case it is pointless to preserve history.)"
initial-input history))
(choices
(completing-read prompt choices nil t initial-input history))
- (t (read-string prompt initial-input history)))))
+ ((read-string prompt initial-input history)))))
(cond ((and (equal value "") (not allow-empty))
(setq value nil))
((and (equal value "\"\"") allow-empty)
@@ -2924,8 +3254,10 @@ The last value is \"don't use any of these switches\"."
"Elsewhere use the reader of the infix command COMMAND.
Use this if you want to share an infix's history with a regular
stand-alone command."
- (cl-letf (((symbol-function #'transient--show) #'ignore))
- (transient-infix-read (get command 'transient--suffix))))
+ (if-let ((obj (transient--suffix-prototype command)))
+ (cl-letf (((symbol-function #'transient--show) #'ignore))
+ (transient-infix-read obj))
+ (error "Not a suffix command: `%s'" command)))
;;;; Readers
@@ -3016,8 +3348,6 @@ prompt."
;;;; Set
-(defvar transient--unset-incompatible t)
-
(cl-defgeneric transient-infix-set (obj value)
"Set the value of infix object OBJ to value.")
@@ -3025,29 +3355,32 @@ prompt."
"Set the value of infix object OBJ to value."
(oset obj value value))
-(cl-defmethod transient-infix-set :around ((obj transient-argument) value)
+(cl-defmethod transient-infix-set :after ((obj transient-argument) value)
"Unset incompatible infix arguments."
- (let ((arg (if (slot-boundp obj 'argument)
- (oref obj argument)
- (oref obj argument-regexp))))
- (if-let ((sic (and value arg transient--unset-incompatible))
- (spec (oref transient--prefix incompatible))
- (incomp (cl-mapcan (lambda (rule)
- (and (member arg rule)
- (remove arg rule)))
- spec)))
- (progn
- (cl-call-next-method obj value)
- (dolist (arg incomp)
- (when-let ((obj (cl-find-if
- (lambda (obj)
- (and (slot-exists-p obj 'argument)
- (slot-boundp obj 'argument)
- (equal (oref obj argument) arg)))
- transient--suffixes)))
- (let ((transient--unset-incompatible nil))
- (transient-infix-set obj nil)))))
- (cl-call-next-method obj value))))
+ (when-let* ((--- value)
+ (val (transient-infix-value obj))
+ (arg (if (slot-boundp obj 'argument)
+ (oref obj argument)
+ (oref obj argument-format)))
+ (spec (oref transient--prefix incompatible))
+ (filter (lambda (x rule)
+ (and (member x rule)
+ (remove x rule))))
+ (incomp (nconc
+ (cl-mapcan (apply-partially filter arg) spec)
+ (and (not (equal val arg))
+ (cl-mapcan (apply-partially filter val) spec)))))
+ (dolist (obj transient--suffixes)
+ (when-let* ((--- (cl-typep obj 'transient-argument))
+ (val (transient-infix-value obj))
+ (arg (if (slot-boundp obj 'argument)
+ (oref obj argument)
+ (oref obj argument-format)))
+ (--- (if (equal val arg)
+ (member arg incomp)
+ (or (member val incomp)
+ (member arg incomp)))))
+ (transient-infix-set obj nil)))))
(cl-defgeneric transient-set-value (obj)
"Set the value of the transient prefix OBJ.")
@@ -3101,7 +3434,7 @@ the set, saved or default value for PREFIX."
(transient--init-suffixes prefix)))))
(defun transient-get-value ()
- (transient--with-emergency-exit
+ (transient--with-emergency-exit :get-value
(cl-mapcan (lambda (obj)
(and (or (not (slot-exists-p obj 'unsavable))
(not (oref obj unsavable)))
@@ -3110,11 +3443,11 @@ the set, saved or default value for PREFIX."
(defun transient--get-wrapped-value (obj)
(and-let* ((value (transient-infix-value obj)))
- (cl-ecase (and (slot-exists-p obj 'multi-value)
- (oref obj multi-value))
- ((nil) (list value))
- ((t rest) (list value))
- (repeat value))))
+ (pcase-exhaustive (and (slot-exists-p obj 'multi-value)
+ (oref obj multi-value))
+ ('nil (list value))
+ ((or 't 'rest) (list value))
+ ('repeat value))))
(cl-defgeneric transient-infix-value (obj)
"Return the value of the suffix object OBJ.
@@ -3149,17 +3482,17 @@ does nothing." nil)
"Return ARGUMENT and VALUE as a unit or nil if the latter is nil."
(and-let* ((value (oref obj value)))
(let ((arg (oref obj argument)))
- (cl-ecase (oref obj multi-value)
- ((nil) (concat arg value))
- ((t rest) (cons arg value))
- (repeat (mapcar (lambda (v) (concat arg v)) value))))))
+ (pcase-exhaustive (oref obj multi-value)
+ ('nil (concat arg value))
+ ((or 't 'rest) (cons arg value))
+ ('repeat (mapcar (lambda (v) (concat arg v)) value))))))
(cl-defmethod transient-infix-value ((_ transient-variable))
"Return nil, which means \"no value\".
Setting the value of a variable is done by, well, setting the
-value of the variable. I.e. this is a side-effect and does not
-contribute to the value of the transient."
+value of the variable. I.e., this is a side-effect and does
+not contribute to the value of the transient."
nil)
;;;; Utilities
@@ -3241,12 +3574,13 @@ have a history of their own.")
(list (propertize (oref suffix key) 'face 'transient-key)))))
transient--suffixes)
#'string<)
- (propertize "|" 'face 'transient-unreachable-key))))))
+ (propertize "|" 'face 'transient-delimiter))))))
(defun transient--show ()
(transient--timer-cancel)
(setq transient--showp t)
- (let ((buf (get-buffer-create transient--buffer-name))
+ (let ((transient--shadowed-buffer (current-buffer))
+ (buf (get-buffer-create transient--buffer-name))
(focus nil))
(with-current-buffer buf
(when transient-enable-popup-navigation
@@ -3259,9 +3593,11 @@ have a history of their own.")
(when (bound-and-true-p tab-line-format)
(setq tab-line-format nil))
(setq header-line-format nil)
- (setq mode-line-format (if (eq transient-mode-line-format 'line)
- nil
- transient-mode-line-format))
+ (setq mode-line-format
+ (if (or (natnump transient-mode-line-format)
+ (eq transient-mode-line-format 'line))
+ nil
+ transient-mode-line-format))
(setq mode-line-buffer-identification
(symbol-name (oref transient--prefix command)))
(if transient-enable-popup-navigation
@@ -3272,16 +3608,8 @@ have a history of their own.")
(transient--insert-groups)
(when (or transient--helpp transient--editp)
(transient--insert-help))
- (when (and (eq transient-mode-line-format 'line)
- window-system)
- (let ((face
- (if-let ((f (and (transient--semantic-coloring-p)
- (transient--prefix-color transient--prefix))))
- `(,@(and (>= emacs-major-version 27) '(:extend t))
- :background ,(face-foreground f))
- 'transient-separator)))
- (insert (propertize "__" 'face face 'display '(space :height (1))))
- (insert (propertize "\n" 'face face 'line-height t))))
+ (when-let ((line (transient--separator-line)))
+ (insert line))
(when transient-force-fixed-pitch
(transient--force-fixed-pitch)))
(unless (window-live-p transient--window)
@@ -3303,11 +3631,31 @@ have a history of their own.")
(fit-window-to-buffer window nil (window-height window))
(fit-window-to-buffer window nil 1))))
+(defun transient--separator-line ()
+ (and-let* ((height (cond ((not window-system) nil)
+ ((natnump transient-mode-line-format)
+ transient-mode-line-format)
+ ((eq transient-mode-line-format 'line) 1)))
+ (face `(,@(and (>= emacs-major-version 27) '(:extend t))
+ :background
+ ,(or (face-foreground (transient--key-face nil 'non-suffix)
+ nil t)
+ "#gray60"))))
+ (concat (propertize "__" 'face face 'display `(space :height (,height)))
+ (propertize "\n" 'face face 'line-height t))))
+
+(defmacro transient-with-shadowed-buffer (&rest body)
+ "While in the transient buffer, temporarly make the shadowed buffer current."
+ (declare (indent 0) (debug t))
+ `(with-current-buffer (or transient--shadowed-buffer (current-buffer))
+ ,@body))
+
(defun transient--insert-groups ()
(let ((groups (cl-mapcan (lambda (group)
(let ((hide (oref group hide)))
(and (not (and (functionp hide)
- (funcall hide)))
+ (transient-with-shadowed-buffer
+ (funcall hide))))
(list group))))
transient--layout))
group)
@@ -3323,23 +3671,25 @@ have a history of their own.")
(cl-defmethod transient--insert-group :around ((group transient-group))
"Insert GROUP's description, if any."
- (when-let ((desc (transient-format-description group)))
+ (when-let ((desc (transient-with-shadowed-buffer
+ (transient-format-description group))))
(insert desc ?\n))
(let ((transient--max-group-level
- (max (oref group level) transient--max-group-level)))
+ (max (oref group level) transient--max-group-level))
+ (transient--pending-group group))
(cl-call-next-method group)))
(cl-defmethod transient--insert-group ((group transient-row))
(transient--maybe-pad-keys group)
(dolist (suffix (oref group suffixes))
- (insert (transient-format suffix))
+ (insert (transient-with-shadowed-buffer (transient-format suffix)))
(insert " "))
(insert ?\n))
(cl-defmethod transient--insert-group ((group transient-column))
(transient--maybe-pad-keys group)
(dolist (suffix (oref group suffixes))
- (let ((str (transient-format suffix)))
+ (let ((str (transient-with-shadowed-buffer (transient-format suffix))))
(insert str)
(unless (string-match-p ".\n\\'" str)
(insert ?\n)))))
@@ -3349,19 +3699,26 @@ have a history of their own.")
(mapcar
(lambda (column)
(transient--maybe-pad-keys column group)
- (let ((rows (mapcar #'transient-format (oref column suffixes))))
- (when-let ((desc (transient-format-description column)))
- (push desc rows))
- (flatten-tree rows)))
+ (transient-with-shadowed-buffer
+ (let* ((transient--pending-group column)
+ (rows (mapcar #'transient-format (oref column suffixes))))
+ (when-let ((desc (transient-format-description column)))
+ (push desc rows))
+ (flatten-tree rows))))
(oref group suffixes)))
(vp (or (oref transient--prefix variable-pitch)
transient-align-variable-pitch))
(rs (apply #'max (mapcar #'length columns)))
(cs (length columns))
- (cw (mapcar (lambda (col)
- (apply #'max
- (mapcar (if vp #'transient--pixel-width #'length)
- col)))
+ (cw (mapcar (let ((widths (oref transient--prefix column-widths)))
+ (lambda (col)
+ (apply
+ #'max
+ (if-let ((min (pop widths)))
+ (if vp (* min (transient--pixel-width " ")) min)
+ 0)
+ (mapcar (if vp #'transient--pixel-width #'length)
+ col))))
columns))
(cc (transient--seq-reductions-from
(apply-partially #'+ (* 3 (if vp (transient--pixel-width " ") 1)))
@@ -3392,15 +3749,6 @@ have a history of their own.")
(when (= c (1- cs))
(insert ?\n))))))))
-(defun transient--pixel-width (string)
- (save-window-excursion
- (with-temp-buffer
- (insert string)
- (set-window-dedicated-p nil nil)
- (set-window-buffer nil (current-buffer))
- (car (window-text-pixel-size
- nil (line-beginning-position) (point))))))
-
(cl-defmethod transient--insert-group ((group transient-subgroups))
(let* ((subgroups (oref group suffixes))
(n (length subgroups)))
@@ -3431,36 +3779,31 @@ making `transient--original-buffer' current.")
"Return a string containing just the ARG character."
(char-to-string arg))
-(cl-defmethod transient-format :around ((obj transient-infix))
- "When reading user input for this infix, then highlight it."
+(cl-defmethod transient-format :around ((obj transient-suffix))
+ "Add additional formatting if appropriate.
+When reading user input for this infix, then highlight it.
+When edit-mode is enabled, then prepend the level information.
+When `transient-enable-popup-navigation' is non-nil then format
+as a button."
(let ((str (cl-call-next-method obj)))
- (when (eq obj transient--active-infix)
- (setq str (concat str "\n"))
- (add-face-text-property
- (if (eq this-command 'transient-set-level) 3 0)
- (length str)
- 'transient-active-infix nil str))
+ (when (and (cl-typep obj 'transient-infix)
+ (eq (oref obj command) this-original-command)
+ (active-minibuffer-window))
+ (setq str (transient--add-face str 'transient-active-infix)))
+ (when transient--editp
+ (setq str (concat (let ((level (oref obj level)))
+ (propertize (format " %s " level)
+ 'face (if (transient--use-level-p level t)
+ 'transient-enabled-suffix
+ 'transient-disabled-suffix)))
+ str)))
+ (when (and transient-enable-popup-navigation
+ (slot-boundp obj 'command))
+ (setq str (make-text-button str nil
+ 'type 'transient
+ 'command (oref obj command))))
str))
-(cl-defmethod transient-format :around ((obj transient-suffix))
- "When edit-mode is enabled, then prepend the level information.
-Optional support for popup buttons is also implemented here."
- (let ((str (concat
- (and transient--editp
- (let ((level (oref obj level)))
- (propertize (format " %s " level)
- 'face (if (transient--use-level-p level t)
- 'transient-enabled-suffix
- 'transient-disabled-suffix))))
- (cl-call-next-method obj))))
- (when (oref obj inapt)
- (add-face-text-property 0 (length str) 'transient-inapt-suffix nil str))
- (if transient-enable-popup-navigation
- (make-text-button str nil
- 'type 'transient
- 'command (oref obj command))
- str)))
-
(cl-defmethod transient-format ((obj transient-infix))
"Return a string generated using OBJ's `format'.
%k is formatted using `transient-format-key'.
@@ -3482,10 +3825,19 @@ Optional support for popup buttons is also implemented here."
(cl-defgeneric transient-format-key (obj)
"Format OBJ's `key' for display and return the result.")
+(cl-defmethod transient-format-key :around ((obj transient-suffix))
+ "Add `transient-inapt-suffix' face if suffix is inapt."
+ (let ((str (cl-call-next-method)))
+ (if (oref obj inapt)
+ (transient--add-face str 'transient-inapt-suffix)
+ str)))
+
(cl-defmethod transient-format-key ((obj transient-suffix))
"Format OBJ's `key' for display and return the result."
- (let ((key (oref obj key))
- (cmd (oref obj command)))
+ (let ((key (if (slot-boundp obj 'key) (oref obj key) ""))
+ (cmd (and (slot-boundp obj 'command) (oref obj command))))
+ (when-let ((width (oref transient--pending-group pad-keys)))
+ (setq key (truncate-string-to-width key width nil ?\s)))
(if transient--redisplay-key
(let ((len (length transient--redisplay-key))
(seq (cl-coerce (edmacro-parse-keys key t) 'list)))
@@ -3502,7 +3854,7 @@ Optional support for popup buttons is also implemented here."
(setq pre (string-replace "TAB" "C-i" pre))
(setq suf (string-replace "RET" "C-m" suf))
(setq suf (string-replace "TAB" "C-i" suf))
- ;; We use e.g. "-k" instead of the more correct "- k",
+ ;; We use e.g., "-k" instead of the more correct "- k",
;; because the former is prettier. If we did that in
;; the definition, then we want to drop the space that
;; is reinserted above. False-positives are possible
@@ -3512,33 +3864,27 @@ Optional support for popup buttons is also implemented here."
(setq suf (string-replace " " "" suf)))
(concat (propertize pre 'face 'transient-unreachable-key)
(and (string-prefix-p (concat pre " ") key) " ")
- (transient--colorize-key suf cmd)
+ (propertize suf 'face (transient--key-face cmd))
(save-excursion
(and (string-match " +\\'" key)
(propertize (match-string 0 key)
'face 'fixed-pitch))))))
((transient--lookup-key transient-sticky-map (kbd key))
- (transient--colorize-key key cmd))
+ (propertize key 'face (transient--key-face cmd)))
(t
(propertize key 'face 'transient-unreachable-key))))
- (transient--colorize-key key cmd))))
-
-(defun transient--colorize-key (key command)
- (propertize key 'face
- (or (and (transient--semantic-coloring-p)
- (transient--suffix-color command))
- 'transient-key)))
+ (propertize key 'face (transient--key-face cmd)))))
(cl-defmethod transient-format-key :around ((obj transient-argument))
+ "Handle `transient-highlight-mismatched-keys'."
(let ((key (cl-call-next-method obj)))
- (cond ((not transient-highlight-mismatched-keys))
- ((not (slot-boundp obj 'shortarg))
- (add-face-text-property
- 0 (length key) 'transient-nonstandard-key nil key))
- ((not (string-equal key (oref obj shortarg)))
- (add-face-text-property
- 0 (length key) 'transient-mismatched-key nil key)))
- key))
+ (cond
+ ((not transient-highlight-mismatched-keys) key)
+ ((not (slot-boundp obj 'shortarg))
+ (transient--add-face key 'transient-nonstandard-key))
+ ((not (string-equal key (oref obj shortarg)))
+ (transient--add-face key 'transient-mismatched-key))
+ (key))))
(cl-defgeneric transient-format-description (obj)
"Format OBJ's `description' for display and return the result.")
@@ -3547,10 +3893,14 @@ Optional support for popup buttons is also implemented here."
"The `description' slot may be a function, in which case that is
called inside the correct buffer (see `transient--insert-group')
and its value is returned to the caller."
- (and-let* ((desc (oref obj description)))
- (if (functionp desc)
- (with-current-buffer transient--original-buffer
- (funcall desc))
+ (and-let* ((desc (oref obj description))
+ (desc (if (functionp desc)
+ (if (= (car (func-arity desc)) 1)
+ (funcall desc obj)
+ (funcall desc))
+ desc)))
+ (if-let* ((face (transient--get-face obj 'face)))
+ (transient--add-face desc face t)
desc)))
(cl-defmethod transient-format-description ((obj transient-group))
@@ -3572,16 +3922,19 @@ If the OBJ's `key' is currently unreachable, then apply the face
(funcall (oref transient--prefix suffix-description)
obj))
(propertize "(BUG: no description)" 'face 'error))))
- (cond ((transient--key-unreachable-p obj)
- (propertize desc 'face 'transient-unreachable))
- ((and transient-highlight-higher-levels
- (> (max (oref obj level) transient--max-group-level)
- transient--default-prefix-level))
- (add-face-text-property
- 0 (length desc) 'transient-higher-level nil desc)
- desc)
- (t
- desc))))
+ (when (if transient--all-levels-p
+ (> (oref obj level) transient--default-prefix-level)
+ (and transient-highlight-higher-levels
+ (> (max (oref obj level) transient--max-group-level)
+ transient--default-prefix-level)))
+ (setq desc (transient--add-face desc 'transient-higher-level)))
+ (when-let ((inapt-face (and (oref obj inapt)
+ (transient--get-face obj 'inapt-face))))
+ (setq desc (transient--add-face desc inapt-face)))
+ (when (and (slot-boundp obj 'key)
+ (transient--key-unreachable-p obj))
+ (setq desc (transient--add-face desc 'transient-unreachable)))
+ desc))
(cl-defgeneric transient-format-value (obj)
"Format OBJ's value for display and return the result.")
@@ -3595,24 +3948,32 @@ If the OBJ's `key' is currently unreachable, then apply the face
(cl-defmethod transient-format-value ((obj transient-option))
(let ((argument (oref obj argument)))
(if-let ((value (oref obj value)))
- (propertize
- (cl-ecase (oref obj multi-value)
- ((nil) (concat argument value))
- ((t rest) (concat argument
- (and (not (string-suffix-p " " argument)) " ")
- (mapconcat #'prin1-to-string value " ")))
- (repeat (mapconcat (lambda (v) (concat argument v)) value " ")))
- 'face 'transient-value)
- (propertize argument 'face 'transient-inactive-value))))
+ (pcase-exhaustive (oref obj multi-value)
+ ('nil
+ (concat (propertize argument 'face 'transient-argument)
+ (propertize value 'face 'transient-value)))
+ ((or 't 'rest)
+ (concat (propertize (if (string-suffix-p " " argument)
+ argument
+ (concat argument " "))
+ 'face 'transient-argument)
+ (propertize (mapconcat #'prin1-to-string value " ")
+ 'face 'transient-value)))
+ ('repeat
+ (mapconcat (lambda (value)
+ (concat (propertize argument 'face 'transient-argument)
+ (propertize value 'face 'transient-value)))
+ value " ")))
+ (propertize argument 'face 'transient-inactive-argument))))
(cl-defmethod transient-format-value ((obj transient-switches))
(with-slots (value argument-format choices) obj
(format (propertize argument-format
'face (if value
- 'transient-value
- 'transient-inactive-value))
- (concat
- (propertize "[" 'face 'transient-inactive-value)
+ 'transient-argument
+ 'transient-inactive-argument))
+ (format
+ (propertize "[%s]" 'face 'transient-delimiter)
(mapconcat
(lambda (choice)
(propertize choice 'face
@@ -3620,8 +3981,33 @@ If the OBJ's `key' is currently unreachable, then apply the face
'transient-value
'transient-inactive-value)))
choices
- (propertize "|" 'face 'transient-inactive-value))
- (propertize "]" 'face 'transient-inactive-value)))))
+ (propertize "|" 'face 'transient-delimiter))))))
+
+(defun transient--add-face (string face &optional append beg end)
+ (let ((str (copy-sequence string)))
+ (add-face-text-property (or beg 0) (or end (length str)) face append str)
+ str))
+
+(defun transient--get-face (obj slot)
+ (and-let* ((! (slot-exists-p obj slot))
+ (! (slot-boundp obj slot))
+ (face (slot-value obj slot)))
+ (if (and (not (facep face))
+ (functionp face))
+ (let ((transient--pending-suffix obj))
+ (if (= (car (func-arity face)) 1)
+ (funcall face obj)
+ (funcall face)))
+ face)))
+
+(defun transient--key-face (&optional cmd enforce-type)
+ (or (and transient-semantic-coloring
+ (not transient--helpp)
+ (not transient--editp)
+ (or (and cmd (get cmd 'transient-face))
+ (get (transient--get-pre-command cmd enforce-type)
+ 'transient-face)))
+ (if cmd 'transient-key 'transient-key-noop)))
(defun transient--key-unreachable-p (obj)
(and transient--redisplay-key
@@ -3636,19 +4022,24 @@ If the OBJ's `key' is currently unreachable, then apply the face
(and val (not (integerp val)) val)))
(defun transient--maybe-pad-keys (group &optional parent)
- (when-let ((pad (if (slot-boundp group 'pad-keys)
- (oref group pad-keys)
- (and parent
- (slot-boundp parent 'pad-keys)
- (oref parent pad-keys)))))
- (let ((width (apply #'max
- (cons (if (integerp pad) pad 0)
- (mapcar (lambda (suffix)
- (length (oref suffix key)))
- (oref group suffixes))))))
- (dolist (suffix (oref group suffixes))
- (oset suffix key
- (truncate-string-to-width (oref suffix key) width nil ?\s))))))
+ (when-let ((pad (or (oref group pad-keys)
+ (and parent (oref parent pad-keys)))))
+ (oset group pad-keys
+ (apply #'max (cons (if (integerp pad) pad 0)
+ (seq-keep (lambda (suffix)
+ (and (eieio-object-p suffix)
+ (slot-boundp suffix 'key)
+ (length (oref suffix key))))
+ (oref group suffixes)))))))
+
+(defun transient--pixel-width (string)
+ (save-window-excursion
+ (with-temp-buffer
+ (insert string)
+ (set-window-dedicated-p nil nil)
+ (set-window-buffer nil (current-buffer))
+ (car (window-text-pixel-size
+ nil (line-beginning-position) (point))))))
(defun transient-command-summary-or-name (obj)
"Return the summary or name of the command represented by OBJ.
@@ -3676,7 +4067,7 @@ if non-nil, else show the `man-page' if non-nil, else use
(cond (show-help (funcall show-help obj))
(info-manual (transient--show-manual info-manual))
(man-page (transient--show-manpage man-page))
- (t (transient--describe-function command)))))
+ ((transient--describe-function command)))))
(cl-defmethod transient-show-help ((obj transient-suffix))
"Call `show-help' if non-nil, else use `describe-function'.
@@ -3690,9 +4081,9 @@ prefix method."
'transient--prefix)))
(and prefix (not (eq (oref transient--prefix command) this-command))
(prog1 t (transient-show-help prefix)))))
- (t (if-let ((show-help (oref obj show-help)))
- (funcall show-help obj)
- (transient--describe-function this-command)))))
+ ((if-let ((show-help (oref obj show-help)))
+ (funcall show-help obj)
+ (transient--describe-function this-command)))))
(cl-defmethod transient-show-help ((obj transient-infix))
"Call `show-help' if non-nil, else show the `man-page'
@@ -3712,7 +4103,7 @@ manpage, then try to jump to the correct location."
(transient--describe-function cmd))
(defun transient--describe-function (fn)
- (describe-function (if (symbolp fn) fn 'transient--anonymous-infix-argument))
+ (describe-function fn)
(unless (derived-mode-p 'help-mode)
(when-let* ((buf (get-buffer "*Help*"))
(win (or (and buf (get-buffer-window buf))
@@ -3722,21 +4113,6 @@ manpage, then try to jump to the correct location."
(window-list)))))
(select-window win))))
-(defun transient--anonymous-infix-argument ()
- "Cannot show any documentation for this anonymous infix command.
-
-The infix command in question was defined anonymously, i.e.,
-it was define when the prefix command that it belongs to was
-defined, which means that it gets no docstring and also that
-no symbol is bound to it.
-
-When you request help for an infix command, then we usually
-show the respective man-page and jump to the location where
-the respective argument is being described.
-
-Because the containing prefix command does not specify any
-man-page, we cannot do that in this case. Sorry about that.")
-
(defun transient--show-manual (manual)
(info manual))
@@ -3829,37 +4205,23 @@ Suffixes on levels %s and %s are unavailable.\n"
(propertize (format ">=%s" (1+ level))
'face 'transient-disabled-suffix))))))
-(defvar-keymap transient-resume-mode-map
- :doc "Keymap for `transient-resume-mode'.
-
-This keymap remaps every command that would usually just quit the
-documentation buffer to `transient-resume', which additionally
-resumes the suspended transient."
- "<remap> <Man-quit>" #'transient-resume
- "<remap> <Info-exit>" #'transient-resume
- "<remap> <quit-window>" #'transient-resume)
-
-(define-minor-mode transient-resume-mode
- "Auxiliary minor-mode used to resume a transient after viewing help.")
-
-(defun transient-toggle-debug ()
- "Toggle debugging statements for transient commands."
- (interactive)
- (setq transient--debug (not transient--debug))
- (message "Debugging transient %s"
- (if transient--debug "enabled" "disabled")))
-
;;; Popup Navigation
-(defun transient-popup-navigation-help ()
- "Inform the user how to enable popup navigation commands."
- (interactive)
- (message "This command is only available if `%s' is non-nil"
- 'transient-enable-popup-navigation))
+(defun transient-scroll-up (&optional arg)
+ "Scroll text of transient popup window upward ARG lines.
+If ARG is nil scroll near full screen. This is a wrapper
+around `scroll-up-command' (which see)."
+ (interactive "^P")
+ (with-selected-window transient--window
+ (scroll-up-command arg)))
-(define-button-type 'transient
- 'face nil
- 'keymap transient-button-map)
+(defun transient-scroll-down (&optional arg)
+ "Scroll text of transient popup window down ARG lines.
+If ARG is nil scroll near full screen. This is a wrapper
+around `scroll-down-command' (which see)."
+ (interactive "^P")
+ (with-selected-window transient--window
+ (scroll-down-command arg)))
(defun transient-backward-button (n)
"Move to the previous button in the transient popup buffer.
@@ -3875,6 +4237,10 @@ See `forward-button' for information about N."
(with-selected-window transient--window
(forward-button n t)))
+(define-button-type 'transient
+ 'face nil
+ 'keymap transient-button-map)
+
(defun transient--goto-button (command)
(cond
((stringp command)
@@ -3952,36 +4318,6 @@ search instead."
(select-window transient--original-window)
(transient--resume-override))
-;;;; Hydra Color Emulation
-
-(defun transient--semantic-coloring-p ()
- (and transient-semantic-coloring
- (not transient--helpp)
- (not transient--editp)))
-
-(defun transient--suffix-color (command)
- (or (get command 'transient-color)
- (get (transient--get-predicate-for command) 'transient-color)))
-
-(defun transient--prefix-color (command)
- (let* ((nonsuf (or (oref command transient-non-suffix)
- 'transient--do-warn))
- (nonsuf (if (memq nonsuf '(transient--do-noop transient--do-warn))
- 'disallow
- (get nonsuf 'transient-color)))
- (suffix (if-let ((pred (oref command transient-suffix)))
- (get pred 'transient-color)
- (if (eq nonsuf 'transient-red)
- 'transient-red
- 'transient-blue))))
- (pcase (list suffix nonsuf)
- (`(transient-purple ,_) 'transient-purple)
- ('(transient-red disallow) 'transient-amaranth)
- ('(transient-blue disallow) 'transient-teal)
- ('(transient-red transient-red) 'transient-pink)
- ('(transient-red transient-blue) 'transient-red)
- ('(transient-blue transient-blue) 'transient-blue))))
-
;;;; Edebug
(defun transient--edebug-command-p ()
@@ -4043,7 +4379,7 @@ we stop there."
(let ((key (oref obj key)))
(cond ((string-equal key "q") "Q")
((string-equal key "Q") "M-q")
- (t key))))
+ (key))))
(defun transient--force-fixed-pitch ()
(require 'face-remap)
@@ -4078,8 +4414,7 @@ we stop there."
(regexp-opt (list "transient-define-prefix"
"transient-define-infix"
"transient-define-argument"
- "transient-define-suffix"
- "transient-define-groups")
+ "transient-define-suffix")
t)
"\\_>[ \t'(]*"
"\\(\\(?:\\sw\\|\\s_\\)+\\)?")