Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFD: Refine taglist #1546

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

RFD: Refine taglist #1546

wants to merge 12 commits into from

Conversation

mjg
Copy link
Contributor

@mjg mjg commented Oct 17, 2020

This is just a Request For Discussion, not a real PR yet.

The main theme is supercharging the taglist buffer so that you can search and navigate quickly in alot. So far, :taglist simply lists all tags in the db. We can do more!

The first patches are clean-up to make similar buffers (bufferlist, namedqueries, taglist) behave in more expected ways (UI+code):

  • do not advertise filtfun for namedqueries since it is not implemented
  • open only one namedqueries buffer (just as for taglist and bufferlist - they cannot differ)
  • correct default function signatures

The first main patch implements an argument for taglist which allows to restrict the taglist: :taglist foo returns only tags containing foo. In fact, full regex are allowed. This is similar but not equal to notmuch --output=tags tag:/foo/. Indeed, :taglist foo helps you find those tags which you are looking for, whereas the notmuch command lists more tags (from threads which match the tag query).

Next, :taglist --msgs restricts the tags to those from the current search or thread buffer. This let's you easily find all tags "from a year" for example: :search date:2020 followed by :taglist --msgs.

Finally, to make this resulting taglist from buffers useful for refining searches, the last patch in the series makes :select in a taglist buffer open a new search with a refined search: the search we came from (if we came from a search or thread buffer with :taglist --msgs) ANDed with the search for the selected tag.

Yes, tests are missing and need to be done. But also I'm wondering what the final UI should look like, in particular:

  • If taglist buffers can differ (different queries) should we allow opening multiple of them? (Probably yes)
  • Should the restriction mode :taglist --msgs be the default on search and thread buffers (and the opposite flag --global be introduced)?
  • Should the taglist buffer offer two different modes of select, i.e. searching just for the tag and the combined search, and which should be default?
  • Do we want a taglistprompt command to speed up regex searches for tags?
  • Besides, I'm not sure how useful the current tags parameter to taglist is in the UI - how do you specify a list, and how useful is it to get exactly that list back?

@mjg
Copy link
Contributor Author

mjg commented Oct 23, 2020

codeclimate and Codacy have different ideas about preferred line lengths ... I've fixed the indentations, although this is really just RFD and may need refactoring, different UI or different defaults. Plus tests and doc :)

@mjg
Copy link
Contributor Author

mjg commented Oct 23, 2020

Travis doc build "fails" because I did document the new arguments ... which gives a none empty diff.

@pazz
Copy link
Owner

pazz commented Oct 23, 2020 via email

@pazz
Copy link
Owner

pazz commented Oct 23, 2020

Travis doc build "fails" because I did document the new arguments ... which gives a none empty diff.

It looks like it fails because you did not commit the generated docs.

index 955b6ff..880c9e1 100644
894--- a/docs/source/usage/modes/global.rst
895+++ b/docs/source/usage/modes/global.rst
896@@ -226,6 +226,10 @@ The following commands are available globally:
897 
898     opens taglist buffer
899 
900+    argument
901+        regular expression to match
902+
903     optional arguments
904+        :---msgs: list tags from displayed messages
905         :---tags: tags to display
906 

try make -C docs cleanall html and check in the autogenerated changes?

@mjg
Copy link
Contributor Author

mjg commented Dec 29, 2020

So, here it is on top of notmuch2 bindings.

In addition, I took up the suggestions from above (do not limit buffers to 1 instance, except for the bufferlist buffer; rename the option) and went ahead with changing the default behaviour of taglist to be "buffer local" and with select to be "stay within current search and refine by tag"; at the same time allowing to "break out" to a global search again. BTW: urwid does not return shift enter so I went for meta enter.

Technically, I had to use a cffi binding for collect_tags() directly now since it is missing. I hope this is cool. (I might prod upstream to include it.)

Next up: look at those CI reports (can I get them ahead of the push?) and implement a few more taglist commands (such as remove tag from all messages).

