@finalout
@titlepage
@title Transient User and Developer Manual
-@subtitle for version 0.8.3
+@subtitle for version 0.8.4
@author Jonas Bernoulli
@page
@vskip 0pt plus 1filll
available at @uref{https://github.com/positron-solutions/transient-showcase}.
@noindent
-This manual is for Transient version 0.8.3.
+This manual is for Transient version 0.8.4.
@insertcopying
@end ifnottex
* Binding Suffix and Infix Commands::
* Defining Suffix and Infix Commands::
* Using Infix Arguments::
+* Using Prefix Scope::
* Current Suffix Command::
* Current Prefix Command::
* Transient State::
available anyway.
@end table
+@defun transient-set-default-level suffix level
+This function sets the default level of the suffix COMMAND to LEVEL@.
+
+If a suffix command appears in multiple menus, it may make sense to
+consistently change its level in all those menus at once. For
+example, the @code{--gpg-sign} argument (which is implemented using the
+command @code{magit:--gpg-sign}), is bound in all of Magit's menu which
+create commits. Users who sometimes sign their commits would want
+that argument to be available in all of these menus, while for users
+who never sign it is just unnecessary noise in any menus.
+
+To always make @code{--gpg-sign} available, use:
+
+@lisp
+(transient-set-default-level 'magit:--gpg-sign 1)
+@end lisp
+
+To never make @code{--gpg-sign} available, use:
+
+@lisp
+(transient-set-default-level 'magit:--gpg-sign 0)
+@end lisp
+
+This sets the level in the suffix prototype object for this command.
+Commands only have a suffix prototype if they were defined using one
+of @code{transient-define-argument}, @code{transient-define-infix} and
+@code{transient-define-suffix}. For all other commands this would signal
+an error. (This is one of the reasons why package authors should
+use one of these functions to define shared suffix commands, and
+especially shared arguments.)
+
+If the user changes the level of a suffix in a particular menu,
+using @kbd{C-x l} as shown above, then that obviously shadows the default.
+
+It is also possible to set the level of a suffix binding in a
+particular menu, either when defining the menu using
+@code{transient-define-prefix,} or later using @code{transient-insert-suffix}. If
+such bindings specify a level, then that also overrides the default.
+(Per-suffix default levels is a new feature, so you might encounter
+this quite often.)
+@end defun
+
@node Other Commands
@section Other Commands
* Binding Suffix and Infix Commands::
* Defining Suffix and Infix Commands::
* Using Infix Arguments::
+* Using Prefix Scope::
* Current Suffix Command::
* Current Prefix Command::
* Transient State::
The boolean @code{:pad-keys} argument controls whether keys of all suffixes
contained in a group are right padded, effectively aligning the
descriptions.
+
+@item
+If a keyword argument accepts a function as value, you an use a
+@code{lambda} expression. As a special case, the @code{##} macro (which returns a
+@code{lambda} expression and is implemented in the @code{llama} package) is also
+supported. Inside group specifications, the use of @code{##} is not
+supported anywhere but directly following a keyword symbol.
@end itemize
The @var{ELEMENT}s are either all subgroups, or all suffixes and strings.
Each keyword has to be a keyword symbol, either @code{:class} or a keyword
argument supported by the constructor of that class. @xref{Suffix Slots}.
+If a keyword argument accepts a function as value, you an use a @code{lambda}
+expression. As a special case, the @code{##} macro (which returns a @code{lambda}
+expression and is implemented in the @code{llama} package) is also supported.
+Inside suffix bindings, the use of @code{##} is not supported anywhere but
+directly following a keyword symbol.
+
@node Defining Suffix and Infix Commands
@section Defining Suffix and Infix Commands
if the current command is not being invoked from @var{PREFIX}.
@end defun
+@node Using Prefix Scope
+@section Using Prefix Scope
+
+Some transients have a sort of secondary value, called a scope. A
+prefix's scope can be accessed using @code{transient-scope}; similar to how
+its value can be accessed using @code{transient-args}.
+
+@defun transient-scope prefixes classes
+This function returns the scope of the active or current transient
+prefix command.
+
+If optional PREFIXES and CLASSES are both nil, return the scope of
+the prefix currently being setup, making this variation useful, e.g.,
+in @code{:if*} predicates. If no prefix is being setup, but the current
+command was invoked from some prefix, then return the scope of that.
+
+If PREFIXES is non-nil, it must be a prefix command or a list of such
+commands. If CLASSES is non-nil, it must be a prefix class or a list
+of such classes. When this function is called from the body or the
+@code{interactive} form of a suffix command, PREFIXES and/or CLASSES should
+be non-nil. If either is non-nil, try the following in order:
+
+@itemize
+@item
+If the current suffix command was invoked from a prefix, which
+appears in PREFIXES, return the scope of that prefix.
+
+@item
+If the current suffix command was invoked from a prefix, and its
+class derives from one of the CLASSES, return the scope of that
+prefix.
+
+@item
+If a prefix is being setup and it appears in PREFIXES, return its
+scope.
+
+@item
+If a prefix is being setup and its class derives from one of the
+CLASSES, return its scope.
+
+@item
+Finally try to return the default scope of the first command in
+PREFIXES@. This only works if that slot is set in the respective
+class definition or using its `transient-init-scope' method.
+@end itemize
+
+If no prefix matches, return nil.
+@end defun
+
@node Current Suffix Command
@section Current Suffix Command
@code{transient-mode-line-format}. It should have the same type.
@item
-@code{column-width} is only respected inside @code{transient-columns} groups and
-allows aligning columns across separate instances of that.
+@code{column-widths} is only respected inside @code{transient-columns} groups and
+allows aligning columns across separate instances of that. A list
+of integers.
@item
@code{variable-pitch} controls whether alignment is done pixel-wise to
@subheading Slots of @code{transient-child}
This is the abstract superclass of @code{transient-suffix} and @code{transient-group}.
-This is where the shared @code{if*} and @code{inapt-if*} slots (see @ref{Predicate Slots})
-and the @code{level} slot (see @ref{Enabling and Disabling Suffixes}) are defined.
+This is where the shared @code{if*} and @code{inapt-if*} slots (see @ref{Predicate Slots}),
+the @code{level} slot (see @ref{Enabling and Disabling Suffixes}), and the @code{advice}
+and @code{advice*} slots (see @ref{Slots of @code{transient-suffix}}) are defined.
@itemize
@item
defining a command using @code{transient-define-suffix}.
@end itemize
+The following two slots are experimental. They can also be set for a
+group, in which case they apply to all suffixes in that group, except
+for suffixes that set the same slot to a non-nil value.
+
+@itemize
+@item
+@code{advice} A function used to advise the command. The advise is called
+using @code{(apply advice command args)}, i.e., it behaves like an "around"
+advice.
+
+@item
+@code{advice*} A function used to advise the command. Unlike @code{advice}, this
+advises not only the command body but also its @code{interactive} spec. If
+both slots are non-nil, @code{advice} is used for the body and @code{advice*} is
+used for the @code{interactive} form. When advising the @code{interactive} spec,
+called using @code{(funcall advice #'advice-eval-interactive-spec spec)}.
+@end itemize
+
@anchor{Slots of @code{transient-infix}}
@subheading Slots of @code{transient-infix}
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; URL: https://github.com/magit/transient
;; Keywords: extensions
-;; Version: 0.8.3
+;; Version: 0.8.4
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Code:
-(defconst transient-version "v0.8.3-2-gf0478b29-builtin")
+(defconst transient-version "v0.8.4-7-gabee7353-builtin")
(require 'cl-lib)
(require 'eieio)
:format "\n %t: %v"
:value -20)))
+(defcustom transient-show-docstring-format "%s"
+ "How to display suffix docstrings.
+
+The command `transient-toggle-docstrings' toggles between showing suffix
+descriptions as usual, and instead or additionally displaying the suffix
+docstrings. The format specified here controls how that is done. %c is
+the description and %s is the docstring. Use \"%-14c %s\" or similar to
+display both.
+
+This command is not bound by default, see its docstring for instructions."
+ :package-version '(transient . "0.8.4")
+ :group 'transient
+ :type 'string)
+
(defcustom transient-read-with-initial-input nil
"Whether to use the last history element as initial minibuffer input."
:package-version '(transient . "0.2.0")
:documentation "The parent group object.")
(level
:initarg :level
- :initform (symbol-value 'transient--default-child-level)
+ :initform nil
:documentation "Enable if level of prefix is equal or greater.")
(if
:initarg :if
(inapt-if-not-derived
:initarg :inapt-if-not-derived
:initform nil
- :documentation "Inapt if major-mode does not derive from value."))
+ :documentation "Inapt if major-mode does not derive from value.")
+ (advice
+ :initarg :advice
+ :initform nil
+ :documentation "Advise applied to the command body.")
+ (advice*
+ :initarg :advice*
+ :initform nil
+ :documentation "Advise applied to the command body and interactive spec."))
"Abstract superclass for group and suffix classes.
It is undefined which predicates are used if more than one `if*'
(cond ((eq key :class)
(setq class val))
((or (symbolp val)
- (and (listp val) (not (eq (car val) 'lambda))))
+ (and (listp val)
+ (not (memq (car val) (list 'lambda (intern ""))))))
(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)
+ level
(list 'quote
(cond (class)
((cl-typep (car spec)
((guard (eq (car-safe val) '\,))
(use key (cadr val)))
((guard (or (symbolp val)
- (and (listp val) (not (eq (car val) 'lambda)))))
+ (and (listp val)
+ (not (memq (car val) (list 'lambda (intern "")))))))
(use key (macroexp-quote val)))
(_ (use key val)))))
(when spec
(shortarg (plist-get args :shortarg)))
(use :key shortarg)))
(list 'list
- (or level transient--default-child-level)
+ level
(macroexp-quote (or class 'transient-suffix))
(cons 'list args))))
(defun transient--nthcdr (n list)
(nthcdr (if (< n 0) (- (length list) (abs n)) n) list))
+(defun transient-set-default-level (command level)
+ "Set the default level of suffix COMMAND to LEVEL.
+
+The default level is shadowed if the binding of the suffix in a
+prefix menu specifies a level, and also if the user changes the
+level of such a binding.
+
+The default level can only be set for commands that were defined
+using `transient-define-suffix', `transient-define-infix' or
+`transient-define-argument'."
+ (if-let ((proto (transient--suffix-prototype command)))
+ (oset proto level level)
+ (user-error "Cannot set level for `%s'; no prototype object exists"
+ command)))
+
;;; Variables
(defvar transient-current-prefix nil
(string (list spec))))
(defun transient--init-group (levels spec parent)
- (pcase-let ((`(,level ,class ,args ,children) (append spec nil)))
+ (pcase-let* ((`(,level ,class ,args ,children) (append spec nil))
+ (level (or level transient--default-child-level)))
(and-let* (((transient--use-level-p level))
(obj (apply class :parent parent :level level args))
((transient--use-suffix-p obj))
(pcase-let* ((`(,level ,class ,args) spec)
(cmd (plist-get args :command))
(key (transient--kbd (plist-get args :key)))
+ (proto (and cmd (transient--suffix-prototype cmd)))
(level (or (alist-get (cons cmd key) levels nil nil #'equal)
(alist-get cmd levels)
- level)))
+ level
+ (and proto (oref proto level))
+ transient--default-child-level)))
(let ((fn (and (symbolp cmd)
(symbol-function cmd))))
(when (autoloadp fn)
(apply class :parent parent :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))))
+ (if proto
(apply #'clone proto :level level args)
(apply class :command cmd :parent parent :level level
args)))))
(setq transient--redisplay-map nil)
(setq transient--redisplay-key nil)
(setq transient--helpp nil)
+ (unless (eq transient--docsp 'permanent)
+ (setq transient--docsp nil))
(setq transient--editp nil)
(setq transient--prefix nil)
(setq transient--layout nil)
(let ((abort t))
(unwind-protect
(prog1 (let ((debugger #'transient--exit-and-debug))
- (advice-eval-interactive-spec spec))
+ (if-let* ((obj (transient-suffix-object suffix))
+ (grp (oref obj parent))
+ (adv (or (oref obj advice*)
+ (oref grp advice*))))
+ (funcall
+ adv #'advice-eval-interactive-spec spec)
+ (advice-eval-interactive-spec spec)))
(setq abort nil))
(when abort
(when-let* ((unwind (oref prefix unwind-suffix)))
(oset prefix unwind-suffix nil))))))
(unwind-protect
(let ((debugger #'transient--exit-and-debug))
- (apply fn args))
+ (if-let* ((obj (transient-suffix-object suffix))
+ (grp (oref obj parent))
+ (adv (or (oref obj advice)
+ (oref grp advice)
+ (oref obj advice*)
+ (oref grp advice*))))
+ (apply adv fn args)
+ (apply fn args)))
(when-let* ((unwind (oref prefix unwind-suffix)))
(transient--debug 'unwind-command)
(funcall unwind suffix))
(interactive)
(setq transient-show-common-commands (not transient-show-common-commands)))
-(transient-define-suffix transient-toggle-docstrings ()
+(transient-define-suffix transient-toggle-docstrings (&optional permanent)
"Toggle whether to show docstrings instead of suffix descriptions.
-To make this available in all menus, bind it in `transient-map'."
+
+By default this is only enabled temporarily for the current transient
+menu invocation. With a prefix argument, enable this until explicitly
+disabled again.
+
+Infix arguments are not affected by this, because otherwise many menus
+would likely become unreadable. To make this command available in all
+menus, bind it in `transient-map'. `transient-show-docstring-format'
+controls how the docstrings are displayed and whether descriptions are
+also displayed."
:transient t
- (interactive)
- (setq transient--docsp (not transient--docsp)))
+ (interactive (list current-prefix-arg))
+ (setq transient--docsp (if permanent 'permanent (not transient--docsp))))
(defun transient-toggle-debug ()
"Toggle debugging statements for transient commands."
;;;; Get
-(defun transient-scope (&optional prefixes)
+(defun transient-scope (&optional prefixes classes)
"Return the scope of the active or current transient prefix command.
-If optional PREFIXES is nil, return the scope of the prefix currently
-being setup, making this variant useful, e.g., in `:if*' predicates.
-If no prefix is being setup, but the current command was invoked from
-some prefix, then return the scope of that.
-
-When this function is called from the body or `interactive' form of a
-suffix command, PREFIXES should be non-nil.
+If optional PREFIXES and CLASSES are both nil, return the scope of
+the prefix currently being setup, making this variation useful, e.g.,
+in `:if*' predicates. If no prefix is being setup, but the current
+command was invoked from some prefix, then return the scope of that.
If PREFIXES is non-nil, it must be a prefix command or a list of such
-commands. In this case try the following in order:
+commands. If CLASSES is non-nil, it must be a prefix class or a list
+of such classes. When this function is called from the body or the
+`interactive' form of a suffix command, PREFIXES and/or CLASSES should
+be non-nil. If either is non-nil, try the following in order:
- If the current suffix command was invoked from a prefix, which
- appears in PREFIXES, then return the scope of that prefix.
+ appears in PREFIXES, return the scope of that prefix.
+
+- If the current suffix command was invoked from a prefix, and its
+ class derives from one of the CLASSES, return the scope of that
+ prefix.
+
+- If a prefix is being setup and it appears in PREFIXES, return its
+ scope.
-- If a prefix is being setup and it appears in PREFIXES, then return
- its scope.
+- If a prefix is being setup and its class derives from one of the
+ CLASSES, return its scope.
-- Finally try to return the default scope of the first prefix in
+- Finally try to return the default scope of the first command in
PREFIXES. This only works if that slot is set in the respective
class definition or using its `transient-init-scope' method.
If no prefix matches, return nil."
- (if prefixes
- (let ((prefixes (ensure-list prefixes)))
- (if-let* ((obj (or (and-let* ((obj transient-current-prefix))
- (and (memq (oref obj command) prefixes) obj))
- (and-let* ((obj transient--prefix))
- (and (memq (oref obj command) prefixes) obj)))))
+ (if (or prefixes classes)
+ (let ((prefixes (ensure-list prefixes))
+ (type (if (symbolp classes) classes (cons 'or classes))))
+ (if-let ((obj (cl-flet ((match (obj)
+ (and obj
+ (or (memq (oref obj command) prefixes)
+ (cl-typep obj type))
+ obj)))
+ (or (match transient-current-prefix)
+ (match transient--prefix)))))
(oref obj scope)
(and (get (car prefixes) 'transient--prefix)
(oref (transient--init-prefix (car prefixes)) scope))))
If the result is nil, then use \"(BUG: no description)\" as the
description. If the OBJ's `key' is currently unreachable, then
apply the face `transient-unreachable' to the complete string."
- (let ((desc (if-let* ((transient--docsp)
- (cmd (oref obj command))
- (doc (ignore-errors (documentation cmd)))
- ((not (equal doc (documentation
- 'transient--default-infix-command)))))
- (substring doc 0 (string-match "\\.?\n" doc))
- (or (cl-call-next-method obj)
- (and (slot-boundp transient--prefix 'suffix-description)
- (funcall (oref transient--prefix suffix-description)
- obj))))))
+ (let ((desc (or (cl-call-next-method obj)
+ (and (slot-boundp transient--prefix 'suffix-description)
+ (funcall (oref transient--prefix suffix-description)
+ obj)))))
+ (when-let* ((transient--docsp)
+ (cmd (oref obj command))
+ ((not (memq 'transient--default-infix-command
+ (function-alias-p cmd))))
+ (docstr (ignore-errors (documentation cmd)))
+ (docstr (string-trim
+ (substring docstr 0 (string-match "\\.?\n" docstr))))
+ ((not (equal docstr ""))))
+ (setq desc (format-spec transient-show-docstring-format
+ `((?c . ,desc)
+ (?s . ,docstr)))))
(if desc
(when-let* ((face (transient--get-face obj 'face)))
(setq desc (transient--add-face desc face t)))
(insert "\n"))
(when transient--helpp
(insert
- (format (propertize "\
+ (format
+ (propertize "\
Type a %s to show help for that suffix command, or %s to show manual.
Type %s to exit help.\n"
- 'face 'transient-heading)
- (propertize "<KEY>" 'face 'transient-key)
- (propertize "?" 'face 'transient-key)
- (propertize "C-g" 'face 'transient-key))))
+ 'face 'transient-heading)
+ (propertize "<KEY>" 'face 'transient-key)
+ (propertize "?" 'face 'transient-key)
+ (propertize "C-g" 'face 'transient-key))))
(when transient--editp
(unless transient--helpp
(insert
- (format (propertize "\
-Type a %s to set level for that suffix command.
-Type %s to set what levels are available for this prefix command.\n"
- 'face 'transient-heading)
- (propertize "<KEY>" 'face 'transient-key)
- (propertize "C-x l" 'face 'transient-key))))
+ (format
+ (propertize "\
+Type %s and then %s to put the respective suffix command on level %s.
+Type %s and then %s to display suffixes up to level %s in this menu.
+Type %s and then %s to describe the respective suffix command.\n"
+ 'face 'transient-heading)
+ (propertize "<KEY>" 'face 'transient-key)
+ (propertize "<N>" 'face 'transient-key)
+ (propertize " N " 'face 'transient-enabled-suffix)
+ (propertize "C-x l" 'face 'transient-key)
+ (propertize "<N>" 'face 'transient-key)
+ (propertize " N " 'face 'transient-enabled-suffix)
+ (propertize "C-h" 'face 'transient-key)
+ (propertize "<KEY>" 'face 'transient-key))))
(with-slots (level) transient--prefix
(insert
- (format (propertize "
-Suffixes on levels %s are available.
-Suffixes on levels %s and %s are unavailable.\n"
- 'face 'transient-heading)
- (propertize (format "1-%s" level)
- 'face 'transient-enabled-suffix)
- (propertize " 0 "
- 'face 'transient-disabled-suffix)
- (propertize (format ">=%s" (1+ level))
- 'face 'transient-disabled-suffix))))))
+ (format
+ (propertize "
+The current level of this menu is %s, so
+ commands on levels %s are displayed, and
+ commands on levels %s and %s are not displayed.\n"
+ 'face 'transient-heading)
+ (propertize (format " %s " level) 'face 'transient-enabled-suffix)
+ (propertize (format " 1..%s " level) 'face 'transient-enabled-suffix)
+ (propertize (format " >= %s " (1+ level))
+ 'face 'transient-disabled-suffix)
+ (propertize " 0 " 'face 'transient-disabled-suffix))))))
(cl-defgeneric transient-show-summary (obj &optional return)
"Show brief summary about the command at point in the echo area.