Implement :filter, :clear

Signed-off-by: Kevin Kuehler <keur@ocf.berkeley.edu>
This commit is contained in:
Kevin Kuehler 2019-07-17 00:35:50 -07:00 committed by Drew DeVault
parent 8b2abcb02a
commit f81e4bd41c
8 changed files with 134 additions and 41 deletions

View File

@ -34,14 +34,20 @@ func (_ ChangeFolder) Execute(aerc *widgets.Aerc, args []string) error {
if acct == nil { if acct == nil {
return errors.New("No account selected") 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() previous := acct.Directories().Selected()
if args[1] == "-" { if args[1] == "-" {
if dir, ok := history[acct.Name()]; ok { if dir, ok := history[acct.Name()]; ok {
store.ApplyClear()
acct.Directories().Select(dir) acct.Directories().Select(dir)
} else { } else {
return errors.New("No previous folder to return to") return errors.New("No previous folder to return to")
} }
} else { } else {
store.ApplyClear()
acct.Directories().Select(args[1]) acct.Directories().Select(args[1])
} }
history[acct.Name()] = previous history[acct.Name()] = previous

34
commands/account/clear.go Normal file
View File

@ -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
}

View File

@ -16,7 +16,7 @@ func init() {
} }
func (_ SearchFilter) Aliases() []string { func (_ SearchFilter) Aliases() []string {
return []string{"search"} return []string{"search", "filter"}
} }
func (_ SearchFilter) Complete(aerc *widgets.Aerc, args []string) []string { 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 { if store == nil {
return errors.New("Cannot perform action. Messages still loading") return errors.New("Cannot perform action. Messages still loading")
} }
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...") aerc.SetStatus("Searching...")
store.Search(criteria, func(uids []uint32) { cb = func(uids []uint32) {
aerc.SetStatus("Search complete.") aerc.SetStatus("Search complete.")
acct.Logger().Printf("Search results: %v", uids) acct.Logger().Printf("Search results: %v", uids)
store.ApplySearch(uids) store.ApplySearch(uids)
// TODO: Remove when stores have multiple OnUpdate handlers // TODO: Remove when stores have multiple OnUpdate handlers
acct.Messages().Scroll() acct.Messages().Scroll()
}) }
}
store.Search(criteria, cb)
return nil return nil
} }

View File

@ -43,6 +43,7 @@ $ = :term<space>
| = :pipe<space> | = :pipe<space>
/ = :search<space> / = :search<space>
\ = :filter<space>
n = :next-result<Enter> n = :next-result<Enter>
N = :prev-result<Enter> N = :prev-result<Enter>

View File

@ -114,6 +114,9 @@ message list, the message in the message viewer, etc).
## MESSAGE LIST COMMANDS ## MESSAGE LIST COMMANDS
*clear*
Clears the current search or filter criteria.
*cf* <folder> *cf* <folder>
Change the folder shown in the message list. 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 the current account's outgoing transport configuration, see
*aerc-config*(5) for details on configuring outgoing emails. *aerc-config*(5) for details on configuring outgoing emails.
*filter* [options] <terms...>
Similar to *search*, but filters the displayed messages to only the search
results. See the documentation for *search* for more details.
*mkdir* <name> *mkdir* <name>
Creates a new folder for this account and changes to that folder. Creates a new folder for this account and changes to that folder.
*next-folder* <n>, *prev-folder* <n>
Cycles to the next (or previous) folder shown in the sidebar, repeated n
times (default: 1).
*next* <n>[%], *prev-message* <n>[%] *next* <n>[%], *prev-message* <n>[%]
Selects the next (or previous) message in the message list. If specified as 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 a percentage, the percentage is applied to the number of messages shown on
screen and the cursor advances that far. screen and the cursor advances that far.
*next-folder* <n>, *prev-folder* <n>
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] <terms...>
Searches the current folder for <terms>. Each separate term is searched
case-insensitively among subject lines.
*-r*: Search for read messages
*-u*: Search for unread messages
*select* <n> *select* <n>
Selects the nth message in the message list (and scrolls it into view if Selects the nth message in the message list (and scrolls it into view if
necessary). necessary).

View File

