Implement :filter, :clear
Signed-off-by: Kevin Kuehler <keur@ocf.berkeley.edu>
This commit is contained in:
parent
8b2abcb02a
commit
f81e4bd41c
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue