Skip to content

Commit

Permalink
Support display-only information in place of a suffix
Browse files Browse the repository at this point in the history
Group headings are also display-only information and raw strings
can be used anywhere a suffix can appear, but this class makes it
possible to display arbitrary information anywhere *and* to style
it in any way one fancies.

By inheriting from `transient-suffix' (instead of `transient-child')
we avoid having to implement many new methods, but semantically this
is a bit questionable.  Leaving the `command' and `key' slots
undefined, means that we have to use `slot-boundp' in a few places,
but on the other hand that helps dealing the semantic inconsistency.

Closes #226.
  • Loading branch information
tarsius committed Oct 24, 2023
1 parent ae5aba3 commit 4f67a03
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 11 deletions.
17 changes: 17 additions & 0 deletions docs/transient.org
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,23 @@ object should not affect later invocations.
- Classes used for infix commands that represent variables should
derived from the abstract ~transient-variable~ class.

- The ~transient-information~ class is special in that suffixes that use
this class are not associated with a command and thus also not with
any key binding. Such suffixes are only used to display arbitrary
information, and that anywhere a suffix can appear. Display-only
suffix specifications take this form:

#+begin_src emacs-lisp
([LEVEL] :info DESCRIPTION [KEYWORD VALUE]...)
#+end_src

The ~:info~ keyword argument replaces the ~:description~ keyword used for
other suffix classes. Other keyword arguments that you might want to
set, include ~:face~, predicate keywords (such as ~:if~), and ~:format~.
By default the value of ~:format~ includes ~%k~, which for this class is
replaced with the empty string or spaces, if keys are being padded in
the containing group.

Magit defines additional classes, which can serve as examples for the
fancy things you can do without modifying Transient. Some of these
classes will likely get generalized and added to Transient. For now
Expand Down
18 changes: 18 additions & 0 deletions docs/transient.texi
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,24 @@ indicates that all remaining arguments are files.
@item
Classes used for infix commands that represent variables should
derived from the abstract @code{transient-variable} class.

@item
The @code{transient-information} class is special in that suffixes that use
this class are not associated with a command and thus also not with
any key binding. Such suffixes are only used to display arbitrary
information, and that anywhere a suffix can appear. Display-only
suffix specifications take this form:

@lisp
([LEVEL] :info DESCRIPTION [KEYWORD VALUE]...)
@end lisp

The @code{:info} keyword argument replaces the @code{:description} keyword used for
other suffix classes. Other keyword arguments that you might want to
set, include @code{:face}, predicate keywords (such as @code{:if}), and @code{:format}.
By default the value of @code{:format} includes @code{%k}, which for this class is
replaced with the empty string or spaces, if keys are being padded in
the containing group.
@end itemize

Magit defines additional classes, which can serve as examples for the
Expand Down
37 changes: 26 additions & 11 deletions lisp/transient.el
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,11 @@ 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"))
"Display-only information.
A suffix object with no associated command.")

(defclass transient-infix (transient-suffix)
((transient :initform t)
(argument :initarg :argument)
Expand Down Expand Up @@ -1071,8 +1076,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)
Expand Down Expand Up @@ -1128,6 +1134,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)
Expand Down Expand Up @@ -1871,6 +1880,7 @@ 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))
((cl-typep def 'transient-group)
(cl-mapcan #'s (oref def suffixes)))
Expand Down Expand Up @@ -1908,12 +1918,15 @@ value. Otherwise return CHILDREN as is."
(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 (get cmd 'transient--suffix))))
(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
Expand All @@ -1922,7 +1935,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)
Expand Down Expand Up @@ -3548,8 +3562,8 @@ Optional support for popup buttons is also implemented here."

(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
Expand Down Expand Up @@ -3642,7 +3656,8 @@ 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)
(cond ((and (slot-boundp obj 'key)
(transient--key-unreachable-p obj))
(propertize desc 'face 'transient-unreachable))
((and transient-highlight-higher-levels
(> (max (oref obj level) transient--max-group-level)
Expand Down

0 comments on commit 4f67a03

Please sign in to comment.