aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2023-05-14 17:16:35 +0200
committerRobin Jarry <robin@jarry.cc>2023-06-17 23:18:47 +0200
commit46c6dfc625649bace7002a77008bf3cadc3e9e3e (patch)
treeb1047e13ed9052b965750498f835036792709538
parent263d8cbec504bf56791dd3aa8c1e440d96b27d4a (diff)
downloadaerc-46c6dfc625649bace7002a77008bf3cadc3e9e3e.tar.gz
aerc-46c6dfc625649bace7002a77008bf3cadc3e9e3e.zip
notmuch: translate filter/search options to query
Translate the regular filter and search options to a notmuch query to achieve a basic equivalence across backends for these commands. The following examples have the same filtering effect: :filter -uf koni :filter -u from:koni :filter tag:unread from:koni And ':filter -x Flagged' would translate to ':filter tag:flagged'. Fixes: https://todo.sr.ht/~rjarry/aerc/177 Signed-off-by: Koni Marti <koni.marti@gmail.com> Tested-by: Inwit <inwit@sindominio.net>
-rw-r--r--worker/notmuch/search.go86
-rw-r--r--worker/notmuch/worker.go24
2 files changed, 106 insertions, 4 deletions
diff --git a/worker/notmuch/search.go b/worker/notmuch/search.go
new file mode 100644
index 00000000..e1428b45
--- /dev/null
+++ b/worker/notmuch/search.go
@@ -0,0 +1,86 @@
+//go:build notmuch
+// +build notmuch
+
+package notmuch
+
+import (
+ "strconv"
+ "strings"
+
+ "git.sr.ht/~rjarry/aerc/log"
+ "git.sr.ht/~sircmpwn/getopt"
+)
+
+type queryBuilder struct {
+ s string
+}
+
+func (q *queryBuilder) add(s string) {
+ if len(s) == 0 {
+ return
+ }
+ if len(q.s) != 0 {
+ q.s += " and "
+ }
+ q.s += "(" + s + ")"
+}
+
+func translate(args []string) (string, error) {
+ if len(args) == 0 {
+ return "", nil
+ }
+ var qb queryBuilder
+ opts, optind, err := getopt.Getopts(args, "rux:X:bat:H:f:c:d:")
+ if err != nil {
+ // if error occurs here, don't fail
+ log.Errorf("getopts failed: %v", err)
+ return strings.Join(args[1:], ""), nil
+ }
+ body := false
+ for _, opt := range opts {
+ switch opt.Option {
+ case 'r':
+ qb.add("not tag:unread")
+ case 'u':
+ qb.add("tag:unread")
+ case 'x':
+ qb.add(getParsedFlag(opt.Value))
+ case 'X':
+ qb.add("not " + getParsedFlag(opt.Value))
+ case 'H':
+ // TODO
+ case 'f':
+ qb.add("from:" + opt.Value)
+ case 't':
+ qb.add("to:" + opt.Value)
+ case 'c':
+ qb.add("cc:" + opt.Value)
+ case 'a':
+ // TODO
+ case 'b':
+ body = true
+ case 'd':
+ qb.add("date:" + strconv.Quote(opt.Value))
+ }
+ }
+ switch {
+ case body:
+ qb.add("body:" + strconv.Quote(strings.Join(args[optind:], " ")))
+ default:
+ qb.add(strings.Join(args[optind:], " "))
+ }
+ return qb.s, nil
+}
+
+func getParsedFlag(name string) string {
+ switch strings.ToLower(name) {
+ case "answered":
+ return "tag:replied"
+ case "seen":
+ return "(not tag:unread)"
+ case "flagged":
+ return "tag:flagged"
+ default:
+ return name
+ }
+}
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index 0ac37ff3..af0f279c 100644
--- a/worker/notmuch/worker.go
+++ b/worker/notmuch/worker.go
@@ -535,13 +535,19 @@ func (w *worker) handleFlagMessages(msg *types.FlagMessages) error {
}
func (w *worker) handleSearchDirectory(msg *types.SearchDirectory) error {
- // the first item is the command (search / filter)
- s := strings.Join(msg.Argv[1:], " ")
+ // the first item is the command (:search)
+ log.Debugf("search args: %v", msg.Argv)
+ s, err := translate(msg.Argv)
+ if err != nil {
+ log.Debugf("ERROR: %v", err)
+ return err
+ }
// we only want to search in the current query, so merge the two together
search := w.query
if s != "" {
search = fmt.Sprintf("(%v) and (%v)", w.query, s)
}
+ log.Debugf("search query: '%s'", search)
uids, err := w.uidsFromQuery(search)
if err != nil {
return err
@@ -634,9 +640,14 @@ func (w *worker) loadExcludeTags(
func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
query := w.query
if msg, ok := parent.(*types.FetchDirectoryContents); ok {
- s := strings.Join(msg.FilterCriteria[1:], " ")
+ log.Debugf("filter input: '%v'", msg.FilterCriteria)
+ s, err := translate(msg.FilterCriteria)
+ if err != nil {
+ return err
+ }
if s != "" {
query = fmt.Sprintf("(%v) and (%v)", query, s)
+ log.Debugf("filter query: '%s'", query)
}
}
uids, err := w.uidsFromQuery(query)
@@ -658,9 +669,14 @@ func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
query := w.query
if msg, ok := parent.(*types.FetchDirectoryThreaded); ok {
- s := strings.Join(msg.FilterCriteria[1:], " ")
+ log.Debugf("filter input: '%v'", msg.FilterCriteria)
+ s, err := translate(msg.FilterCriteria)
+ if err != nil {
+ return err
+ }
if s != "" {
query = fmt.Sprintf("(%v) and (%v)", query, s)
+ log.Debugf("filter query: '%s'", query)
}
}
threads, err := w.db.ThreadsFromQuery(query)