annotations and sorting for activities-completing-read
(updated) This provides custom annotation and sorting functions for activities-completing-read. It shows the number of buffers (and files), for the last or default state, a magit-style color-coded activity age, and flags for being active or having a modified set of buffers.
This is shown below with vertico but it should support any UI which handles annotation-functions (most do). For theme friendliness, it creates no new faces, instead ~borrowing the vc-annotate color palette~ building its own color palette (along with success and warning faces). Happy to take any input/ideas.
BTW, are there other pieces of information inside an activity that would be relevant to show that occur to you?
BTW, are there other pieces of information inside an activity that would be relevant to show that occur to you?
I can't think of any. For myself, the only one that matters is last-used time, anyway. Let's keep this as simple as possible.
I could show both default and last ages, but that might be confusing.
Along that line, do you think having the default and last buf+file cnt is useful or TMI? Another approach would be to use some indicator (like *) that the state is "dirty" in the sense that its display buffer list has changed from the default. So buffer cnt + file cnt + dirty flag + last mod age.
I could show both default and last ages, but that might be confusing.
I don't think that's necessary. This is just for completing-read, not for "management."
Along that line, do you think having the default and last buf+file cnt is useful or TMI? Another approach would be to use some indicator (like
*) that the state is "dirty" in the sense that its display buffer list has changed from the default. So buffer cnt + file cnt + dirty flag + last mod age.
For me, it's TMI. I select activities by name, not by a number of how many windows or files are visible in it. I can't think of a situation where I would need to know that.
However, knowing whether an activity has a last-used state could be useful, I think.
For me, it's TMI.
I do think I agree. I like having the last buf/file count, since I use multiple "similar" activities, some richer than others (based on screen size). But for the default counts, I'm just comparing to see if they changed at all (i.e. computing a "dirty" flag in my head). For such a flag, what would you recommend? I was thinking "same number of buffers and same buffer names = not dirty".
Update, like this:
I'm not sure what you mean by "dirty." For me what would be interesting is whether the activity has a last-used state or just the default.
Sorry, I'm not sure I understand your idea. If you have activity auto-saving on (which is the default), an activity will pretty much always have a "last" state, even if it's only lightly modified from the default (different window points, sizes, etc.). So my idea of "dirty" is if the last state differs is an interesting way from the default — i.e. different buffers showing.
I did need to accommodate a missing "last" state, but that's a pretty rare case for me, like when I've just defined a new state.
If you have activity auto-saving on (which is the default), an activity will pretty much always have a "last" state, even if it's only lightly modified from the default (different window points, sizes, etc.) ... I did need to accommodate a missing "last" state, but that's a pretty rare case for me, like when I've just defined a new state.
I often use activities-kill, which means that the activity won't have a last-used state.
So my idea of "dirty" is if the last state differs is an interesting way from the default — i.e. different buffers showing.
I feel like that concept might be hard to communicate to the user, but if you want to add an asterisk or something and document it to mean that, I won't object.
OK sounds good; lack of asterisk would also mean "no last state".
One oddity I've found: when I revert an activity (which I use instead of kill), I'd expect its last state to be removed. It isn't, and sometimes as a result there are two distinct sets of buffer names for the last and default state, even right after revert runs. This happens if a buffer is a duplicate which has a uniquifyd name. I.e. last might have main.c while default has main.c<proj>. Have you seen this? Perhaps revert should just remove last, like kill does.
One oddity I've found: when I
revertan activity (which I use instead of kill), I'd expect its last state to be removed. It isn't, and sometimes as a result there are two distinct sets of buffer names for the last and default state, even right afterrevertruns. This happens if a buffer is a duplicate which has auniquifyd name. I.e. last might havemain.cwhile default hasmain.c<proj>. Have you seen this? Perhaps revert should just remove last, like kill does.
I can see pros and cons on either side. Please feel free to open a new issue about that if you like.
Thanks. I've pushed the * flag and a README update. Let me know if you see anything else that needs addressing.
Here's how it's looking (note the final * for modified activities):
Couple things I think still need a bit of thought:
- Active activities get saved automatically, which always updates their
time. I guess that could be considered OK, since it shows the activity being continuously saved, but when there are no changes to any of the activity's configuration, it is confusing that the activity age is updated. I suppose we could replace the age annotation information with<active>for all active activities. - I had assumed activities were being sorted most recent first, since that's how mine had sorted. But as the screenshot shows, this isn't the case. Easy to do by pre-sorting the names based on recency. Let me know if that should be a separate PR.
Thanks for the screenshot.
Couple things I think still need a bit of thought:
1. Active activities get saved automatically, which always updates their `time`. I guess that could be considered OK, since it shows the activity being continuously saved, but when there are no changes to any of the activity's configuration, it is confusing that the activity age is updated. I suppose we could replace the age annotation information with `<active>` for all active activities.
Well, I do think that active activities should be marked in some way; it would seem like an obviously missing feature otherwise, IMO.
Replacing the age with some other string to indicate its being active would be one possibility. OTOH the multiple colors already make that column a bit busy-looking. I'm not sure what the best solution is.
We should probably also keep in mind potential window width in terms of the multiple columns of information. Have you tested with a half-screen-width window?
2. I had assumed activities were being sorted most recent first, since that's how mine had sorted. But as the screenshot shows, this isn't the case. Easy to do by pre-sorting the names based on recency. Let me know if that should be a separate PR.
I think a customizable sorting option should be part of this PR, if you're willing to work on it. Having the age column but not sorting by it seems weird. I'd guess that active ones should be sorted first, then the rest by age.
There's also https://github.com/alphapapa/frecency.el, which might be good to use. But I think that library should be rewritten; I've learned a lot since I wrote it, and its API is a bit awkward, so I wouldn't suggest using it now. But if the sorting function were customizeable, it could be used later.
I do think that active activities should be marked in some way; it would seem like an obviously missing feature otherwise, IMO.
Yeah, agree. Perhaps an <A> before the buffer count? Or maybe better, change the codes up:
*=active, modified+=inactive, modified-=active, unmodified=inactive, unmodified
Have you tested with a half-screen-width window?
With the flex-spacer it's responsive to small widths:
If we like it better, could measure the maximum activity width and pile everything up left (but still aligned). I tend to prefer flush right.
Will work on a simple sorting scheme.
Something else: window-state-buffers differ depending on whether that state is loaded and active, or not:
- Buffers which get renamed by uniquify are updated in the
laststate to theirname<dir>versions. - Buffers visiting symlinked files are named after the symlink (e.g.
.emacs) indefault, but the resolved file (e.g.init.el) inlast(or maybe whichever state was loaded).
This leads to false-positive modified states. Both of these mean I should check for equivalent files (if available) or buffer names (if not).
OK I've got something I'm reasonably happy with, with @ meaning active, * meaning modified. I've also fixed the modification check to compare true filenames and then buffers as a backup, and it fixes the 2 corner cases mentioned above. Here it is for the absurdly small window width of 45 chars:
Obviously still need to sort, and couple of small structural bothers:
- When you revert, since last is not immediately updated or removed, the activity still shows up as modified for some time (see #86).
- As mentioned above, active activities have their timestamps continuously incremented, so they will all mention the same 5 seconds age (or something small). It would be better of course to update the timestamp (and data) for a given activity only if something actually changed in the window state. I confirmed that
cl-tree-equalworks on window-states.
I got sort working, but I forgot that vertico substitutes it's own "most recent used then shortest candidate" sort function. You can disable it by let-binding vertico-sort-function nil but that's a bit odd to throw in. Thoughts?
Update: Got this working by factoring out a new completion-table and setting display-sort-function metadata, which vertico (and presumably others) respect.
Have you ever seen the persist file disappear? It's happened to me twice in the past few days while hacking on this. I note that persist removes the file if the value is ever the default value on save (nil). I don't see any reason it would be getting set to nil, but perhaps edebug is involved. Auto-save is on.
Have you ever seen the persist file disappear? It's happened to me twice in the past few days while hacking on this. I note that persist removes the file if the value is ever the default value on save (
nil). I don't see any reason it would be getting set to nil, but perhaps edebug is involved. Auto-save is on.
It has happened to me with another package, but only when I used my unpackaged/reload-package command to reload the package's symbols after it was already loaded. Since I stopped doing that, it hasn't happened again. I'm guessing there's some kind of unexpected state and actions that can happen when developing if it involves reloading the file in which the variable is defined, or if C-M-x is used on the variable's definition. So AFAICT it shouldn't ever affect users, but developers could find it quite inconvenient. It would probably be worth filing an issue about and trying to nail it down and possibly fix it.
OK, see #87. Give the sort and active marker a look and let me know what you think about preventing the time-tick from updating unless "things have changed".
Anything further needed here? If you like I can look into updating activity timestamp on save only if buffers changed.
I have a lot going on right now, so it may be a while before I can review this further. I appreciate your patience.
No rush on review, but I wanted to mention that I factored out the buffer/file comparison code from the annotation function, and updated activities-save using it to retain the timestamp if the list of buffers/files has not changed. This works much better with the idle-time save-all, no longer spuriously updating the age of active but "idle" activities. Working quite well so far.
Gentle ping on this one. Been in active use for quite some time without any issues on my end.
Gentle ping on this one. Been in active use for quite some time without any issues on my end.
Thanks. My to-do list is quite long and my time has been limited. I guess this can be merged now...
Gentle ping on this one. Been in active use for quite some time without any issues on my end.
Thanks. My to-do list is quite long and my time has been limited. I guess this can be merged now...
Happy to move at your pace, no problem. I tend to forget such things myself and so appreciate a ping here and there.
Forgive the additional round of review and comments. If you don't have enough "steam" to make some of the stylistic changes, I can make them myself before merging.
No worries at all. In my view, asking an author to incorporate significant changes, and (far more importantly) support them for years or decades to come calls for real patience. As long as good faith progress is being made towards merge, I'm happy to iterate.
Forgive the additional round of review and comments. If you don't have enough "steam" to make some of the stylistic changes, I can make them myself before merging.
No worries at all. In my view, asking an author to incorporate significant changes, and (far more importantly) support them for years or decades to come calls for real patience. As long as good faith progress is being made towards merge, I'm happy to iterate.
I appreciate that. Sometimes I wonder if I should handle some nitpicks myself before merging; I try to consider whether mentioning it will serve any educational purpose, but then I don't always know how familiar the contributor already is, and whether they just have other preferences.
Forgive the additional round of review and comments. If you don't have enough "steam" to make some of the stylistic changes, I can make them myself before merging.
No worries at all. In my view, asking an author to incorporate significant changes, and (far more importantly) support them for years or decades to come calls for real patience. As long as good faith progress is being made towards merge, I'm happy to iterate.
I appreciate that. Sometimes I wonder if I should handle some nitpicks myself before merging;
I'd say feel free to push any changes yourself if they are glaring and not worth describing. But I appreciate the collaborative feel your approach creates. I do a lot of work on other people's packages I admire, and some very capable authors have trouble with that, and can't resist NIH syndrome. So I'd say your approach is very valuable, especially to any less experienced contributors who come along.
I try to consider whether mentioning it will serve any educational purpose, but then I don't always know how familiar the contributor already is, and whether they just have other preferences.
I do have some different preferences but I always enjoy learning how others approach a problem. And I don't mind adapting to the local accent (Canadian ? ;). So some of both here.
As an example I mentioned above, I don't often reach for cl-labels when composing lambda's would do — i.e. when you are just going to pass the function on as an argument, once. But I found to my surprise I like quite the readability and mimetics (of some-func vs. #'my-local-func), so may use that more.
Other than deciding whether we are OK with ½ in the ages I think this is ready from my end. Feel free to season to taste.
Actually one more thing: I don't like that oldest-age maximizes across both last and default. It should use last or default, since that's what's shown.
Update: done. This reminds me: where did you learn the pcase .. (cl-struct pattern? I can't find any mention of that in the pcase docs. Quite useful and I learned that here (though I still do also rely on and enjoy the read/write version cl-symbol-macrolet enables, ala with-slots).