Using transient for project menu
Project management is a basic necessity of developing software (and things that are not software). As I use Emacs throughout the development cycle I also need to control projects from there. I’m sharing here a transient I wrote to help me learn to write transients and to use these better.
Starting to use projectile #
When I started using Emacs (back in 2016) I looked for recommendations on packages and add-ons to set up my development environment. At the time, projectile was the tool to use according to word of mouth. It is very complete and provides many integrations for a thorough project management experience: finding projects, searching for files or strings, executing operations over the whole project…
I started using it but I did not leverage a lot of its functionality as I still used the CLI a lot for most project operations. That became more and more usual as we started using Docker at work and my job revolved around a set of containers from different projects instead of a single one, or using technologies that could only be tested on the server (looking at you Jenkinsfiles). Adjusting projectile to cover those use cases could have been possible but far from my level of expertise.
Writing a hydra
to help with projectile
#
To entice me to learn more about how to use it and do so more
effectively (for the few projects were I could do so) I wrote a hydra
which served me for a very long time. It is thanks to this hydra
that I knew how little of projectile I ever used.
A hydra
is a contextual menu that provides you a command to invoke
the menu (you can bind it to a key-stroke) and you can invoke any of
the options with a key-press. You can do so repeatedly, or the menu
can exit after an action. Here is a sweet example from it’s README (I
keep a minimally modified copy as example):
(defhydra hydra-zoom (global-map "<f2>")
"zoom"
("g" text-scale-increase "in")
("l" text-scale-decrease "out"))
The hydra
for projectile was quite big and had multiple levels, to
separate action contexts, with many options that I do not use but I
could maybe use one day.
Replacing hydra
with transient
?
#
While I loved hydra
on first sight and adopted it immediately, one
of my issues with it has always been that there are very few publicly
shared hydras
. There are many packages and workflows where a hydra
makes sense, but you have to define them yourself.
Another example of great contextual menus come from the magit package,
whose solution was deeply embedded and not reusable. Back in 2019,
Jonas Bernoulli (the magit
maintainer)
released a reimplementation of magit
’s menus as an independent
package he called transient
.
Transients
are quite similar to hydras, but allow many more options
and complexity, as they are intended to cover all git command
flags. Documentation and adoption of transient
is not much better
than hydra
at the moment so there is not a good argument in favor of
either. Nonetheless, I decided to try writing a transient
.
My projectile transient menu #
I Looked up documentation for transient
and there is an incomplete
wiki page where someone tried to compile a Developer Quick Start Guide
on how to use it. It is quite sparse, but enough for simple-ish
examples, like the one below:
(transient-define-prefix mbernabe/transient-project-root ()
"projectile"
[["Project Management"
("p" "find project" projectile-switch-project)
("q" "find open project" projectile-switch-open-project)
("H" "find dirty projects" projectile-browse-dirty-projects)
("F" "find-file in projects" projectile-find-file-in-known-projects)
]
[
"Project operations"
("D" "dired" projectile-dired)
("E" "edit .dir-locals" projectile-edit-dir-locals)
("v" "vc" projectile-vc)
]
[
"Buffer management"
("G" "tags" projectile-regenerate-tags)
("I" "ibuffer" projectile-ibuffer)
]
[
"Buffer operations"
("S" "save buffers" projectile-save-project-buffers)
("k" "kill-buffers" projectile-kill-buffers)
]]
[
[
"Actions"
("c" "configure" projectile-configure-project)
("t" "test" projectile-test-project)
("b" "build" projectile-compile-project)
("r" "run" projectile-run-project)
("!" "command at root" projectile-run-shell-command-in-root)
("&" "async command at root" projectile-run-async-shell-command-in-root)
]
[
"Shells"
("e" "eshell" projectile-run-eshell)
("s" "shell" projectile-run-shell)
]
[
"Navigation"
("f" "find-file" projectile-find-file)
("T" "find-test-file" projectile-find-test-file)
("d" "find-dir" projectile-find-dir)
("b" "find buffer" projectile-switch-to-buffer)
("i" "toggle impl-test" projectile-toggle-between-implementation-and-test)
]
["search file contents"
("o" "multi-occur" projectile-multi-occur)
("g" "rg" projectile-ripgrep)
("R" "replace" projectile-replace)
("j" "find-tag" projectile-find-tag)
]])
This covers less options than my previous hydra
did, but removes
those I’m sure I won’t use and still keeps many options. Another
benefit is that it is a single page now as I can group options
contextually, so all options are visible together and it’s easier to
remember that I had that other option too.
What’s next? #
There are two major topics I’m tracking for the future. The first one
is that transient
was suggested for inclusion in Emacs itself. There
are some matters of missing documentation blocking the move, but it
could be possible for Emacs 29 to ship with transient
embedded1. This would provide a
contextual menu tool out of the box and be much more likely to be
adopted by package authors.
The other is that nowadays Emacs ships with project.el
, an embedded
project management package integrated with all the typical Emacs
facilities. It is not as feature complete as projectile
, of course,
but for my basic needs it’s likely to be enough, and that’s an
external package less to track, update and integrate. The issue, as
before, is that I do not know how it works and have to learn from it.
You may have already noticed that operations in the transient
are
labeled quite generically instead of using projectile specifics. That
is partly to ease changing from projectile
to project.el
without
changing the interface.
Right now my priority is to update my muscle memory on the few options
I used, and get some projects where I can practice the other options
(there’s some hope in the horizon for it). Some day I’ll go over
project.el
documentation and try to migrate some of the
functionality over to it. Meanwhile, I may create some more
transient
menus for other workflows.
-
Commit including transient into the Emacs master branch April 20th 2021. To be released with Emacs 29.1. ↩︎