~sircmpwn/aerc

f81e4bd41c3ba9427390eadfc5133ed8daada6ab — Kevin Kuehler 2 years ago 8b2abcb
Implement :filter, :clear

Signed-off-by: Kevin Kuehler <keur@ocf.berkeley.edu>
M commands/account/cf.go => commands/account/cf.go +6 -0
@@ 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

A commands/account/clear.go => commands/account/clear.go +34 -0
@@ 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
}

M commands/account/search.go => commands/account/search.go +21 -9
@@ 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
}

M config/binds.conf => config/binds.conf +1 -0
@@ 43,6 43,7 @@ $ = :term<space>
| = :pipe<space>

/ = :search<space>
\ = :filter<space>
n = :next-result<Enter>
N = :prev-result<Enter>


M doc/aerc.1.scd => doc/aerc.1.scd +22 -4
@@ 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).

M lib/msgstore.go => lib/msgstore.go +33 -14
@@ 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
		}
	}

M widgets/account.go => widgets/account.go +1 -1
@@ 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

M widgets/msglist.go => widgets/msglist.go +16 -13
@@ 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 {