From bb9ac43e13a96dc5bc501f60c3afb3718fe8085d Mon Sep 17 00:00:00 2001
From: Robin Jarry <robin@jarry.cc>
Date: Mon, 19 Sep 2022 23:58:24 +0200
Subject: [PATCH] switch: add -n and -p flags for relative switch

Allow switching to next or previous account with switch-account -n and
switch-account -p, respectively. By default, these are bound to Alt-n
and Alt-p.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Koni Marti <koni.marti@gmail.com>
---
 CHANGELOG.md               |  1 +
 commands/compose/switch.go | 40 ++++++++++++++++++++++++++++++++------
 config/binds.conf          |  2 ++
 doc/aerc.1.scd             |  9 ++++++---
 widgets/aerc.go            | 36 ++++++++++++++++++++++++++++++++++
 5 files changed, 79 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f044039..83e3eb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Cursor shape support in embedded terminals.
 - Bracketed paste support.
 - Display current directory in `status-line.render-format` with `%p`.
+- Change accounts while composing a message with `:switch-account`.
 
 ### Changed
 
diff --git a/commands/compose/switch.go b/commands/compose/switch.go
index 5109ee0..58702b5 100644
--- a/commands/compose/switch.go
+++ b/commands/compose/switch.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 
 	"git.sr.ht/~rjarry/aerc/widgets"
+	"git.sr.ht/~sircmpwn/getopt"
 )
 
 type AccountSwitcher interface {
@@ -26,12 +27,29 @@ func (SwitchAccount) Complete(aerc *widgets.Aerc, args []string) []string {
 }
 
 func (SwitchAccount) Execute(aerc *widgets.Aerc, args []string) error {
-	if len(args) != 2 {
+	opts, optind, err := getopt.Getopts(args, "np")
+	if err != nil {
+		return err
+	}
+	var next, prev bool
+	for _, opt := range opts {
+		switch opt.Option {
+		case 'n':
+			next = true
+			prev = false
+		case 'p':
+			next = false
+			prev = true
+		}
+	}
+	posargs := args[optind:]
+	// NOT ((prev || next) XOR (len(posargs) == 1))
+	if (prev || next) == (len(posargs) == 1) {
 		name := ""
 		if acct := aerc.SelectedAccount(); acct != nil {
 			name = fmt.Sprintf("Current account: %s. ", acct.Name())
 		}
-		return errors.New(name + "Usage: switch-account <account-name>")
+		return errors.New(name + "Usage: switch-account [-np] <account-name>")
 	}
 
 	switcher, ok := aerc.SelectedTabContent().(AccountSwitcher)
@@ -39,9 +57,19 @@ func (SwitchAccount) Execute(aerc *widgets.Aerc, args []string) error {
 		return errors.New("this tab cannot switch accounts")
 	}
 
-	if acct, err := aerc.Account(args[1]); err != nil {
-		return err
-	} else {
-		return switcher.SwitchAccount(acct)
+	var acct *widgets.AccountView
+
+	switch {
+	case prev:
+		acct, err = aerc.PrevAccount()
+	case next:
+		acct, err = aerc.NextAccount()
+	default:
+		acct, err = aerc.Account(posargs[0])
 	}
+	if err != nil {
+		return err
+	}
+
+	return switcher.SwitchAccount(acct)
 }
diff --git a/config/binds.conf b/config/binds.conf
index f46111b..9e23ab9 100644
--- a/config/binds.conf
+++ b/config/binds.conf
@@ -94,6 +94,8 @@ $noinherit = true
 $ex = <C-x>
 <C-k> = :prev-field<Enter>
 <C-j> = :next-field<Enter>
+<A-p> = :switch-account -p<Enter>
+<A-n> = :switch-account -n<Enter>
 <tab> = :next-field<Enter>
 <C-p> = :prev-tab<Enter>
 <C-n> = :next-tab<Enter>
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index f0ca97e..38862a5 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -476,9 +476,12 @@ message list, the message in the message viewer, etc).
 	configuration. For details on configuring outgoing mail delivery consult
 	*aerc-config*(5).
 
-*switch-account* <account-name>
-	Switches the account. If run without arguments, the current account name
-	is displayed.
+*switch-account* <-n|-p|account-name>
+	Switches the account. Can be used to switch to a specific account from
+	its name or to cycle through accounts using the -p and -n flags.
+
+	*-p*: switch to previous account
+	*-n*: switch to next account
 
 *header* [-f] <field> [value]
 	Add a new email header. If the header already exists, -f must be
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 656b25d..2b7a6d8 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -382,6 +382,42 @@ func (aerc *Aerc) Account(name string) (*AccountView, error) {
 	return nil, fmt.Errorf("account <%s> not found", name)
 }
 
+func (aerc *Aerc) PrevAccount() (*AccountView, error) {
+	cur := aerc.SelectedAccount()
+	if cur == nil {
+		return nil, fmt.Errorf("no account selected, cannot get prev")
+	}
+	for i, conf := range aerc.conf.Accounts {
+		if conf.Name == cur.Name() {
+			i -= 1
+			if i == -1 {
+				i = len(aerc.conf.Accounts) - 1
+			}
+			conf = aerc.conf.Accounts[i]
+			return aerc.Account(conf.Name)
+		}
+	}
+	return nil, fmt.Errorf("no prev account")
+}
+
+func (aerc *Aerc) NextAccount() (*AccountView, error) {
+	cur := aerc.SelectedAccount()
+	if cur == nil {
+		return nil, fmt.Errorf("no account selected, cannot get next")
+	}
+	for i, conf := range aerc.conf.Accounts {
+		if conf.Name == cur.Name() {
+			i += 1
+			if i == len(aerc.conf.Accounts) {
+				i = 0
+			}
+			conf = aerc.conf.Accounts[i]
+			return aerc.Account(conf.Name)
+		}
+	}
+	return nil, fmt.Errorf("no next account")
+}
+
 func (aerc *Aerc) AccountNames() []string {
 	results := make([]string, 0)
 	for name := range aerc.accounts {