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 {
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

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 {
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")
}
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...")
store.Search(criteria, func(uids []uint32) {
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
}

View file

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

View file

@ -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* <folder>
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] <terms...>
Similar to *search*, but filters the displayed messages to only the search
results. See the documentation for *search* for more details.
*mkdir* <name>
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>[%]
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* <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>
Selects the nth message in the message list (and scrolls it into view if
necessary).

View file

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

View file

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

View file

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