Skip to content

Commit

Permalink
manual: Move comparisons to wiki
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsius committed Oct 28, 2023
1 parent ef02586 commit 02607dd
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 633 deletions.
321 changes: 14 additions & 307 deletions docs/transient.org
Original file line number Diff line number Diff line change
Expand Up @@ -1961,313 +1961,6 @@ and 7. ~level~ controls whether a suffix or a group should be
available depending on user preference.
See [[*Enabling and Disabling Suffixes]].

* Related Abstractions and Packages
** Comparison With Prefix Keys and Prefix Arguments

While transient commands were inspired by regular prefix keys and
prefix arguments, they are also quite different and much more complex.

The following diagrams illustrate some of the differences.

- =(c)= represents a return to the command loop.
- =(+)= represents the user's choice to press one key or another.
- ={WORD}= are possible behaviors.
- ={NUMBER}= is a footnote.

*** Regular Prefix Commands
:PROPERTIES:
:UNNUMBERED: notoc
:END:

See [[info:elisp#Prefix Keys]].

#+begin_example
,--> command1 --> (c)
|
(c)-(+)-> prefix command or key --+--> command2 --> (c)
|
`--> command3 --> (c)
#+end_example

*** Regular Prefix Arguments
:PROPERTIES:
:UNNUMBERED: notoc
:END:

See [[info:elisp#Prefix Command Arguments]].

#+begin_example
,----------------------------------,
| |
v |
(c)-(+)---> prefix argument command --(c)-(+)-> any command --> (c)
| ^ |
| | |
`-- sets or changes --, ,-- maybe used --' |
| | |
v | |
prefix argument state |
^ |
| |
`-------- discards --------'
#+end_example

*** Transients
:PROPERTIES:
:UNNUMBERED: notoc
:END:

(∩`-´)⊃━☆゚.*・。゚

This diagram ignores the infix value and external state:

#+begin_example
(c)
| ,- {stay} ------<-,-<------------<-,-<---,
(+) | | | |
| | | | |
| | ,--> infix1 --| | |
| | | | | |
| | |--> infix2 --| | |
v v | | | |
prefix -(c)-(+)-> infix3 --' ^ |
| | |
|---------------> suffix1 -->--| |
| | |
|---------------> suffix2 ----{1}------> {exit} --> (c)
| |
|---------------> suffix3 -------------> {exit} --> (c)
| |
`--> any command --{2}-> {warn} -->--|
| |
|--> {noop} -->--|
| |
|--> {call} -->--'
|
`------------------> {exit} --> (c)
#+end_example

This diagram takes the infix value into account to an extend, while
still ignoring external state:

#+begin_example
(c)
| ,- {stay} ------<-,-<------------<-,-<---,
(+) | | | |
| | | | |
| | ,--> infix1 --| | |
| | | | | | |
| | ,--> infix2 --| | |
v v | | | | |
prefix -(c)-(+)-> infix3 --' | |
| | ^ |
| | | |
|---------------> suffix1 -->--| |
| | ^ | |
| | | | |
|---------------> suffix2 ----{1}------> {exit} --> (c)
| | ^ | |
| | | | v
| | | | |
|---------------> suffix3 -------------> {exit} --> (c)
| | ^ | |
| sets | | v
| | maybe | |
| | used | |
| | | | |
| | infix --' | |
| `---> value | |
| ^ | |
| | | |
| hides | |
| | | |
| `--------------------------<---|
| | |
`--> any command --{2}-> {warn} -->--| |
| | |
|--> {noop} -->--| |
| | |
|--> {call} -->--' ^
| |
`------------------> {exit} --> (c)
#+end_example

This diagram provides more information about the infix value
and also takes external state into account.

#+begin_example
,----sets--- "anything"
|
v
,---------> external
| state
| | |
| initialized | ☉‿⚆
sets from |
| | maybe
| ,----------' used
| | |
(c) | | v
| ,- {stay} --|---<-,-<------|-----<-,-<---,
(+) | | | | | | |
| | | v | | | |
| | ,--> infix1 --| | | |
| | | | | | | | |
| | | | v | | | |
| | ,--> infix2 --| | | |
| | | | ^ | | | |
v v | | | | | | |
prefix -(c)-(+)-> infix3 --' | | |
| | ^ | ^ |
| | | v | |
|---------------> suffix1 -->--| |
| | | ^ | | |
| | | | v | |
|---------------> suffix2 ----{1}------> {exit} --> (c)
| | | ^ | | |
| | | | | | v
| | | | v | |
|---------------> suffix3 -------------> {exit} --> (c)
| | | ^ | |
| sets | | | v
| | initialized maybe | |
| | from used | |
| | | | | |
| | `-- infix ---' | |
| `---> value -----------------------------> persistent
| ^ ^ | | across
| | | | | invocations -,
| hides | | | |
| | `----------------------------------------------'
| | | |
| `--------------------------<---|
| | |
`--> any command --{2}-> {warn} -->--| |
| | |
|--> {noop} -->--| |
| | |
|--> {call} -->--' ^
| |
`------------------> {exit} --> (c)
#+end_example

- ={1}= Transients can be configured to be exited when a suffix command
is invoked. The default is to do so for all suffixes except for
those that are common to all transients and which are used to
perform tasks such as providing help and saving the value of the
infix arguments for future invocations. The behavior can also be
specified for individual suffix commands and may even depend on
state.

- ={2}= Transients can be configured to allow the user to invoke
non-suffix commands. The default is to not allow that and instead
warn the user.

Despite already being rather complex, even the last diagram leaves out
many details. Most importantly it implies that the decision whether
to remain transient is made later than it actually is made (for the
most part a function on ~pre-command-hook~ is responsible). But such
implementation details are of little relevance to users and are
covered elsewhere.

** Comparison With Other Packages
*** Magit-Popup
:PROPERTIES:
:UNNUMBERED: notoc
:END:

Transient is the successor to Magit-Popup (see [[info:magit-popup]]).

One major difference between these two implementations of the same
ideas is that while Transient uses transient keymaps and embraces the
command-loop, Magit-Popup implemented an inferior mechanism that does
not use transient keymaps and that instead of using the command-loop
implements a naive alternative based on ~read-char~.

Magit-Popup does not use classes and generic functions and defining a
new command type is near impossible as it involves adding hard-coded
special-cases to many functions. Because of that only a single new
type was added, which was not already part of Magit-Popup's initial
release.

A lot of things are hard-coded in Magit-Popup. One random example is
that the key bindings for switches must begin with ~-~ and those for
options must begin with ~=~.

*** Hydra
:PROPERTIES:
:UNNUMBERED: notoc
:END:

Hydra (see https://github.com/abo-abo/hydra) is another package that
provides features similar to those of Transient.

Both packages use transient keymaps to make a set of commands
temporarily available and show the available commands in a popup
buffer.

A Hydra “body” is equivalent to a Transient “prefix” and a Hydra
“head” is equivalent to a Transient “suffix”. Hydra has no equivalent
of a Transient “infix”.

Both hydras and transients can be used as simple command dispatchers.
Used like this they are similar to regular prefix commands and prefix
keys, except that the available commands are shown in the popup buffer.

(Another package that does this is ~which-key~. It does so automatically
for any incomplete key sequence. The advantage of that approach is
that no additional work is necessary; the disadvantage is that the
available commands are not organized semantically.)

Both Hydra and Transient provide features that go beyond simple
command dispatchers:

- Invoking a command from a hydra does not necessarily exit the hydra.
That makes it possible to invoke the same command again, but using a
shorter key sequence (i.e., the key that was used to enter the hydra
does not have to be pressed again).

Transient supports that too, but for now this feature is not a focus
and the interface is a bit more complicated. A very basic example
using the current interface:

#+BEGIN_SRC emacs-lisp
(transient-define-prefix outline-navigate ()
:transient-suffix 'transient--do-stay
:transient-non-suffix 'transient--do-warn
[("p" "previous visible heading" outline-previous-visible-heading)
("n" "next visible heading" outline-next-visible-heading)])
#+END_SRC

- Transient supports infix arguments; values that are set by infix
commands and then consumed by the invoked suffix command(s).

To my knowledge, Hydra does not support that.

Both packages make it possible to specify how exactly the available
commands are outlined:

- With Hydra this is often done using an explicit format string, which
gives authors a lot of flexibility and makes it possible to do fancy
things.

The downside of this is that it becomes harder for a user to add
additional commands to an existing hydra and to change key bindings.

- Transient allows the author of a transient to organize the commands
into groups and the use of generic functions allows authors of
transients to control exactly how a certain command type is
displayed.

However while Transient supports giving sections a heading it does
not currently support giving the displayed information more
structure by, for example, using box-drawing characters.

That could be implemented by defining a new group class, which lets
the author specify a format string. It should be possible to
implement that without modifying any existing code, but it does not
currently exist.

* FAQ
:PROPERTIES:
:APPENDIX: t
Expand Down Expand Up @@ -2296,6 +1989,20 @@ you will be able to yank it another buffer.
#'transient--do-stay)
#+end_src

** How does Transient compare to prefix keys and universal arguments?
:PROPERTIES:
:UNNUMBERED: notoc
:END:

See https://github.com/magit/transient/wiki/Comparison-with-prefix-keys-and-universal-arguments.

** How does Transient compare to Magit-Popup and Hydra?
:PROPERTIES:
:UNNUMBERED: notoc
:END:

See https://github.com/magit/transient/wiki/Comparison-with-other-packages.

** Why did some of the key bindings change?
:PROPERTIES:
:UNNUMBERED: notoc
Expand Down
Loading

0 comments on commit 02607dd

Please sign in to comment.