-
-
Notifications
You must be signed in to change notification settings - Fork 66
Developer Quick Start Guide
This guide assumes you have minimal knowledge of Emacs, some programming
experience in lisp and non-lisp languages, and have at least seen screenshots
of magit
.
- There are some useful links in issue #51.
- Issues with the `faq` label are also useful.
Transient gets it’s name from the non-persistent keymap and the popup UI for displaying that keymap. It is a transient, ie temporary UI. The most basic user flow looks like this:
Prefix -> Suffix
- The prefix is the command you invoke first, such as
magit-dispatch
- A suffix is a bound command displayed in the UI, such as
magit-stage
The entire ensemble (UI & keymap) is frequently referred to as “a transient”. “Prefix” and “a transient” are almost the same thing. Invoking a prefix will show a transient.
A prefix can be bound as a suffix. This is what happens when, in the
magit-dispatch
transient, you select l
for magit-log
. When calling
nested transients, the sentences look like this:
Prefix -> Suffix (Prefix) -> Suffix (Prefix) -> Suffix
Some suffixes are designed to hold state, toggling or storing an argument. Infixes are suffixes made for this purpose. A full command sentence may look like this:
Prifix -> Infix -> Infix -> Suffix
- Prefixes display the pop-up UI and bind the keymap.
- Suffixes are bound within a prefix
- Infixes are a specialized suffix with easy support in the API for storing state
- A Suffix may be a new Prefix, in which case the transient is nested
Emacs lisp ships with eieio, a close cousin to the Common Lisp Object System. It’s OOP. There are classes & subclasses. You can inherit into new classes and override methods to customize behaviors.
When defining a transient prefix or suffix, you will see a lot of :property
value
pairs. This is not a syntax. It’s just vanilla lists. Macros like
define-transient-prefix
will read these values into a list and use them to
create objects with those properties.
You can use eieio API’s to explore transient objects. Let’s look at some transients you have already:
;; 'transient--prefix object is stored in symbol properties
(setq prefix-object (plist-get (symbol-plist 'magit-log) 'transient--prefix))
;; get the class of the object
(setq prefix-class (eieio-object-class prefix-object))
;; get the slots for that class, returns a list of structs
(eieio-class-slots prefix-class)
;; print some basic information about slots & methods
(eieio-help-class prefix-class)
(transient-define-prefix transient-toys-hello ()
"Say hello"
[("h" "hello" (lambda () (interactive) (message "hello")))])
(transient-toys-hello)
Just execute-extended-command
(M-x) and select transient-toys-hello
to
view this transient. All transients are commands and all of the actions are
also commands.
Any command can be used. It’s a good reason to use private--namespace
style names for suffix actions since these commands don’t show up in (M-x) by
default.
(defun transient-toys--wave ()
"Wave at the user"
(interactive)
(message (propertize
(format "Waves at %s" (current-time-string))
'face 'success)))
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[("w" "wave" transient-toys--wave)])
(transient-toys-wave)
There are compact forms and explicit forms. Not all behaviors have a compact
form, so as you use more behaviors, you will see more of the property style
API. Here we use the :transient
property, set to true so that the
transient remains …transient instead of exiting.
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[("w" "wave" transient-toys--wave :transient t)])
(transient-toys-wave)
Launch the command, wave several times (note timestamp update) and then exit with (C-g).
The transient-define-suffix
macro can help if you need to bind a command in
multiple places and only override some properties for some prefixes. It
makes the transient definition more compact at the expense of a more verbose
command.
(transient-define-suffix transient-toys--wave ()
"Wave at the user"
:transient t
:key "C-w"
:description "wave"
(interactive)
(message (propertize
(format "Waves at %s" (current-time-string))
'face 'success)))
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[(transient-toys--wave)])
(transient-toys-wave)
Even if you define a property via one of the macros, you can still override that property in the later transient definition. This includes both the compact forms and property API’s.
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[("fw" "Furiously wave" transient-toys--wave :transient nil)])
(transient-toys-wave)
There is basic layout support and you can use it to collect or differentiate
commands. To define a transient, you need at least one group. Groups are
vectors, delimited as [ ...group... ]
.
If you begin a group vector with a string, you get a group heading. Groups also support some properties.
There is no transient-define-group
at this time.
The default behavior treats groups a little differently depending on how they are nested. For most simple groupings, this is sufficient control.
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[("wo" "wave one" transient-toys--wave)]
[("wt" "wave two" transient-toys--wave)])
(transient-toys-wave)
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[[("wo" "wave one" transient-toys--wave)]
[("wt" "wave two" transient-toys--wave)]])
(transient-toys-wave)
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
[("wo" "wave one" transient-toys--wave)]
[[("wt" "wave two" transient-toys--wave)]
[("wa" "wave all" transient-toys--wave)]])
(transient-toys-wave)
Very straightforward. Just make the first element in the vector a string or
add a :description
property, which can be a function.
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
["Group One"
("wo" "wave one" transient-toys--wave)
("we" "wave emotionally" transient-toys--wave)]
["Group Two"
("ws" "wave some" transient-toys--wave)
("wb" "wave better" transient-toys--wave)]
[["Group Three" ("wt" "wave two" transient-toys--wave)]
["Group Four" ("wa" "wave all" transient-toys--wave)]])
(transient-toys-wave)
Note, the property API for dynamic descriptions is the same for both prefixes and suffixes.
(require 'cl-lib)
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
["Group One"
("wo" "wave one" transient-toys--wave)
("we" "wave emotionally" transient-toys--wave)]
[:description current-time-string
("ws" "wave some" transient-toys--wave)
("wb" "wave better" transient-toys--wave)]
[[:description (lambda () (format "Group %s" (cl-gensym)))
("wt" "wave two" transient-toys--wave)]
[:description (lambda () (format "Group %s" (cl-gensym)))
("wa" "wave all" transient-toys--wave)]])
(transient-toys-wave)
Functions need arguments. Infixes are specialized suffixes with behavior defaults that make sense for setting values. They have interfaces for persisting state across invocations.
The compact style for infixes is heavily influenced by the CLI style switches
and arguments that transient was built to control. The ”" character at the
end of the long option (
:argument=) results in an argument with a value
instead of a boolean switch.
(transient-define-prefix transient-toys-wave ()
"Wave at the user"
["Arguments"
("-s" "switch" "--switch")
("-a" "argument" "--argument=")
("t" "toggle" "--toggle")
("v" "value" "--value=")]
["Commands"
("ws" "wave some" transient-toys--wave)
("wb" "wave better" transient-toys--wave)])
(transient-toys-wave)
If you need to fine-tune a switch, use transient-define-infix
. Likewise,
use transient-define-argument
for fine-tuning an argument. The class
definitions can be used as a reference while the
[[https://magit.vc/manual/transient/Suffix-Slots.html#Slots-of-transient_002dinfix][manual]
provides more explanation.
In the example below, :init-value
is defined to randomly set the switch to
true. Every time you invoke the transient-toys-wave
prefix, there’s a
chance that the --switch
will already be set.
; there is some built-in class introspection you might want to play with
; (eieio-help-class transient-infix)
(transient-define-infix transient-toys--switch ()
"Switch on and off"
:argument "--switch"
:shortarg "-s" ; will be used for :key when key is not set
:description "switch"
; if you haven't seen setf, think of it as having the power to set via a getter
:init-value (lambda (ob)
(setf
(slot-value ob 'value) ; get value
(eq 0 (random 2))))) ; write t with 50% probability
(transient-define-prefix transient-toys-args ()
"Wave at the user"
["Arguments"
(transient-toys--switch)])
(transient-toys-args)
Choices can be set for an argument. The property API and
transient-define-argument
are equavalent for configuring choices. You can
either hardcode or generate choices.
(transient-define-argument transient-toys--animals-argument ()
"Animal picker"
:argument "--animal="
:shortarg "-a"
:description "Animals"
; :multi-value t ; multi-value can be set to --animals=fox,otter,kitten etc
:class 'transient-option
:choices '("fox" "kitten" "perigrine" "otter"))
(transient-define-prefix transient-toys-animals ()
"Select animal"
["Arguments"
(transient-toys--animals-argument)])
(transient-toys-animals)
The compact form of choices can be used for a compact argument. Use :class
'transient-option
if you need to force the class.
(transient-define-prefix transient-toys-animals ()
"Select animal"
["Arguments"
("-a" "Animal" "--animal=" :choices ("fox" "kitten" "perigrine" "otter"))])
(transient-toys-animals)
(defun transient-toys--animal-choices (a b c)
(if (eq 0 (random 2))
'("fox" "kitten" "otter")
'("ant" "perigrine" "zebra")))
(transient-define-prefix transient-toys-animals ()
"Select animal"
["Arguments"
("-a" "Animal" "--animal="
:always-read t ; don't allow unsetting, just read a new value
:choices transient-toys--animal-choices)])
(transient-toys-animals)