Chronometrist development diary

↩ blog

← Previous: Star Wars, and non-violent communication → Next: Literate Programming

<2021-01-12T20:55:20+0530>

It's one kind of pain to have a developer shoot down your request for a feature you really, really needed, by saying, "Sorry, it'd make things quite complicated" and "we have to wait until <external factor> is resolved"…

…it's another kind of pain when the one making the feature request and the developer are the same person ¯\_(ツ)_/¯

The status quo

Chronometrist is a time tracker for the GNU Emacs text editor/operating system. I made it to understand how I spend my time. Developing it keeps me surprisingly busy, probably because it is something I use every day.

One of the driving forces behind the development of Chronometrist was the ability to attach arbitrary data to time intervals.

Currently, tags and key-values can be added, using functions which use completing-read for input. Using completing-read means that these automatically adapt to the user's configuration, and can use various completing-read backends, e.g. standard Emacs completion, ido, ivy, or helm.

The functions which prompt for tags and key-values can be added to one or more of the four hooks, so the user can choose at what point they want to be prompted for tags/key-values - before clocking in, after clocking in, before clocking out, or after clocking out.

However, the completing-read-based interface takes more keystrokes than I would like - most of the time I'm reusing existing tags/keys/values, rather than inserting new values - and so I thought of using the popular Hydra library to select recently-used tags/keys/values with a single key.

Hail Hydra

The first hurdle was creating hydras programmatically, with hydra heads (actions the user can take) being defined at runtime - one head per suggested combination of tags, with numbers 0-9 selecting the suggestion. 1 With some macrology (and help from wasamasa and mplsCorwin), I got that out of the way.

It was shaping up nicely, so I figured I'd add one of the functions (which call the macro I wrote to generate the hydras, and then launch the body of the generated hydra) to one of the aforementioned hooks and start testing it. It was here that I discovered that hydras do not "block" - the hydra would be displayed, the function calling it would return, and execution would continue into the next function in the hook, without waiting for user input to the hydra.

Bummer. All that work… :\

Approach 1: Make it work, no matter what

I got some hope from dale's suggestion that I try separating the hook's contents into what comes before the hydra and what comes after it, modify the hook to remove the post-hydra part before calling the hydra body, and resume the post-hydra functions in the :post head argument to the hydra.

This could work - indeed, I started working on it…before quickly realizing that I have at least four potential hooks the hydra could be in. Figuring out which hook the hydra is being run from is a pain in itself. Moreover, it is not general - the hydra would only run correctly in these four hooks. In theory, I could try detecting how the hydra is called (wasamasa suggested looking at backtrace-frame), but that approach seems like an ugly complication to add :(

"Complication"…reminds me of Alan Kay's criticism of "incremental problem solving", and that tangled mass of cables. Nooo QQ

Approach 2: The end of elegance

Thanks to the hook-based architecture, the tags and key-value functionality is currently an optional extension. 2 I could make this package a minor mode instead, and hardcode the hydra into Chronometrist 3, calling the hydra body if hydra.el is loaded and the key-value minor mode is enabled.

Approach 3: The NIH is strong with this one

I could write an ad hoc hydra-like prompt. Actually, I already have, making use of read-key-sequence. It works in hooks, and is smaller than I expected. All that's left is to get it to read universal arguments. It doesn't look as pretty as a hydra, but it Works™.

Approach 4: Modify defhydra/wait for upstream

I opened an issue about this on the Hydra GitHub. Let's see what the response is. I don't feel so hopeful about this one.

Approach 5: Transient investigation

Transient always impressed me as a user, and scared me as a fledgling Elisp developer. The documentation has been worked on since I last saw it…I could try looking into it again.

Footnotes:

1

in the uncommon case that I want something else, I foresaw pressing "o" for "other" options, or entering a number with a universal argument, which would edit the selected combination. These revert to the current completing-read interface.

2

just not packaged separately yet

3

possibly providing a -function variable, so users can choose the completing-read interface as the default, if they want.