~miguelbernadi

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.


  1. Commit including transient into the Emacs master branch April 20th 2021. To be released with Emacs 29.1. ↩︎

COMMENTS

Have a comment on this article? Start a discussion in my public inbox by sending an email to ~miguelbernadi/public-inbox@lists.sr.ht [mailing list etiquette] , or see existing discussions.