about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--commands/account/cf.go6
-rw-r--r--commands/account/clear.go34
-rw-r--r--commands/account/search.go30
-rw-r--r--config/binds.conf1
-rw-r--r--doc/aerc.1.scd26
-rw-r--r--lib/msgstore.go47
-rw-r--r--widgets/account.go2
-rw-r--r--widgets/msglist.go29
8 files changed, 134 insertions, 41 deletions
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<space>
 | = :pipe<space>
 
 / = :search<space>
+\ = :filter<space>
 n = :next-result<Enter>
 N = :prev-result<Enter>
 
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* <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).
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 {