@ -16,7 +16,7 @@ type MessageStore struct {
DirInfo models.DirectoryInfo DirInfo models.DirectoryInfo
Messages map[uint32]*models.MessageInfo Messages map[uint32]*models.MessageInfo
// Ordered list of known UIDs // Ordered list of known UIDs
Uids []uint32 uids []uint32
selected int selected int
bodyCallbacks map[uint32][]func(io.Reader) bodyCallbacks map[uint32][]func(io.Reader)
@ -25,6 +25,7 @@ type MessageStore struct {
// Search/filter results // Search/filter results
results []uint32 results []uint32
resultIndex int resultIndex int
filter bool
// Map of uids we've asked the worker to fetch // Map of uids we've asked the worker to fetch
onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers onUpdate func(store *MessageStore) // TODO: multiple onUpdate handlers
@ -156,7 +157,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
} }
} }
store.Messages = newMap store.Messages = newMap
store.Uids = msg.Uids store.uids = msg.Uids
update = true update = true
case *types.MessageInfo: case *types.MessageInfo:
if existing, ok := store.Messages[msg.Info.Uid]; ok && existing != nil { 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) delete(store.Deleted, uid)
} }
} }
uids := make([]uint32, len(store.Uids)-len(msg.Uids)) uids := make([]uint32, len(store.uids)-len(msg.Uids))
j := 0 j := 0
for _, uid := range store.Uids { for _, uid := range store.uids {
if _, deleted := toDelete[uid]; !deleted && j < len(uids) { if _, deleted := toDelete[uid]; !deleted && j < len(uids) {
uids[j] = uid uids[j] = uid
j += 1 j += 1
} }
} }
store.Uids = uids store.uids = uids
update = true update = true
} }
@ -284,8 +285,15 @@ func (store *MessageStore) Read(uids []uint32, read bool,
}, cb) }, cb)
} }
func (store *MessageStore) Uids() []uint32 {
if store.filter {
return store.results
}
return store.uids
}
func (store *MessageStore) Selected() *models.MessageInfo { 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 { func (store *MessageStore) SelectedIndex() int {
@ -294,24 +302,24 @@ func (store *MessageStore) SelectedIndex() int {
func (store *MessageStore) Select(index int) { func (store *MessageStore) Select(index int) {
store.selected = index 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 */ /* This space deliberately left blank */
} }
if store.selected > len(store.Uids) { if store.selected > len(store.uids) {
store.selected = len(store.Uids) store.selected = len(store.uids)
} }
} }
func (store *MessageStore) nextPrev(delta int) { func (store *MessageStore) nextPrev(delta int) {
if len(store.Uids) == 0 { if len(store.uids) == 0 {
return return
} }
store.selected += delta store.selected += delta
if store.selected < 0 { if store.selected < 0 {
store.selected = 0 store.selected = 0
} }
if store.selected >= len(store.Uids) { if store.selected >= len(store.uids) {
store.selected = len(store.Uids) - 1 store.selected = len(store.uids) - 1
} }
} }
@ -340,6 +348,17 @@ func (store *MessageStore) ApplySearch(results []uint32) {
store.NextResult() 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) { func (store *MessageStore) nextPrevResult(delta int) {
if len(store.results) == 0 { if len(store.results) == 0 {
return return
@ -351,9 +370,9 @@ func (store *MessageStore) nextPrevResult(delta int) {
if store.resultIndex < 0 { if store.resultIndex < 0 {
store.resultIndex = len(store.results) - 1 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 { 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 break
} }
} }

View File

@ -172,7 +172,7 @@ func (acct *AccountView) SelectedAccount() *AccountView {
} }
func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) { 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 nil, errors.New("no message selected")
} }
return acct.msglist.Selected(), nil return acct.msglist.Selected(), nil

View File

@ -56,9 +56,10 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
needsHeaders []uint32 needsHeaders []uint32
row int = 0 row int = 0
) )
uids := store.Uids()
for i := len(store.Uids) - 1 - ml.scroll; i >= 0; i-- { for i := len(uids) - 1 - ml.scroll; i >= 0; i-- {
uid := store.Uids[i] uid := uids[i]
msg := store.Messages[uid] msg := store.Messages[uid]
if row >= ctx.Height() { if row >= ctx.Height() {
@ -106,7 +107,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
row += 1 row += 1
} }
if len(store.Uids) == 0 { if len(uids) == 0 {
msg := ml.conf.Ui.EmptyMessage msg := ml.conf.Ui.EmptyMessage
ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0, ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
tcell.StyleDefault, "%s", msg) tcell.StyleDefault, "%s", msg)
@ -128,23 +129,24 @@ func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
if ml.Store() != store { if ml.Store() != store {
return return
} }
uids := store.Uids()
if len(store.Uids) > 0 { if len(uids) > 0 {
// When new messages come in, advance the cursor accordingly // When new messages come in, advance the cursor accordingly
// Note that this assumes new messages are appended to the top, which // Note that this assumes new messages are appended to the top, which
// isn't necessarily true once we implement SORT... ideally we'd look // isn't necessarily true once we implement SORT... ideally we'd look
// for the previously selected UID. // for the previously selected UID.
if len(store.Uids) > ml.nmsgs && ml.nmsgs != 0 { if len(uids) > ml.nmsgs && ml.nmsgs != 0 {
for i := 0; i < len(store.Uids)-ml.nmsgs; i++ { for i := 0; i < len(uids)-ml.nmsgs; i++ {
ml.Store().Next() ml.Store().Next()
} }
} }
if len(store.Uids) < ml.nmsgs && ml.nmsgs != 0 { if len(uids) < ml.nmsgs && ml.nmsgs != 0 {
for i := 0; i < ml.nmsgs-len(store.Uids); i++ { for i := 0; i < ml.nmsgs-len(uids); i++ {
ml.Store().Prev() ml.Store().Prev()
} }
} }
ml.nmsgs = len(store.Uids) ml.nmsgs = len(uids)
} }
ml.Scroll() ml.Scroll()
@ -158,7 +160,7 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) {
ml.store = store ml.store = store
if store != nil { if store != nil {
ml.spinner.Stop() ml.spinner.Stop()
ml.nmsgs = len(store.Uids) ml.nmsgs = len(store.Uids())
store.OnUpdate(ml.storeUpdate) store.OnUpdate(ml.storeUpdate)
} else { } else {
ml.spinner.Start() ml.spinner.Start()
@ -172,12 +174,13 @@ func (ml *MessageList) Store() *lib.MessageStore {
func (ml *MessageList) Empty() bool { func (ml *MessageList) Empty() bool {
store := ml.Store() store := ml.Store()
return store == nil || len(store.Uids) == 0 return store == nil || len(store.Uids()) == 0
} }
func (ml *MessageList) Selected() *models.MessageInfo { func (ml *MessageList) Selected() *models.MessageInfo {
store := ml.Store() 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) { func (ml *MessageList) Select(index int) {
@ -189,7 +192,7 @@ func (ml *MessageList) Select(index int) {
func (ml *MessageList) Scroll() { func (ml *MessageList) Scroll() {
store := ml.Store() store := ml.Store()
if store == nil || len(store.Uids) == 0 { if store == nil || len(store.Uids()) == 0 {
return return
} }
if ml.Height() != 0 { if ml.Height() != 0 {