aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Culverhouse <tim@timculverhouse.com>2022-07-05 14:48:40 -0500
committerRobin Jarry <robin@jarry.cc>2022-07-10 21:15:12 +0200
commitc2f4404fca15be37228545b1893f5fa335168337 (patch)
tree2bfc788a7e38510f1d5057c4b47d2460a548cb44
parentccd042889f6d8aa78b70c01395ef69aec48ac48c (diff)
downloadaerc-c2f4404fca15be37228545b1893f5fa335168337.tar.gz
threading: enable filtering of server-side threads
This patch enables the filtering of a threaded view which uses server-built threads. Filtering is done server-side, in order to preserve the use of server-built threads. In adding this feature, the filtering of notmuch folders was brought up to feature parity with the other workers. The filters function the same (ie: they can be stacked). The notmuch filters, however, still use notmuch syntax for the filtering. Signed-off-by: Tim Culverhouse <tim@timculverhouse.com> Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r--commands/account/search.go17
-rw-r--r--commands/account/sort.go6
-rw-r--r--doc/aerc-notmuch.5.scd12
-rw-r--r--lib/msgstore.go63
-rw-r--r--widgets/dirlist.go6
-rw-r--r--worker/imap/open.go25
-rw-r--r--worker/imap/search.go3
-rw-r--r--worker/maildir/worker.go23
-rw-r--r--worker/notmuch/worker.go19
-rw-r--r--worker/types/messages.go6
10 files changed, 103 insertions, 77 deletions
diff --git a/commands/account/search.go b/commands/account/search.go
index 771c850f..7cae227f 100644
--- a/commands/account/search.go
+++ b/commands/account/search.go
@@ -6,6 +6,7 @@ import (
"git.sr.ht/~rjarry/aerc/lib/statusline"
"git.sr.ht/~rjarry/aerc/widgets"
+ "git.sr.ht/~rjarry/aerc/worker/types"
)
type SearchFilter struct{}
@@ -32,27 +33,29 @@ func (SearchFilter) Execute(aerc *widgets.Aerc, args []string) error {
return errors.New("Cannot perform action. Messages still loading")
}
- var cb func([]uint32)
if args[0] == "filter" {
if len(args[1:]) == 0 {
return Clear{}.Execute(aerc, []string{"clear"})
}
acct.SetStatus(statusline.FilterActivity("Filtering..."), statusline.Search(""))
- cb = func(uids []uint32) {
- acct.SetStatus(statusline.FilterResult(strings.Join(args, " ")))
- acct.Logger().Printf("Filter results: %v", uids)
- store.ApplyFilter(uids)
+ store.SetFilter(args[1:])
+ cb := func(msg types.WorkerMessage) {
+ if _, ok := msg.(*types.Done); ok {
+ acct.SetStatus(statusline.FilterResult(strings.Join(args, " ")))
+ acct.Logger().Printf("Filter results: %v", store.Uids())
+ }
}
+ store.Sort(nil, cb)
} else {
acct.SetStatus(statusline.Search("Searching..."))
- cb = func(uids []uint32) {
+ cb := func(uids []uint32) {
acct.SetStatus(statusline.Search(strings.Join(args, " ")))
acct.Logger().Printf("Search results: %v", uids)
store.ApplySearch(uids)
// TODO: Remove when stores have multiple OnUpdate handlers
acct.Messages().Invalidate()
}
+ store.Search(args, cb)
}
- store.Search(args, cb)
return nil
}
diff --git a/commands/account/sort.go b/commands/account/sort.go
index a35ff333..e9ee4a34 100644
--- a/commands/account/sort.go
+++ b/commands/account/sort.go
@@ -84,8 +84,10 @@ func (Sort) Execute(aerc *widgets.Aerc, args []string) error {
}
acct.SetStatus(statusline.Sorting(true))
- store.Sort(sortCriteria, func() {
- acct.SetStatus(statusline.Sorting(false))
+ store.Sort(sortCriteria, func(msg types.WorkerMessage) {
+ if _, ok := msg.(*types.Done); ok {
+ acct.SetStatus(statusline.Sorting(false))
+ }
})
return nil
}
diff --git a/doc/aerc-notmuch.5.scd b/doc/aerc-notmuch.5.scd
index 44b6b9d2..7da7ecf4 100644
--- a/doc/aerc-notmuch.5.scd
+++ b/doc/aerc-notmuch.5.scd
@@ -74,10 +74,14 @@ in notmuch, like :delete and :archive.++
Others are slightly different in semantics and mentioned below:
*cf* <notmuch query>
- The change folder command allows for arbitrary notmuch queries and should
- usually be preferred over *:filter* as it will be much faster if you use
- the notmuch database to do the filtering
-
+ The change folder command allows for arbitrary notmuch queries. Performing a
+ cf command will perform a new top-level notmuch query
+
+*filter* <notmuch query>
+ The filter command for notmuch backends takes in arbitrary notmuch queries.
+ It applies the query on the set of messages shown in the message list. This
+ can be used to perform successive filters/queries. It is equivalent to
+ performing a set of queries concatenated with "and"
# SEE ALSO
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 99ae594e..2e773699 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -33,8 +33,7 @@ type MessageStore struct {
// Search/filter results
results []uint32
resultIndex int
- filtered []uint32
- filter bool
+ filter []string
sortCriteria []*types.SortCriterion
@@ -83,6 +82,7 @@ func NewMessageStore(worker *types.Worker,
threadedView: thread,
buildThreads: clientThreads,
+ filter: []string{"filter"},
sortCriteria: defaultSortCriteria,
pendingBodies: make(map[uint32]interface{}),
@@ -215,7 +215,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
}
store.Messages = newMap
store.uids = msg.Uids
- sort.SortBy(store.filtered, store.uids)
store.checkMark()
update = true
case *types.DirectoryThreaded:
@@ -313,14 +312,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
}
store.results = newResults
- var newFiltered []uint32
- for _, res := range store.filtered {
- if _, deleted := toDelete[res]; !deleted {
- newFiltered = append(newFiltered, res)
- }
- }
- store.filtered = newFiltered
-
for _, thread := range store.Threads {
thread.Walk(func(t *types.Thread, _ int, _ error) error {
if _, deleted := toDelete[t.Uid]; deleted {
@@ -392,13 +383,7 @@ func (store *MessageStore) runThreadBuilder() {
store.builder.Update(msg)
}
}
- var uids []uint32
- if store.filter {
- uids = store.filtered
- } else {
- uids = store.uids
- }
- store.Threads = store.builder.Threads(uids)
+ store.Threads = store.builder.Threads(store.uids)
}
func (store *MessageStore) Delete(uids []uint32,
@@ -496,10 +481,6 @@ func (store *MessageStore) Uids() []uint32 {
return uids
}
}
-
- if store.filter {
- return store.filtered
- }
return store.uids
}
@@ -732,30 +713,16 @@ func (store *MessageStore) ApplySearch(results []uint32) {
store.NextResult()
}
-func (store *MessageStore) ApplyFilter(results []uint32) {
- defer store.Reselect(store.Selected())
- store.results = nil
- store.filtered = results
- store.filter = true
- if store.onFilterChange != nil {
- store.onFilterChange(store)
- }
- store.update()
- // any marking is now invalid
- // TODO: could save that probably
- store.ClearVisualMark()
+func (store *MessageStore) SetFilter(args []string) {
+ store.filter = append(store.filter, args...)
}
func (store *MessageStore) ApplyClear() {
- store.results = nil
- store.filtered = nil
- store.filter = false
- if store.BuildThreads() {
- store.runThreadBuilder()
- }
+ store.filter = []string{"filter"}
if store.onFilterChange != nil {
store.onFilterChange(store)
}
+ store.Sort(nil, nil)
}
func (store *MessageStore) nextPrevResult(delta int) {
@@ -796,24 +763,30 @@ func (store *MessageStore) ModifyLabels(uids []uint32, add, remove []string,
}, cb)
}
-func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func()) {
+func (store *MessageStore) Sort(criteria []*types.SortCriterion, cb func(types.WorkerMessage)) {
+ if criteria == nil {
+ criteria = store.sortCriteria
+ } else {
+ store.sortCriteria = criteria
+ }
store.Sorting = true
- store.sortCriteria = criteria
handle_return := func(msg types.WorkerMessage) {
store.Sorting = false
if cb != nil {
- cb()
+ cb(msg)
}
}
if store.threadedView && !store.buildThreads {
store.worker.PostAction(&types.FetchDirectoryThreaded{
- SortCriteria: criteria,
+ SortCriteria: criteria,
+ FilterCriteria: store.filter,
}, handle_return)
} else {
store.worker.PostAction(&types.FetchDirectoryContents{
- SortCriteria: criteria,
+ SortCriteria: criteria,
+ FilterCriteria: store.filter,
}, handle_return)
}
}
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index ad5f048c..5c3416c7 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -191,7 +191,11 @@ func (dirlist *DirectoryList) Select(name string) {
}
dirlist.sortDirsByFoldersSortConfig()
if newStore {
- dirlist.worker.PostAction(&types.FetchDirectoryContents{}, nil)
+ store, ok := dirlist.MsgStore(name)
+ if ok {
+ // Fetch directory contents via store.Sort
+ store.Sort(nil, nil)
+ }
}
}
dirlist.Invalidate()
diff --git a/worker/imap/open.go b/worker/imap/open.go
index a0607d00..b52a3c65 100644
--- a/worker/imap/open.go
+++ b/worker/imap/open.go
@@ -3,7 +3,6 @@ package imap
import (
"sort"
- "github.com/emersion/go-imap"
sortthread "github.com/emersion/go-imap-sortthread"
"git.sr.ht/~rjarry/aerc/worker/types"
@@ -29,11 +28,13 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
imapw.worker.Logger.Printf("Fetching UID list")
- seqSet := &imap.SeqSet{}
- seqSet.AddRange(1, imapw.selected.Messages)
-
- searchCriteria := &imap.SearchCriteria{
- SeqNum: seqSet,
+ searchCriteria, err := parseSearch(msg.FilterCriteria)
+ if err != nil {
+ imapw.worker.PostMessage(&types.Error{
+ Message: types.RespondTo(msg),
+ Error: err,
+ }, nil)
+ return
}
sortCriteria := translateSortCriterions(msg.SortCriteria)
@@ -98,10 +99,16 @@ func (imapw *IMAPWorker) handleDirectoryThreaded(
msg *types.FetchDirectoryThreaded) {
imapw.worker.Logger.Printf("Fetching threaded UID list")
- seqSet := &imap.SeqSet{}
- seqSet.AddRange(1, imapw.selected.Messages)
+ searchCriteria, err := parseSearch(msg.FilterCriteria)
+ if err != nil {
+ imapw.worker.PostMessage(&types.Error{
+ Message: types.RespondTo(msg),
+ Error: err,
+ }, nil)
+ return
+ }
threads, err := imapw.client.thread.UidThread(sortthread.References,
- &imap.SearchCriteria{SeqNum: seqSet})
+ searchCriteria)
if err != nil {
imapw.worker.PostMessage(&types.Error{
Message: types.RespondTo(msg),
diff --git a/worker/imap/search.go b/worker/imap/search.go
index f866b1cc..46a25c7c 100644
--- a/worker/imap/search.go
+++ b/worker/imap/search.go
@@ -11,6 +11,9 @@ import (
func parseSearch(args []string) (*imap.SearchCriteria, error) {
criteria := imap.NewSearchCriteria()
+ if len(args) == 0 {
+ return criteria, nil
+ }
opts, optind, err := getopt.Getopts(args, "rubax:X:t:H:f:c:")
if err != nil {
diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go
index 08628383..cc03ec81 100644
--- a/worker/maildir/worker.go
+++ b/worker/maildir/worker.go
@@ -406,10 +406,25 @@ func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error {
func (w *Worker) handleFetchDirectoryContents(
msg *types.FetchDirectoryContents) error {
- uids, err := w.c.UIDs(*w.selected)
- if err != nil {
- w.worker.Logger.Printf("error scanning uids: %v", err)
- return err
+ var (
+ uids []uint32
+ err error
+ )
+ if len(msg.FilterCriteria) > 0 {
+ filter, err := parseSearch(msg.FilterCriteria)
+ if err != nil {
+ return err
+ }
+ uids, err = w.search(filter)
+ if err != nil {
+ return err
+ }
+ } else {
+ uids, err = w.c.UIDs(*w.selected)
+ if err != nil {
+ w.worker.Logger.Printf("error scanning uids: %v", err)
+ return err
+ }
}
sortedUids, err := w.sort(uids, msg.SortCriteria)
if err != nil {
diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go
index 35e68409..27be73db 100644
--- a/worker/notmuch/worker.go
+++ b/worker/notmuch/worker.go
@@ -550,7 +550,14 @@ func (w *worker) loadExcludeTags(
}
func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
- uids, err := w.uidsFromQuery(w.query)
+ query := w.query
+ if msg, ok := parent.(*types.FetchDirectoryContents); ok {
+ s := strings.Join(msg.FilterCriteria[1:], " ")
+ if s != "" {
+ query = fmt.Sprintf("(%v) and (%v)", query, s)
+ }
+ }
+ uids, err := w.uidsFromQuery(query)
if err != nil {
return fmt.Errorf("could not fetch uids: %v", err)
}
@@ -567,12 +574,18 @@ func (w *worker) emitDirectoryContents(parent types.WorkerMessage) error {
}
func (w *worker) emitDirectoryThreaded(parent types.WorkerMessage) error {
- threads, err := w.db.ThreadsFromQuery(w.query)
+ query := w.query
+ if msg, ok := parent.(*types.FetchDirectoryThreaded); ok {
+ s := strings.Join(msg.FilterCriteria[1:], " ")
+ if s != "" {
+ query = fmt.Sprintf("(%v) and (%v)", query, s)
+ }
+ }
+ threads, err := w.db.ThreadsFromQuery(query)
if err != nil {
return err
}
w.w.PostMessage(&types.DirectoryThreaded{
- Message: types.RespondTo(parent),
Threads: threads,
}, nil)
return nil
diff --git a/worker/types/messages.go b/worker/types/messages.go
index e303ade4..a414eb26 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -87,12 +87,14 @@ type OpenDirectory struct {
type FetchDirectoryContents struct {
Message
- SortCriteria []*SortCriterion
+ SortCriteria []*SortCriterion
+ FilterCriteria []string
}
type FetchDirectoryThreaded struct {
Message
- SortCriteria []*SortCriterion
+ SortCriteria []*SortCriterion
+ FilterCriteria []string
}
type SearchDirectory struct {