@mjg
Copy link
Contributor Author

mjg commented Dec 30, 2020

About pep8: a lot of files on master do not conform to pep8. Should I rebase this series so that at least the new lines do conform? Or do some overall pep8 sanitize commit upfront?

@mjg
Copy link
Contributor Author

mjg commented Dec 31, 2020

So, I'll stop after these two more commits ... For the last one I need help: I cannot get the tagline to disappear from the list after I delete a tag. Also, you may prefer an extra taglist widget similar to the namedqueries thing. So the last one is more to demonstate what is possible: quickly acting upon selected tags.

Copy link
Owner

@pazz pazz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait: did you not claim to remove this "one buffer only" restriction completely both for taglist and bufferlist buffers?
bf5bd3e claims to do this but only does it for Taglist. Should that patch not do the same for BufferListBuffer and thereby obsolete this commit?

Copy link
Owner

@pazz pazz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this with a re in quotes because I wasn't sure. The command taglist "lists/*" blows up:

DEBUG:globals:CMDLINE: taglist "lists/*"
DEBUG:ui:search command string: "taglist "lists/*""
DEBUG:__init__:mode:search got commandline "taglist "lists/*""
DEBUG:__init__:ARGS: ['taglist', '"lists/*"']
DEBUG:__init__:cmd parms {'globally': False, 'tags': None, 'match': '"lists/*"'}
ERROR:ui:Traceback (most recent call last):
  File "/home/pazz/projects/alot/alot/ui.py", line 723, in apply_command
    cmd.apply(self)
  File "/home/pazz/projects/alot/alot/commands/globals.py", line 598, in apply
    ui.buffer_open(buffers.TagListBuffer(ui, tags, self.filtfun, querystring, self.match))
  File "/home/pazz/projects/alot/alot/buffers/taglist.py", line 25, in __init__
    self.rebuild()
  File "/home/pazz/projects/alot/alot/buffers/taglist.py", line 94, in rebuild
    self.taglist.set_focus(focusposition % len(displayedtags))
ZeroDivisionError: integer division or modulo by zero

@mjg
Copy link
Contributor Author

mjg commented Jan 1, 2021

Wait: did you not claim to remove this "one buffer only" restriction completely both for taglist and bufferlist buffers?
bf5bd3e claims to do this but only does it for Taglist. Should that patch not do the same for BufferListBuffer and thereby obsolete this commit?

I don't mind either way. It's still RFD :)
With the current series, the bufferlist buffer is the only one which can exist only once. This is meant to help those who have not bound bnext or are not aware of it. But going for full consistency is fine with me if you prefer that.

I'll rebase for make doc and pep8 anyways.

@mjg
Copy link
Contributor Author

mjg commented Jan 1, 2021

I tried this with a re in quotes because I wasn't sure. The command taglist "lists/*" blows up:

DEBUG:globals:CMDLINE: taglist "lists/*"
DEBUG:ui:search command string: "taglist "lists/*""
DEBUG:__init__:mode:search got commandline "taglist "lists/*""
DEBUG:__init__:ARGS: ['taglist', '"lists/*"']
DEBUG:__init__:cmd parms {'globally': False, 'tags': None, 'match': '"lists/*"'}
ERROR:ui:Traceback (most recent call last):
  File "/home/pazz/projects/alot/alot/ui.py", line 723, in apply_command
    cmd.apply(self)
  File "/home/pazz/projects/alot/alot/commands/globals.py", line 598, in apply
    ui.buffer_open(buffers.TagListBuffer(ui, tags, self.filtfun, querystring, self.match))
  File "/home/pazz/projects/alot/alot/buffers/taglist.py", line 25, in __init__
    self.rebuild()
  File "/home/pazz/projects/alot/alot/buffers/taglist.py", line 94, in rebuild
    self.taglist.set_focus(focusposition % len(displayedtags))
ZeroDivisionError: integer division or modulo by zero

Oh yes, I have to check for "no match at all". Technically, this problem is not new, but I guess nobody had a database with 0 tags in total ;)

@pazz
Copy link
Owner

pazz commented Jan 1, 2021 via email

@mjg
Copy link
Contributor Author

mjg commented Jan 1, 2021

Sorry my bad: you claimed to have taglist and named queries buffers behave the same and leave bufferlist as is. I am not sure what's best here: have all three behave equally makes sense and keeps the code cleaner, but in a way several bufferlists are pointless (which has never before kept us from allowing it :D) For sake of keeping this PR lean, let's keep it as is.

Just to get it right: "as is" means "before this series", so I should simply not touch bufferlist handling, right? It is not technically related to the series anyways and can wait. So I'll keep this PR strictly to "supercharging" the taglist.

How about the change in "taglist, bufferlist: provide defaults of correct signature"?

Copy link
Owner

@pazz pazz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn about this one. One the one hand it enables a natural "refine by tag" functionality
(should a select then not refocus the original buffer instead of opening a new one?).
On the other hand this is really not intuitive if you start with a named search or bufferlist buffer..

@pazz
Copy link
Owner

pazz commented Jan 1, 2021

grr, github seems to have disabled the feature to accumulate comments over commits when writing reviews. I hope my comments are somehow related to the individual commits..

BTW: urwid does not return
shift enter so I went for meta enter.

WTF? really?

@pazz
Copy link
Owner

pazz commented Jan 1, 2021 via email

@mjg
Copy link
Contributor Author

mjg commented Jan 1, 2021

I'm torn about this one. One the one hand it enables a natural "refine by tag" functionality
(should a select then not refocus the original buffer instead of opening a new one?).
On the other hand this is really not intuitive if you start with a named search or bufferlist buffer..

It's somehow the choice between the philosophies "do what you think I mean" and "do exactly what I tell you to", in this case the new default behaviour depending on which buffer we come from or explicit user choice.

Technically, it's either having an extra option --globally (buffer-local being the default, as in this series right now) or an extra option --locally or some better name (and global by default, as is current behaviour). I would always bind this to a key anyways. Maybe not changing the current behaviour is less intrusive and more in line with alot's overall command ui.

[I'm also kind of confused by github's comment interface right now, I'll answer by replying one comment by one.]

@mjg
Copy link
Contributor Author

mjg commented Jan 1, 2021

Technically, I had to use a cffi binding for collect_tags() directly now since it is missing. I hope this is cool. (I might prod upstream to include it.)
Fine for now, but yes: the nicer solution would be to lobby for this command in the notmuch2 bindings directly.

Submitted on the notmuch ML (from this branch).

do you distinguish between search and thread as "original" buffer just before the taglist is opened?

Yes, by type of ui.current_buffer; depending on that I query for tags and pass that to the taglist buffer.

Secondly, I'm confused by the statusline. For instance: [1: taglist] for "tag:inbox AND NOT tag:killed" matching "*" This means tags matching * in messages matching "...", right? [acdff3c]

Yes, maybe the wording in the status line can be clearer. First all tags from the query are collected (i.e. the union of all tags of messages which match the query), and then those tages are matched (textually) against the regexp. I try to express "global query as "" (as notmuch does) and "no regexp" as "" (even though "" or ".*" would be technically correct). Dunno.

Why not enrich the taglist by another column that shows separate TextWidgets in case the tag is hidden etc, or the "original" tagstring?

I wanted the alignment to be the same as before - the existing alignment looked good to me, since this extra info as usually empty. Also, typical translations are 1 character symbols replacing a word like attachment, so it looks funny to have that 1 character symbol in the tag name column and then the translation in the usually empty extra column (I tried).

Final comment: Is it possible to right-align the final two columns? That should be doable without changes in your code and only by changing (my) theme file right?

count is aligned right, unread_count is aligned left so that they "line up" in the middle, exactly like in the namedqueries buffer. At least that's how I wrote the code adjusted the default theme (by copying from that buffer). Both counts together end up in one TextWidget (and thus one column).

For me, this sometimes gets distorted by some "flag translation symbols", although this depends on the font and the terminal; it happens in other widgets, too.

@pazz
Copy link
Owner

pazz commented Jan 2, 2021

OK cool. Thanks for the update. This looks almost ready then.
Apart from the codeclimate/pep8 stuff, the issues flagged by codacity look reasonable to me. E.g. refactoring code with pass commands. The travis builds seem to fail only because of outdated documentation, which can be autogenerated (as above).
Not sure about tests.

@mjg mjg force-pushed the refine-taglist branch 2 times, most recently from 23eb4b7 to 0044ea3 Compare January 2, 2021 19:50
@mjg
Copy link
Contributor Author

mjg commented Jan 2, 2021

I tried to catch the errors with no tags now and managed to solve the "NO WORKEE" for untag. I've also run each commit through autopep8 though codeclimate seems to think otherwise. If the new defaults (regarding branch local) are OK I'll hunt down the remaining code climate issues manually. [Edit: Turns out autopep8 does not do what it claims to do without allowing it to be "aggressive" - it left longer lines which need just whitespace changes. Might have to rerun it.]

@pazz
Copy link
Owner

pazz commented Jan 3, 2021

I can confirm that the bug with an empty taglist is fixed now.

Now I wonder why we even need the extra filter for tag strings. If I only want to see a certain subset of tags then I could amend the "original" query by adding AND tag:X AND tag:Y...

@mjg
Copy link
Contributor Author

mjg commented Jan 3, 2021

I can confirm that the bug with an empty taglist is fixed now.

Now I wonder why we even need the extra filter for tag strings. If I only want to see a certain subset of tags then I could amend the "original" query by adding AND tag:X AND tag:Y...

You mean the regexp match? I guess it depends on the way you tag. Say, you deal with a lot of bachelor theses. Do you tag e-mails as BA/Einstein, BA/Witten or similar? Then a regexp match for BA gives you all bachelor projects without long ORs or constantly out-of-sync named queries. You could tag them as BA, Einstein and BA, Witten instead, but that means you always have to use AND queries if you search within a specific project.

But maybe I misunderstand your question.

@pazz
Copy link
Owner

pazz commented Jan 3, 2021 via email

@mjg
Copy link
Contributor Author

mjg commented Jan 3, 2021

I see. this is because notmuch queries cannot per se search for tags by regexp. makes sense.

Exactly.

Another thing: I see that the syntax of the taglist commands has both a regex argument and --tags parameter, which lets you list tags. I presume that the latter is obsoleted by a RE that is a disjunction of tags?

I'm not sure why it was there: You could specify a list of tags (though I never figured out how to pass a list from the UI), and the taglist buffer would return that exact list of tags.

A correct regexp for listing tags foo and bar is ^foo$|^bar$, but often foo|bar gives the same. I would argue for removing the --tags parameter from the taglist command.

BTW: I think it's great to think this feature through thoroughly before merging. If I stop answering quickly in the next days it's because term starts again ...

@pazz
Copy link
Owner

pazz commented Jan 3, 2021 via email

@mjg
Copy link
Contributor Author

mjg commented Jan 4, 2021

Since it came up: The select command in this PR quotes a tag to be on the safe side, i.e. it ands tag:"alot" to the query when refining for the tag alot. I'm not sure if we do this on other occasions (or if it comes up at all) or if there is a better way to do it. Quoting of notmuch search terms is somewhat peculiar (see thread:"{...}").

@pazz
Copy link
Owner

pazz commented Jan 4, 2021 via email

@mjg
Copy link
Contributor Author

mjg commented Jan 4, 2021

Hmm, meanwhile I've learned that notmuch does support regex in tag searches now. notmuch search tag:/BA/ gives all threads with tags which contain BA. If I open a taglist buffer on that search (with this PR) I get more tags then just those matching BA (namely those from all messages from threads with tags matching BA). But I'm wondering whether that is sufficient (to skip the match from this PR), or whether it simply means there are two similar but different ways to do two similar but different things.

@pazz
Copy link
Owner

pazz commented Jan 4, 2021 via email

namedqueries do not support filtering, so do not advertise this in the
interface.
We actively prevent opening multiple taglist or bufferlist buffers, but
so far allow to open multiple named query buffers. They cannot differ,
though, since there is nothing to configure about them.

Rather than confusing the user by behaving differently for different
buffer types, lift the restriction for all of them (but the bufferlist):
at worst there are multiple instances of buffers with the same content.

We do keep a single exception for the bufferlist buffer because that is
the one you typically use to switch to other buffers.
The filtfun argument of these lists expects a boolean valued function of
a single argument. The current default is `lambda x: x` which in most
cases will result in no filtering, but may give the false impression
that the filtfun argument is a transformation rather than a filter.

Be explicit and specify the default no-op filter as `lambda x: True`.
If a taglist buffer is asked to display 0 tags then it throws a
ZeroDivisionError currently. Since people usually have at least 1 tag
nobody noticed so far. But in the future the UI will allow to restrict
the set of tags in a taglist buffer, so prepare for the case of 0 tags.
Currently, selecting a tag searches for that tag surrounded by quotes.
This results in a somewhat strange display in the resulting search
buffer status line.

Surround the tag by quotes only if it contains a space. We do the same
when preparing the input line for `retag`, for example (where it is
ncessary), and it gives a more natural display.
Currently, there is no way to specify the taglist filter function from
UI, and the tags argument just allows to display the list of tags that
you specify.

Introduce a match argument that allows to list those tags matching the
specified regular expression.

Especially useful if you bind a key to `prompt 'taglist '`.
Currently, the taglist command runs on the global list of tags.

Change the default behaviour to restrict the taglist to those
appearing in the displayed messages of a search or thread buffer. This
is basically the same list which `notmuch search --output=tags` would
return for the query underlying the search buffer resp. the query for
the thread displayed in a thread buffer.

Introduce a flag `--global` which allows to list all tags globally.
Binding a key to `taglist --global` restores the previous default
behaviour.
When a taglist buffer lists tags from a search or thead buffer (without
`--global`) then selecting a tag lists all messages in the db with that
tag. This feels counter-intuitive.

Instead, remember the original query and refine the query by filtering
on the tag in addition to the original query when the tag is `select`ed.

In addition, introduce a new command `globalselect` which does a global
search with the selected tag. The default key binding is `meta enter`,
complementing the default binding `enter` for `select`.
Until recently, taglist buffers were global. Now they can differ by
realm (global, search, thread) and pattern matching on the tags. Provide
this information for the statusbar and update the defaults to display
it.

In addition, provide a count for the messages matching the specific
query, i.e. globally or the query from the search or thread buffer,
restricted to matches on the pattern matched tags.

Note that even in the global case without pattern match the matched
count is typically lower than the total count since the latter includes
messages with excluded tags as well as those without any tags, whereas
the former list any not excluded message with any tag.
taglist jumps through some hoops to amend the tags in the list by
additional information (hidden, name before translation) without urwids
column spreading kicking in, when the aim is in fact to add that text to
the tag text.

Refactor TagWidget so that callers can ask the widget to amend the
information. The default is off for callers like the search and thread
widgets.
So far, the taglist shows a list of tags (no surprise here) only,
recently with a total count of matched message.

Show a count for each tag analogous to the namelist widget:
These are the counts for original query (global, search, or thread) AND
the tag, and also for that AND tag:unread.
The usual untag command requires an argument. In the taglist, we have a
a shortcut available since a tag is selected. Use that for quickly
removing tags.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants