diff --git a/commands/account/cf.go b/commands/account/cf.go index 6c928ea..2ebc294 100644 --- a/commands/account/cf.go +++ b/commands/account/cf.go @@ -34,14 +34,20 @@ func (_ ChangeFolder) Execute(aerc *widgets.Aerc, args []string) error { if acct == nil { return errors.New("No account selected") } + store := acct.Store() + if store == nil { + return errors.New("Cannot perform action. Messages still loading") + } previous := acct.Directories().Selected() if args[1] == "-" { if dir, ok := history[acct.Name()]; ok { + store.ApplyClear() acct.Directories().Select(dir) } else { return errors.New("No previous folder to return to") } } else { + store.ApplyClear() acct.Directories().Select(args[1]) } history[acct.Name()] = previous diff --git a/commands/account/clear.go b/commands/account/clear.go new file mode 100644 index 0000000..bb9c04e --- /dev/null +++ b/commands/account/clear.go @@ -0,0 +1,34 @@ +package account + +import ( + "errors" + "git.sr.ht/~sircmpwn/aerc/widgets" +) + +type Clear struct{} + +func init() { + register(Clear{}) +} + +func (_ Clear) Aliases() []string { + return []string{"clear"} +} + +func (_ Clear) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (_ Clear) Execute(aerc *widgets.Aerc, args []string) error { + acct := aerc.SelectedAccount() + if acct == nil { + return errors.New("No account selected") + } + store := acct.Store() + if store == nil { + return errors.New("Cannot perform action. Messages still loading") + } + store.ApplyClear() + aerc.SetStatus("Clear complete.") + return nil +} diff --git a/commands/account/search.go b/commands/account/search.go index 0687c5b..da7ab03 100644 --- a/commands/account/search.go +++ b/commands/account/search.go @@ -16,7 +16,7 @@ func init() { } func (_ SearchFilter) Aliases() []string { - return []string{"search"} + return []string{"search", "filter"} } func (_ SearchFilter) Complete(aerc *widgets.Aerc, args []string) []string { @@ -54,13 +54,25 @@ func (_ SearchFilter) Execute(aerc *widgets.Aerc, args []string) error { if store == nil { return errors.New("Cannot perform action. Messages still loading") } - aerc.SetStatus("Searching...") - store.Search(criteria, func(uids []uint32) { - aerc.SetStatus("Search complete.") - acct.Logger().Printf("Search results: %v", uids) - store.ApplySearch(uids) - // TODO: Remove when stores have multiple OnUpdate handlers - acct.Messages().Scroll() - }) + + var cb func([]uint32) + if args[0] == "filter" { + aerc.SetStatus("Filtering...") + cb = func(uids []uint32) { + aerc.SetStatus("Filter complete.") + acct.Logger().Printf("Filter results: %v", uids) + store.ApplyFilter(uids) + } + } else { + aerc.SetStatus("Searching...") + cb = func(uids []uint32) { + aerc.SetStatus("Search complete.") + acct.Logger().Printf("Search results: %v", uids) + store.ApplySearch(uids) + // TODO: Remove when stores have multiple OnUpdate handlers + acct.Messages().Scroll() + } + } + store.Search(criteria, cb) return nil } diff --git a/config/binds.conf b/config/binds.conf index b9b19be..ac49bd0 100644 --- a/config/binds.conf +++ b/config/binds.conf @@ -43,6 +43,7 @@ $ = :term | = :pipe / = :search +\ = :filter n = :next-result N = :prev-result diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index de82394..4c5a552 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -114,6 +114,9 @@ message list, the message in the message viewer, etc). ## MESSAGE LIST COMMANDS +*clear* + Clears the current search or filter criteria. + *cf* Change the folder shown in the message list. @@ -122,18 +125,33 @@ message list, the message in the message viewer, etc). the current account's outgoing transport configuration, see *aerc-config*(5) for details on configuring outgoing emails. +*filter* [options] + Similar to *search*, but filters the displayed messages to only the search + results. See the documentation for *search* for more details. + *mkdir* Creates a new folder for this account and changes to that folder. -*next-folder* , *prev-folder* - Cycles to the next (or previous) folder shown in the sidebar, repeated n - times (default: 1). - *next* [%], *prev-message* [%] Selects the next (or previous) message in the message list. If specified as a percentage, the percentage is applied to the number of messages shown on screen and the cursor advances that far. +*next-folder* , *prev-folder* + Cycles to the next (or previous) folder shown in the sidebar, repeated n + times (default: 1). + +*next-result*, *prev-result* + Selects the next or previous search result. + +*search* [-ru] + Searches the current folder for . Each separate term is searched + case-insensitively among subject lines. + + *-r*: Search for read messages + + *-u*: Search for unread messages + *select* Selects the nth message in the message list (and scrolls it into view if necessary). diff --git a/lib/msgstore.go b/lib/msgstore.go index 27b63f3..baf8ee4 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -16,7 +16,7 @@ type MessageStore struct { DirInfo models.DirectoryInfo Messages map[uint32]*models.MessageInfo // Ordered list of known UIDs - Uids []uint32 + uids []uint32 selected int bodyCallbacks map[uint32][]func(io.Reader) @@ -25,6 +25,7 @@ type MessageStore struct { // Search/filter results results []uint32 resultIndex int + filter bool // Map of uids we've asked the worker to fetch onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers @@ -156,7 +157,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { } } store.Messages = newMap - store.Uids = msg.Uids + store.uids = msg.Uids update = true case *types.MessageInfo: if existing, ok := store.Messages[msg.Info.Uid]; ok && existing != nil { @@ -192,15 +193,15 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { delete(store.Deleted, uid) } } - uids := make([]uint32, len(store.Uids)-len(msg.Uids)) + uids := make([]uint32, len(store.uids)-len(msg.Uids)) j := 0 - for _, uid := range store.Uids { + for _, uid := range store.uids { if _, deleted := toDelete[uid]; !deleted && j < len(uids) { uids[j] = uid j += 1 } } - store.Uids = uids + store.uids = uids update = true } @@ -284,8 +285,15 @@ func (store *MessageStore) Read(uids []uint32, read bool, }, cb) } +func (store *MessageStore) Uids() []uint32 { + if store.filter { + return store.results + } + return store.uids +} + func (store *MessageStore) Selected() *models.MessageInfo { - return store.Messages[store.Uids[len(store.Uids)-store.selected-1]] + return store.Messages[store.uids[len(store.uids)-store.selected-1]] } func (store *MessageStore) SelectedIndex() int { @@ -294,24 +302,24 @@ func (store *MessageStore) SelectedIndex() int { func (store *MessageStore) Select(index int) { store.selected = index - for ; store.selected < 0; store.selected = len(store.Uids) + store.selected { + for ; store.selected < 0; store.selected = len(store.uids) + store.selected { /* This space deliberately left blank */ } - if store.selected > len(store.Uids) { - store.selected = len(store.Uids) + if store.selected > len(store.uids) { + store.selected = len(store.uids) } } func (store *MessageStore) nextPrev(delta int) { - if len(store.Uids) == 0 { + if len(store.uids) == 0 { return } store.selected += delta if store.selected < 0 { store.selected = 0 } - if store.selected >= len(store.Uids) { - store.selected = len(store.Uids) - 1 + if store.selected >= len(store.uids) { + store.selected = len(store.uids) - 1 } } @@ -340,6 +348,17 @@ func (store *MessageStore) ApplySearch(results []uint32) { store.NextResult() } +func (store *MessageStore) ApplyFilter(results []uint32) { + store.results = results + store.filter = true + store.update() +} + +func (store *MessageStore) ApplyClear() { + store.results = nil + store.filter = false +} + func (store *MessageStore) nextPrevResult(delta int) { if len(store.results) == 0 { return @@ -351,9 +370,9 @@ func (store *MessageStore) nextPrevResult(delta int) { if store.resultIndex < 0 { store.resultIndex = len(store.results) - 1 } - for i, uid := range store.Uids { + for i, uid := range store.uids { if store.results[len(store.results)-store.resultIndex-1] == uid { - store.Select(len(store.Uids) - i - 1) + store.Select(len(store.uids) - i - 1) break } } diff --git a/widgets/account.go b/widgets/account.go index 981a143..f070df1 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -172,7 +172,7 @@ func (acct *AccountView) SelectedAccount() *AccountView { } func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) { - if len(acct.msglist.Store().Uids) == 0 { + if len(acct.msglist.Store().Uids()) == 0 { return nil, errors.New("no message selected") } return acct.msglist.Selected(), nil diff --git a/widgets/msglist.go b/widgets/msglist.go index 8968653..e8ba8c1 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -56,9 +56,10 @@ func (ml *MessageList) Draw(ctx *ui.Context) { needsHeaders []uint32 row int = 0 ) + uids := store.Uids() - for i := len(store.Uids) - 1 - ml.scroll; i >= 0; i-- { - uid := store.Uids[i] + for i := len(uids) - 1 - ml.scroll; i >= 0; i-- { + uid := uids[i] msg := store.Messages[uid] if row >= ctx.Height() { @@ -106,7 +107,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) { row += 1 } - if len(store.Uids) == 0 { + if len(uids) == 0 { msg := ml.conf.Ui.EmptyMessage ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0, tcell.StyleDefault, "%s", msg) @@ -128,23 +129,24 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) { if ml.Store() != store { return } + uids := store.Uids() - if len(store.Uids) > 0 { + if len(uids) > 0 { // When new messages come in, advance the cursor accordingly // Note that this assumes new messages are appended to the top, which // isn't necessarily true once we implement SORT... ideally we'd look // for the previously selected UID. - if len(store.Uids) > ml.nmsgs && ml.nmsgs != 0 { - for i := 0; i < len(store.Uids)-ml.nmsgs; i++ { + if len(uids) > ml.nmsgs && ml.nmsgs != 0 { + for i := 0; i < len(uids)-ml.nmsgs; i++ { ml.Store().Next() } } - if len(store.Uids) < ml.nmsgs && ml.nmsgs != 0 { - for i := 0; i < ml.nmsgs-len(store.Uids); i++ { + if len(uids) < ml.nmsgs && ml.nmsgs != 0 { + for i := 0; i < ml.nmsgs-len(uids); i++ { ml.Store().Prev() } } - ml.nmsgs = len(store.Uids) + ml.nmsgs = len(uids) } ml.Scroll() @@ -158,7 +160,7 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) { ml.store = store if store != nil { ml.spinner.Stop() - ml.nmsgs = len(store.Uids) + ml.nmsgs = len(store.Uids()) store.OnUpdate(ml.storeUpdate) } else { ml.spinner.Start() @@ -172,12 +174,13 @@ func (ml *MessageList) Store() *lib.MessageStore { func (ml *MessageList) Empty() bool { store := ml.Store() - return store == nil || len(store.Uids) == 0 + return store == nil || len(store.Uids()) == 0 } func (ml *MessageList) Selected() *models.MessageInfo { store := ml.Store() - return store.Messages[store.Uids[len(store.Uids)-ml.store.SelectedIndex()-1]] + uids := store.Uids() + return store.Messages[uids[len(uids)-ml.store.SelectedIndex()-1]] } func (ml *MessageList) Select(index int) { @@ -189,7 +192,7 @@ func (ml *MessageList) Select(index int) { func (ml *MessageList) Scroll() { store := ml.Store() - if store == nil || len(store.Uids) == 0 { + if store == nil || len(store.Uids()) == 0 { return } if ml.Height() != 0 {