~sircmpwn/aerc

Begin staticcheck v1 PROPOSED

Tamás Gulácsi
Tamás Gulácsi: 3
 Begin staticcheck
 Make staticcheck happy
 Make golangci-lint happy

 52 files changed, 200 insertions(+), 246 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/23204/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH 1/3] Begin staticcheck Export this patch

Tamás Gulácsi
From: Tamás Gulácsi <tamas@gulacsi.eu>

Signed-off-by: Tamás Gulácsi <T.Gulacsi@unosoft.hu>
---
 commands/compose/send.go |  2 +-
 commands/msg/delete.go   | 14 ++++++--------
 commands/msg/recall.go   |  2 --
 commands/msgview/open.go |  2 +-
 commands/msgview/save.go |  5 +----
 lib/socket.go            |  2 +-
 lib/ui/borders.go        |  7 +++----
 lib/ui/tab.go            |  1 -
 8 files changed, 13 insertions(+), 22 deletions(-)

diff --git a/commands/compose/send.go b/commands/compose/send.go
index 849182d..25d2aed 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -189,7 +189,7 @@ func newSendmailSender(ctx sendCtx) (io.WriteCloser, error) {
		return nil, fmt.Errorf("no command specified")
	}
	bin := args[0]
	rs := make([]string, len(ctx.rcpts), len(ctx.rcpts))
	rs := make([]string, len(ctx.rcpts))
	for i := range ctx.rcpts {
		rs[i] = ctx.rcpts[i].Address
	}
diff --git a/commands/msg/delete.go b/commands/msg/delete.go
index 677a666..3d6e07c 100644
--- a/commands/msg/delete.go
+++ b/commands/msg/delete.go
@@ -88,15 +88,13 @@ func findNextNonDeleted(deleted []uint32, store *lib.MessageStore) *models.Messa
	if !contains(deleted, selected.Uid) {
		return selected
	}
	for {
		store.Next()
		next := store.Selected()
		if next == selected || next == nil {
			// the last message is in the deleted state or doesn't exist
			return nil
		}
		return next
	store.Next()
	next := store.Selected()
	if next == selected || next == nil {
		// the last message is in the deleted state or doesn't exist
		return nil
	}
	return next
}

func contains(uids []uint32, uid uint32) bool {
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index 44f8ddf..61b7ca6 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -90,8 +90,6 @@ func (Recall) Execute(aerc *widgets.Aerc, args []string) error {
					composer.Close()
				}
			})

			return
		})
	}

diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index 57e7227..4fd1dab 100644
--- a/commands/msgview/open.go
+++ b/commands/msgview/open.go
@@ -37,7 +37,7 @@ func (Open) Execute(aerc *widgets.Aerc, args []string) error {
		if part, err := p.Msg.BodyStructure.PartAtIndex(p.Index); err == nil {
			mimeType := fmt.Sprintf("%s/%s", part.MIMEType, part.MIMESubType)

			if exts, _ := mime.ExtensionsByType(mimeType); exts != nil && len(exts) > 0 {
			if exts, _ := mime.ExtensionsByType(mimeType); len(exts) != 0 {
				extension = exts[0]
			}
		}
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 5d01e4d..568a73f 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.go
@@ -151,10 +151,7 @@ func isDirExists(path string) bool {
//pathExists returns true if path exists
func pathExists(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		return false // we don't really care why it failed
	}
	return true
	return err == nil // we don't really care why it failed
}

//isAbsPath returns true if path given is anchored to / or . or ~
diff --git a/lib/socket.go b/lib/socket.go
index cdf0f73..4f61cdb 100644
--- a/lib/socket.go
+++ b/lib/socket.go
@@ -80,7 +80,7 @@ func (as *AercServer) handleClient(conn net.Conn) {
			if err != nil {
				conn.Write([]byte(fmt.Sprintf("result: %v\n", err)))
			} else {
				conn.Write([]byte(fmt.Sprint("result: success\n")))
				conn.Write([]byte("result: success\n"))
			}
		}
	}
diff --git a/lib/ui/borders.go b/lib/ui/borders.go
index fd49e7f..5767e70 100644
--- a/lib/ui/borders.go
+++ b/lib/ui/borders.go
@@ -15,10 +15,9 @@ const (

type Bordered struct {
	Invalidatable
	borders      uint
	content      Drawable
	onInvalidate func(d Drawable)
	uiConfig     config.UIConfig
	borders  uint
	content  Drawable
	uiConfig config.UIConfig
}

func NewBordered(
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index 2cd8828..2c0607a 100644
--- a/lib/ui/tab.go
+++ b/lib/ui/tab.go
@@ -28,7 +28,6 @@ type Tabs struct {
type Tab struct {
	Content        Drawable
	Name           string
	invalid        bool
	pinned         bool
	indexBeforePin int
}
-- 
2.30.2

[PATCH 2/3] Make staticcheck happy Export this patch

Tamás Gulácsi
Signed-off-by: Tamás Gulácsi <T.Gulacsi@unosoft.hu>
---
 widgets/account-wizard.go |  3 +--
 widgets/aerc.go           |  3 ---
 widgets/common.go         | 20 --------------------
 widgets/compose.go        |  8 --------
 widgets/dirlist.go        | 13 -------------
 widgets/msgviewer.go      |  5 +----
 widgets/terminal.go       |  4 ++--
 worker/imap/open.go       |  6 ++++++
 worker/imap/worker.go     |  9 +++++++++
 worker/lib/parse.go       |  6 +++---
 10 files changed, 22 insertions(+), 55 deletions(-)
 delete mode 100644 widgets/common.go

diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go
index d8e2eb0..bcdb7a6 100644
--- a/widgets/account-wizard.go
+++ b/widgets/account-wizard.go
@@ -46,7 +46,6 @@ type AccountWizard struct {
	steps     []*ui.Grid
	focus     int
	temporary bool
	testing   bool
	// CONFIGURE_BASICS
	accountName *ui.TextInput
	email       *ui.TextInput
@@ -194,7 +193,7 @@ func NewAccountWizard(conf *config.AercConfig, aerc *Aerc) *AccountWizard {
					}
					wizard.imapUri()
				}
				hostport, srv = getSRV(server, []string{"submission"})
				hostport, _ = getSRV(server, []string{"submission"})
				if hostport != "" {
					wizard.smtpServer.Set(hostport)
					wizard.smtpMode = SMTP_STARTTLS
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 6df0c95..bfd3762 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -577,13 +577,11 @@ func (aerc *Aerc) AddDialog(d ui.DrawableInteractive) {
		aerc.Invalidate()
	})
	aerc.Invalidate()
	return
}

func (aerc *Aerc) CloseDialog() {
	aerc.dialog = nil
	aerc.Invalidate()
	return
}

func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string, chErr chan error) {
@@ -601,7 +599,6 @@ func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string,
		}
		chErr <- nil
		chText <- pw
		return
	})
	aerc.AddDialog(getPasswd)

diff --git a/widgets/common.go b/widgets/common.go
deleted file mode 100644
index e1cee05..0000000
--- a/widgets/common.go
@@ -1,20 +0,0 @@
package widgets

import (
	"fmt"

	"git.sr.ht/~sircmpwn/aerc/lib"
	"git.sr.ht/~sircmpwn/aerc/models"
)

func msgInfoFromUids(store *lib.MessageStore, uids []uint32) ([]*models.MessageInfo, error) {
	infos := make([]*models.MessageInfo, len(uids))
	for i, uid := range uids {
		var ok bool
		infos[i], ok = store.Messages[uid]
		if !ok {
			return nil, fmt.Errorf("uid not found")
		}
	}
	return infos, nil
}
diff --git a/widgets/compose.go b/widgets/compose.go
index 01c6281..bfa9599 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -756,19 +756,11 @@ func (he *headerEditor) storeValue() {
			// fix the issue
			he.header.SetText(he.name, val)
		}
		val = format.FormatAddresses(list)
	default:
		he.header.SetText(he.name, val)
	}
}

//setValue overwrites the current value of the header editor and flushes it
//to the underlying header
func (he *headerEditor) setValue(val string) {
	he.input.Set(val)
	he.storeValue()
}

func (he *headerEditor) Draw(ctx *ui.Context) {
	name := textproto.CanonicalMIMEHeaderKey(he.name)
	// Extra character to put a blank cell between the header and the input
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index 75a3297..e8876a0 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -12,7 +12,6 @@ import (

	"git.sr.ht/~sircmpwn/aerc/config"
	"git.sr.ht/~sircmpwn/aerc/lib"
	libsort "git.sr.ht/~sircmpwn/aerc/lib/sort"
	"git.sr.ht/~sircmpwn/aerc/lib/ui"
	"git.sr.ht/~sircmpwn/aerc/models"
	"git.sr.ht/~sircmpwn/aerc/worker/types"
@@ -455,18 +454,6 @@ func findString(slice []string, str string) int {
	return -1
}

func (dirlist *DirectoryList) getSortCriteria() []*types.SortCriterion {
	if len(dirlist.UiConfig().Sort) == 0 {
		return nil
	}
	criteria, err := libsort.GetSortCriteria(dirlist.UiConfig().Sort)
	if err != nil {
		dirlist.logger.Printf("getSortCriteria failed: %v", err)
		return nil
	}
	return criteria
}

func countRUE(msgStore *lib.MessageStore) (recent, unread int) {
	for _, msg := range msgStore.Messages {
		if msg == nil {
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 216a8c5..266c4e6 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -54,10 +54,7 @@ func NewMessageViewer(acct *AccountView,
	hf := HeaderLayoutFilter{
		layout: HeaderLayout(conf.Viewer.HeaderLayout),
		keep: func(msg *models.MessageInfo, header string) bool {
			if fmtHeader(msg, header, "2") != "" {
				return true
			}
			return false
			return fmtHeader(msg, header, "2") != ""
		},
	}
	layout := hf.forMessage(msg.MessageInfo())
diff --git a/widgets/terminal.go b/widgets/terminal.go
index 920c312..8117bf7 100644
--- a/widgets/terminal.go
+++ b/widgets/terminal.go
@@ -133,7 +133,7 @@ func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
				return
			}
			term.writeMutex.Lock()
			n, err = term.vterm.Write(buf[:n])
			_, err = term.vterm.Write(buf[:n])
			term.writeMutex.Unlock()
			if err != nil {
				term.Close(err)
@@ -163,7 +163,7 @@ func (term *Terminal) flushTerminal() {
		if n == 0 {
			break
		}
		n, err = term.pty.Write(buf[:n])
		_, err = term.pty.Write(buf[:n])
		if err != nil {
			term.Close(err)
			return
diff --git a/worker/imap/open.go b/worker/imap/open.go
index 891b8a2..ae942ef 100644
--- a/worker/imap/open.go
+++ b/worker/imap/open.go
@@ -1,6 +1,8 @@
package imap

import (
	"strings"

	"github.com/emersion/go-imap"
	sortthread "github.com/emersion/go-imap-sortthread"

@@ -55,10 +57,14 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
		uids, err = imapw.client.UidSearch(searchCriteria)
	}
	if err != nil {
		imapw.worker.Logger.Printf("FetchDirectory error: %+v", err)
		imapw.worker.PostMessage(&types.Error{
			Message: types.RespondTo(msg),
			Error:   err,
		}, nil)
		if strings.HasSuffix(err.Error(), "broken pipe") {
			imapw.worker.PostAction(&types.Connect{}, nil)
		}
	} else {
		imapw.worker.Logger.Printf("Found %d UIDs", len(uids))
		if len(imapw.seqMap) < len(uids) {
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index dab0afb..c1c6109 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -64,6 +64,10 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
	if w.idleStop != nil {
		close(w.idleStop)
		if err := <-w.idleDone; err != nil {
			w.worker.Logger.Printf("IMAPWorker(%p) idleDone got %+v", w, err)
			if err.Error() == "imap: connection closed" {
				w.worker.PostAction(&types.Connect{}, nil)
			}
			w.worker.PostMessage(&types.Error{Error: err}, nil)
		}
	}
@@ -73,6 +77,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
	switch msg := msg.(type) {
	case *types.Unsupported:
		// No-op
	case *types.Error:
		w.worker.Logger.Printf("IMAPWorker(%p) got %#v", w, msg)
	case *types.Configure:
		u, err := url.Parse(msg.Config.Source)
		if err != nil {
@@ -157,6 +163,9 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
		}

		c.Updates = w.updates
		if w.client != nil {
			w.client.Close()
		}
		w.client = &imapClient{c, idle.NewClient(c), sortthread.NewSortClient(c)}
		w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
	case *types.ListDirectories:
diff --git a/worker/lib/parse.go b/worker/lib/parse.go
index efdb60f..e07bdf0 100644
--- a/worker/lib/parse.go
+++ b/worker/lib/parse.go
@@ -102,7 +102,7 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
	return &body, nil
}

var DateParseError = errors.New("date parsing failed")
var ErrDateParse = errors.New("date parsing failed")

func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
	from, err := parseAddressList(h, "from")
@@ -141,7 +141,7 @@ func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
	if err != nil {
		// still return a valid struct plus a sentinel date parsing error
		// if only the date parsing failed
		err = fmt.Errorf("%w: %v", DateParseError, err)
		err = fmt.Errorf("%w: %v", ErrDateParse, err)
	}
	return &models.Envelope{
		Date:      date,
@@ -237,7 +237,7 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
	}
	h := &mail.Header{msg.Header}
	env, err := parseEnvelope(h)
	if err != nil && !errors.Is(err, DateParseError) {
	if err != nil && !errors.Is(err, ErrDateParse) {
		return nil, fmt.Errorf("could not parse envelope: %v", err)
		// if only the date parsing failed we still get the rest of the
		// envelop structure in a valid state.
-- 
2.30.2

[PATCH 3/3] Make golangci-lint happy Export this patch

Tamás Gulácsi
Signed-off-by: Tamás Gulácsi <T.Gulacsi@unosoft.hu>
---
 aerc.go                      |  7 +++--
 commands/compose/postpone.go |  2 +-
 commands/msg/forward.go      |  6 ++--
 commands/msg/read.go         | 12 +++++---
 commands/msg/recall.go       |  2 +-
 commands/msg/reply.go        |  2 +-
 completer/completer.go       |  9 +++---
 config/bindings.go           |  4 +--
 config/config.go             | 19 ++++++------
 config/style.go              |  8 +++--
 config/triggers.go           |  2 +-
 lib/oauthbearer.go           |  3 +-
 lib/sort/sort.go             |  2 +-
 lib/structure_helpers.go     |  7 ++---
 widgets/account-wizard.go    | 59 ++++++++++++------------------------
 widgets/aerc.go              |  8 ++---
 widgets/compose.go           | 18 ++++++-----
 widgets/msgviewer.go         | 14 +++++----
 widgets/pgpinfo.go           |  8 ++---
 widgets/terminal.go          |  4 +--
 worker/imap/create.go        |  2 +-
 worker/imap/fetch.go         | 12 ++++----
 worker/imap/flags.go         | 11 ++++---
 worker/imap/imap.go          | 14 ++++-----
 worker/imap/list.go          |  2 +-
 worker/imap/movecopy.go      |  4 +--
 worker/imap/open.go          |  6 ++--
 worker/imap/remove.go        |  2 +-
 worker/imap/worker.go        |  5 +--
 worker/lib/parse.go          | 30 +++++++++---------
 worker/lib/sort.go           | 16 +++++-----
 worker/maildir/container.go  | 14 ++++-----
 worker/maildir/message.go    |  4 +--
 worker/maildir/worker.go     | 16 +++++-----
 34 files changed, 165 insertions(+), 169 deletions(-)

diff --git a/aerc.go b/aerc.go
index 33acb43..c08089d 100644
--- a/aerc.go
+++ b/aerc.go
@@ -1,6 +1,7 @@
package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
@@ -59,12 +60,14 @@ func execCommand(aerc *widgets.Aerc, ui *libui.UI, cmd []string) error {
	cmds := getCommands((*aerc).SelectedTab())
	for i, set := range cmds {
		err := set.ExecuteCommand(aerc, cmd)
		if _, ok := err.(commands.NoSuchCommand); ok {
		var nsc commands.NoSuchCommand
		var ee commands.ErrorExit
		if errors.As(err, &nsc) {
			if i == len(cmds)-1 {
				return err
			}
			continue
		} else if _, ok := err.(commands.ErrorExit); ok {
		} else if errors.As(err, &ee) {
			ui.Exit()
			return nil
		} else if err != nil {
diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
index 6d6c8e7..566f2d7 100644
--- a/commands/compose/postpone.go
+++ b/commands/compose/postpone.go
@@ -82,7 +82,7 @@ func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
			Flags:       []models.Flag{models.SeenFlag},
			Date:        time.Now(),
			Reader:      r,
			Length:      int(nbytes),
			Length:      nbytes,
		}, func(msg types.WorkerMessage) {
			switch msg := msg.(type) {
			case *types.Done:
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index ec89bfa..45b64d1 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -34,6 +34,8 @@ func (forward) Complete(aerc *widgets.Aerc, args []string) []string {
	return nil
}

const cNewEmail = "New email"

func (forward) Execute(aerc *widgets.Aerc, args []string) error {
	opts, optind, err := getopt.Getopts(args, "AT:")
	if err != nil {
@@ -73,7 +75,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
		to := strings.Join(args[optind:], ", ")
		tolist, err := mail.ParseAddressList(to)
		if err != nil {
			return fmt.Errorf("invalid to address(es): %v", err)
			return fmt.Errorf("invalid to address(es): %w", err)
		}
		h.SetAddressList("to", tolist)
	}
@@ -100,7 +102,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
		}
		composer.OnHeaderChange("Subject", func(subject string) {
			if subject == "" {
				tab.Name = "New email"
				tab.Name = cNewEmail
			} else {
				tab.Name = subject
			}
diff --git a/commands/msg/read.go b/commands/msg/read.go
index 95becf7..347ed22 100644
--- a/commands/msg/read.go
+++ b/commands/msg/read.go
@@ -20,13 +20,15 @@ func init() {
}

func (FlagMsg) Aliases() []string {
	return []string{"flag", "unflag", "read", "unread"}
	return []string{"flag", "unflag", cRead, "unread"}
}

func (FlagMsg) Complete(aerc *widgets.Aerc, args []string) []string {
	return nil
}

const cRead = "read"

// If this was called as 'flag' or 'unflag', without the toggle (-t)
// option, then it will flag the corresponding messages with the given
// flag.  If the toggle option was given, it will individually toggle
@@ -43,20 +45,20 @@ func (FlagMsg) Execute(aerc *widgets.Aerc, args []string) error {
	// Whether to toggle the flag (true) or to enable/disable it (false)
	var toggle bool
	// Whether to enable (true) or disable (false) the flag
	enable := (args[0] == "read" || args[0] == "flag")
	enable := (args[0] == cRead || args[0] == "flag")
	// User-readable name for the action being performed
	var actionName string
	// Getopt option string, varies by command name
	var getoptString string
	// Help message to provide on parsing failure
	var helpMessage string
	// Used during parsing to prevent choosing a flag muliple times
	// Used during parsing to prevent choosing a flag multiple times
	// A default flag will be used if this is false
	flagChosen := false

	if args[0] == "read" || args[0] == "unread" {
	if args[0] == cRead || args[0] == "unread" {
		flag = models.SeenFlag
		flagName = "read"
		flagName = cRead
		getoptString = "t"
		helpMessage = "Usage: " + args[0] + " [-t]"
	} else { // 'flag' / 'unflag'
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index 61b7ca6..5de0277 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -71,7 +71,7 @@ func (Recall) Execute(aerc *widgets.Aerc, args []string) error {
		tab := aerc.NewTab(composer, subject)
		composer.OnHeaderChange("Subject", func(subject string) {
			if subject == "" {
				tab.Name = "New email"
				tab.Name = cNewEmail
			} else {
				tab.Name = subject
			}
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 2e4a21a..0b36513 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -179,7 +179,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
		tab := aerc.NewTab(composer, subject)
		composer.OnHeaderChange("Subject", func(subject string) {
			if subject == "" {
				tab.Name = "New email"
				tab.Name = cNewEmail
			} else {
				tab.Name = subject
			}
diff --git a/completer/completer.go b/completer/completer.go
index bc6c96f..846ea92 100644
--- a/completer/completer.go
+++ b/completer/completer.go
@@ -2,6 +2,7 @@ package completer

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
@@ -82,14 +83,14 @@ func (c *Completer) completeAddress(s string) ([]string, error) {
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("stdout: %v", err)
		return nil, fmt.Errorf("stdout: %w", err)
	}
	if err := cmd.Start(); err != nil {
		return nil, fmt.Errorf("cmd start: %v", err)
		return nil, fmt.Errorf("cmd start: %w", err)
	}
	completions, err := readCompletions(stdout)
	if err != nil {
		return nil, fmt.Errorf("read completions: %v", err)
		return nil, fmt.Errorf("read completions: %w", err)
	}

	// Wait returns an error if the exit status != 0, which some completion
@@ -132,7 +133,7 @@ func readCompletions(r io.Reader) ([]string, error) {
	completions := []string{}
	for {
		line, err := buf.ReadString('\n')
		if err == io.EOF {
		if errors.Is(err, io.EOF) {
			return completions, nil
		} else if err != nil {
			return nil, err
diff --git a/config/bindings.go b/config/bindings.go
index 9956b41..c7530c9 100644
--- a/config/bindings.go
+++ b/config/bindings.go
@@ -101,7 +101,7 @@ func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) {
	buf := bytes.NewBufferString(keystrokes)
	for {
		tok, _, err := buf.ReadRune()
		if err == io.EOF {
		if errors.Is(err, io.EOF) {
			break
		} else if err != nil {
			return nil, err
@@ -111,7 +111,7 @@ func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) {
		switch tok {
		case '<':
			name, err := buf.ReadString(byte('>'))
			if err == io.EOF {
			if errors.Is(err, io.EOF) {
				return nil, errors.New("Expecting '>'")
			} else if err != nil {
				return nil, err
diff --git a/config/config.go b/config/config.go
index af9c63b..9063722 100644
--- a/config/config.go
+++ b/config/config.go
@@ -128,7 +128,7 @@ type TriggersConfig struct {
}

type TemplateConfig struct {
	TemplateDirs []string `ini:"template-dirs", delim:":"`
	TemplateDirs []string `ini:"template-dirs" delim:":"`
	QuotedReply  string   `ini:"quoted-reply"`
	Forwards     string   `ini:"forwards"`
}
@@ -170,8 +170,9 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
	}
	file.NameMapper = mapName

	var accounts []AccountConfig
	for _, _sec := range file.SectionStrings() {
	sects := file.SectionStrings()
	accounts := make([]AccountConfig, 0, len(sects))
	for _, _sec := range sects {
		if _sec == "DEFAULT" {
			continue
		}
@@ -222,13 +223,13 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {

		source, err := parseCredential(account.Source, account.SourceCredCmd)
		if err != nil {
			return nil, fmt.Errorf("Invalid source credentials for %s: %s", _sec, err)
			return nil, fmt.Errorf("Invalid source credentials for %s: %w", _sec, err)
		}
		account.Source = source

		outgoing, err := parseCredential(account.Outgoing, account.OutgoingCredCmd)
		if err != nil {
			return nil, fmt.Errorf("Invalid outgoing credentials for %s: %s", _sec, err)
			return nil, fmt.Errorf("Invalid outgoing credentials for %s: %w", _sec, err)
		}
		account.Outgoing = outgoing

@@ -261,7 +262,7 @@ func parseCredential(cred, command string) (string, error) {
	cmd.Stdin = os.Stdin
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("failed to read password: %s", err)
		return "", fmt.Errorf("failed to read password: %w", err)
	}

	pw := strings.TrimSpace(string(output))
@@ -371,14 +372,14 @@ func (config *AercConfig) LoadConfig(file *ini.File) error {
		var index int
		if strings.Contains(sectionName, "~") {
			index = strings.Index(sectionName, "~")
			regex := string(sectionName[index+1:])
			regex := sectionName[index+1:]
			contextualUi.Regex, err = regexp.Compile(regex)
			if err != nil {
				return err
			}
		} else if strings.Contains(sectionName, "=") {
			index = strings.Index(sectionName, "=")
			value := string(sectionName[index+1:])
			value := sectionName[index+1:]
			contextualUi.Regex, err = regexp.Compile(regexp.QuoteMeta(value))
			if err != nil {
				return err
@@ -663,7 +664,7 @@ func (ui *UIConfig) loadStyleSet(styleSetDirs []string) error {
	ui.style = NewStyleSet()
	err := ui.style.LoadStyleSet(ui.StyleSetName, styleSetDirs)
	if err != nil {
		return fmt.Errorf("Unable to load default styleset: %s", err)
		return fmt.Errorf("Unable to load default styleset: %w", err)
	}

	return nil
diff --git a/config/style.go b/config/style.go
index c3e8503..67a992b 100644
--- a/config/style.go
+++ b/config/style.go
@@ -277,6 +277,8 @@ func findStyleSet(stylesetName string, stylesetsDir []string) (string, error) {
		"Can't find styleset %q in any of %v", stylesetName, stylesetsDir)
}

const cSelected = "selected"

func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
	ss.reset()

@@ -294,7 +296,7 @@ func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
		case 2:
			styleName, attr = tokens[0], tokens[1]
		case 3:
			if tokens[1] != "selected" {
			if tokens[1] != cSelected {
				return errors.New("Unknown modifier: " + tokens[1])
			}
			selectedKeys = append(selectedKeys, key)
@@ -340,7 +342,7 @@ func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
	for _, key := range selectedKeys {
		tokens := strings.Split(key, ".")
		styleName, modifier, attr := tokens[0], tokens[1], tokens[2]
		if modifier != "selected" {
		if modifier != cSelected {
			return errors.New("Unknown modifier: " + modifier)
		}

@@ -378,7 +380,7 @@ func (ss *StyleSet) ParseStyleSet(file *ini.File) error {
		styleName, attr := tokens[0], tokens[1]
		val := defaultSection.KeysHash()[key]

		if styleName != "selected" {
		if styleName != cSelected {
			continue
		}

diff --git a/config/triggers.go b/config/triggers.go
index 847a10f..7527cfc 100644
--- a/config/triggers.go
+++ b/config/triggers.go
@@ -21,7 +21,7 @@ func (trig *TriggersConfig) ExecTrigger(triggerCmd string,
		return err
	}

	var command []string
	command := make([]string, 0, len(triggerCmdParts))
	for _, part := range triggerCmdParts {
		formattedPart, err := triggerFmt(part)
		if err != nil {
diff --git a/lib/oauthbearer.go b/lib/oauthbearer.go
index 1030696..063866b 100644
--- a/lib/oauthbearer.go
+++ b/lib/oauthbearer.go
@@ -3,6 +3,7 @@ package lib
import (
	"context"
	"fmt"

	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-sasl"
	"golang.org/x/oauth2"
@@ -22,7 +23,7 @@ func (c *OAuthBearer) ExchangeRefreshToken(refreshToken string) (*oauth2.Token,

func (c *OAuthBearer) Authenticate(username string, password string, client *client.Client) error {
	if ok, err := client.SupportAuth(sasl.OAuthBearer); err != nil || !ok {
		return fmt.Errorf("OAuthBearer not supported %v", err)
		return fmt.Errorf("OAuthBearer not supported %w", err)
	}

	if c.OAuth2.Endpoint.TokenURL != "" {
diff --git a/lib/sort/sort.go b/lib/sort/sort.go
index 840f77e..2a58f70 100644
--- a/lib/sort/sort.go
+++ b/lib/sort/sort.go
@@ -10,7 +10,7 @@ import (
)

func GetSortCriteria(args []string) ([]*types.SortCriterion, error) {
	var sortCriteria []*types.SortCriterion
	sortCriteria := make([]*types.SortCriterion, 0, len(args))
	reverse := false
	for _, arg := range args {
		if arg == "-r" {
diff --git a/lib/structure_helpers.go b/lib/structure_helpers.go
index 4abf304..8fb4d48 100644
--- a/lib/structure_helpers.go
+++ b/lib/structure_helpers.go
@@ -28,10 +28,9 @@ func FindFirstNonMultipart(bs *models.BodyStructure, path []int) []int {
		mimetype := strings.ToLower(part.MIMEType)
		if mimetype != "multipart" {
			return cur
		} else if mimetype == "multipart" {
			if path := FindFirstNonMultipart(part, cur); path != nil {
				return path
			}
		}
		if path := FindFirstNonMultipart(part, cur); path != nil {
			return path
		}
	}
	return nil
diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go
index bcdb7a6..afd62f3 100644
--- a/widgets/account-wizard.go
+++ b/widgets/account-wizard.go
@@ -566,19 +566,7 @@ func (wizard *AccountWizard) finish(tutorial bool) {
	wizard.aerc.RemoveTab(wizard)
}

func (wizard *AccountWizard) imapUri() url.URL {
	host := wizard.imapServer.String()
	user := wizard.imapUsername.String()
	pass := wizard.imapPassword.String()
	var scheme string
	switch wizard.imapMode {
	case IMAP_OVER_TLS:
		scheme = "imaps"
	case IMAP_STARTTLS:
		scheme = "imap"
	case IMAP_INSECURE:
		scheme = "imap+insecure"
	}
func (wizard *AccountWizard) uriWithUserPassword(scheme, host, user, pass string) url.URL {
	var (
		userpass   *url.Userinfo
		userwopass *url.Userinfo
@@ -602,11 +590,26 @@ func (wizard *AccountWizard) imapUri() url.URL {
	}
	wizard.imapStr.Text("Connection URL: " +
		strings.ReplaceAll(clean.String(), "%2A", "*"))
	wizard.imapUrl = uri
	return uri
}

func (wizard *AccountWizard) smtpUri() url.URL {
func (wizard *AccountWizard) imapUri() {
	host := wizard.imapServer.String()
	user := wizard.imapUsername.String()
	pass := wizard.imapPassword.String()
	var scheme string
	switch wizard.imapMode {
	case IMAP_OVER_TLS:
		scheme = "imaps"
	case IMAP_STARTTLS:
		scheme = "imap"
	case IMAP_INSECURE:
		scheme = "imap+insecure"
	}
	wizard.imapUrl = wizard.uriWithUserPassword(scheme, host, user, pass)
}

func (wizard *AccountWizard) smtpUri() {
	host := wizard.smtpServer.String()
	user := wizard.smtpUsername.String()
	pass := wizard.smtpPassword.String()
@@ -619,31 +622,7 @@ func (wizard *AccountWizard) smtpUri() url.URL {
	case SMTP_INSECURE:
		scheme = "smtp+plain"
	}
	var (
		userpass   *url.Userinfo
		userwopass *url.Userinfo
	)
	if pass == "" {
		userpass = url.User(user)
		userwopass = userpass
	} else {
		userpass = url.UserPassword(user, pass)
		userwopass = url.UserPassword(user, strings.Repeat("*", len(pass)))
	}
	uri := url.URL{
		Scheme: scheme,
		Host:   host,
		User:   userpass,
	}
	clean := url.URL{
		Scheme: scheme,
		Host:   host,
		User:   userwopass,
	}
	wizard.smtpStr.Text("Connection URL: " +
		strings.ReplaceAll(clean.String(), "%2A", "*"))
	wizard.smtpUrl = uri
	return uri
	wizard.smtpUrl = wizard.uriWithUserPassword(scheme, host, user, pass)
}

func (wizard *AccountWizard) Invalidate() {
diff --git a/widgets/aerc.go b/widgets/aerc.go
index bfd3762..9591e9b 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -371,9 +371,9 @@ func (aerc *Aerc) SelectTabIndex(index int) bool {
}

func (aerc *Aerc) TabNames() []string {
	var names []string
	for _, tab := range aerc.tabs.Tabs {
		names = append(names, tab.Name)
	names := make([]string, len(aerc.tabs.Tabs))
	for i, tab := range aerc.tabs.Tabs {
		names[i] = tab.Name
	}
	return names
}
@@ -502,7 +502,7 @@ func (aerc *Aerc) Mailto(addr *url.URL) error {
	h := &mail.Header{}
	to, err := mail.ParseAddressList(addr.Opaque)
	if err != nil {
		return fmt.Errorf("Could not parse to: %v", err)
		return fmt.Errorf("Could not parse to: %w", err)
	}
	h.SetAddressList("to", to)
	for key, vals := range addr.Query() {
diff --git a/widgets/compose.go b/widgets/compose.go
index bfa9599..c9927fb 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -66,14 +66,14 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
	if h == nil {
		h = new(mail.Header)
	}
	if fl, err := h.AddressList("from"); err != nil || fl == nil {
	if fl, err := h.AddressList(cFrom); err != nil || fl == nil {
		fl, err = mail.ParseAddressList(acctConfig.From)
		// realistically this blows up way before us during the config loading
		if err != nil {
			return nil, err
		}
		if fl != nil {
			h.SetAddressList("from", fl)
			h.SetAddressList(cFrom, fl)

		}
	}
@@ -117,6 +117,8 @@ func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
	return c, nil
}

const cFrom = "from"

func (c *Composer) buildComposeHeader(aerc *Aerc, cmpl *completer.Completer) {

	c.layout = aerc.conf.Compose.HeaderLayout
@@ -134,7 +136,7 @@ func (c *Composer) buildComposeHeader(aerc *Aerc, cmpl *completer.Completer) {
			}
			c.editors[h] = e
			switch h {
			case "from":
			case cFrom:
				// Prepend From to support backtab
				c.focusable = append([]ui.MouseableDrawableInteractive{e}, c.focusable...)
			default:
@@ -201,7 +203,7 @@ func (c *Composer) AddTemplate(template string, data interface{}) error {

	mr, err := mail.CreateReader(templateText)
	if err != nil {
		return fmt.Errorf("Template loading failed: %v", err)
		return fmt.Errorf("Template loading failed: %w", err)
	}

	// copy the headers contained in the template to the compose headers
@@ -212,7 +214,7 @@ func (c *Composer) AddTemplate(template string, data interface{}) error {

	part, err := mr.NextPart()
	if err != nil {
		return fmt.Errorf("Could not get body of template: %v", err)
		return fmt.Errorf("Could not get body of template: %w", err)
	}

	c.AppendContents(part.Body)
@@ -479,7 +481,7 @@ func writeAttachment(path string, writer *mail.Writer) error {
		// Sniff the mime type instead
		// http.DetectContentType only cares about the first 512 bytes
		head, err := reader.Peek(512)
		if err != nil && err != io.EOF {
		if err != nil && !errors.Is(err, io.EOF) {
			return errors.Wrap(err, "Peek")
		}
		mimeString = http.DetectContentType(head)
@@ -720,7 +722,7 @@ func extractHumanHeaderValue(key string, h *mail.Header) string {
	var val string
	var err error
	switch strings.ToLower(key) {
	case "to", "from", "cc", "bcc":
	case "to", cFrom, "cc", "bcc":
		var list []*mail.Address
		list, err = h.AddressList(key)
		val = format.FormatAddresses(list)
@@ -747,7 +749,7 @@ func (he *headerEditor) loadValue() {
func (he *headerEditor) storeValue() {
	val := he.input.String()
	switch strings.ToLower(he.name) {
	case "to", "from", "cc", "bcc":
	case "to", cFrom, "cc", "bcc":
		list, err := mail.ParseAddressList(val)
		if err == nil {
			he.header.SetAddressList(he.name, list)
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 266c4e6..051e367 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -146,14 +146,16 @@ func fmtHeader(msg *models.MessageInfo, header string, timefmt string) string {
	}
}

const cMultipart = "multipart"

func enumerateParts(acct *AccountView, conf *config.AercConfig,
	msg lib.MessageView, body *models.BodyStructure,
	index []int) ([]*PartViewer, error) {

	var parts []*PartViewer
	parts := make([]*PartViewer, 0, len(body.Parts))
	for i, part := range body.Parts {
		curindex := append(index, i+1)
		if part.MIMEType == "multipart" {
		if part.MIMEType == cMultipart {
			// Multipart meta-parts are faked
			pv := &PartViewer{part: part}
			parts = append(parts, pv)
@@ -205,7 +207,7 @@ func createSwitcher(acct *AccountView, switcher *PartSwitcher,
				switcher.Invalidate()
			})
			// Switch to user's preferred mimetype
			if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
			if switcher.selected == -1 && pv.part.MIMEType != cMultipart {
				switcher.selected = i
			}
			mime := strings.ToLower(pv.part.MIMEType) +
@@ -301,7 +303,7 @@ func (mv *MessageViewer) PreviousPart() {
		if switcher.selected < 0 {
			switcher.selected = len(switcher.parts) - 1
		}
		if switcher.parts[switcher.selected].part.MIMEType != "multipart" {
		if switcher.parts[switcher.selected].part.MIMEType != cMultipart {
			break
		}
	}
@@ -315,7 +317,7 @@ func (mv *MessageViewer) NextPart() {
		if switcher.selected >= len(switcher.parts) {
			switcher.selected = 0
		}
		if switcher.parts[switcher.selected].part.MIMEType != "multipart" {
		if switcher.parts[switcher.selected].part.MIMEType != cMultipart {
			break
		}
	}
@@ -385,7 +387,7 @@ func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) {
				if localY != y+i {
					continue
				}
				if ps.parts[i].part.MIMEType == "multipart" {
				if ps.parts[i].part.MIMEType == cMultipart {
					continue
				}
				if ps.parts[ps.selected].term != nil {
diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go
index 94fb877..8c033ad 100644
--- a/widgets/pgpinfo.go
+++ b/widgets/pgpinfo.go
@@ -31,12 +31,12 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
		p.details.SignedBy == nil {

		x := ctx.Printf(0, 0, warningStyle, "*")
		x += ctx.Printf(x, 0, defaultStyle,
		ctx.Printf(x, 0, defaultStyle,
			" Signed with unknown key (%8X); authenticity unknown",
			p.details.SignedByKeyId)
	} else if p.details.SignatureError != nil {
		x := ctx.Printf(0, 0, errorStyle, "Invalid signature!")
		x += ctx.Printf(x, 0, errorStyle,
		ctx.Printf(x, 0, errorStyle,
			" This message may have been tampered with! (%s)",
			p.details.SignatureError.Error())
	} else {
@@ -44,7 +44,7 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
		ident := entity.PrimaryIdentity()

		x := ctx.Printf(0, 0, validStyle, "✓ Authentic ")
		x += ctx.Printf(x, 0, defaultStyle,
		ctx.Printf(x, 0, defaultStyle,
			"Signature from %s (%8X)",
			ident.Name, p.details.SignedByKeyId)
	}
@@ -57,7 +57,7 @@ func (p *PGPInfo) DrawEncryption(ctx *ui.Context, y int) {
	ident := entity.PrimaryIdentity()

	x := ctx.Printf(0, y, validStyle, "✓ Encrypted ")
	x += ctx.Printf(x, y, defaultStyle,
	ctx.Printf(x, y, defaultStyle,
		"To %s (%8X) ", ident.Name, p.details.DecryptedWith.PublicKey.KeyId)
}

diff --git a/widgets/terminal.go b/widgets/terminal.go
index 8117bf7..1fde629 100644
--- a/widgets/terminal.go
+++ b/widgets/terminal.go
@@ -407,7 +407,7 @@ func (term *Terminal) styleFromCell(cell *vterm.ScreenCell) tcell.Style {
	if background.IsDefaultBg() {
		bg = tcell.ColorDefault
	} else if background.IsIndexed() {
		bg = tcell.Color(tcell.PaletteColor(int(background.GetIndex())))
		bg = tcell.PaletteColor(int(background.GetIndex()))
	} else if background.IsRgb() {
		r, g, b := background.GetRGB()
		bg = tcell.NewRGBColor(int32(r), int32(g), int32(b))
@@ -415,7 +415,7 @@ func (term *Terminal) styleFromCell(cell *vterm.ScreenCell) tcell.Style {
	if foreground.IsDefaultFg() {
		fg = tcell.ColorDefault
	} else if foreground.IsIndexed() {
		fg = tcell.Color(tcell.PaletteColor(int(foreground.GetIndex())))
		fg = tcell.PaletteColor(int(foreground.GetIndex()))
	} else if foreground.IsRgb() {
		r, g, b := foreground.GetRGB()
		fg = tcell.NewRGBColor(int32(r), int32(g), int32(b))
diff --git a/worker/imap/create.go b/worker/imap/create.go
index 6ce71ef..d2113ac 100644
--- a/worker/imap/create.go
+++ b/worker/imap/create.go
@@ -14,6 +14,6 @@ func (imapw *IMAPWorker) handleCreateDirectory(msg *types.CreateDirectory) {
			Error:   err,
		}, nil)
	} else {
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}
diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go
index def0da8..5da8149 100644
--- a/worker/imap/fetch.go
+++ b/worker/imap/fetch.go
@@ -38,9 +38,9 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
			reader := _msg.GetBody(section)
			textprotoHeader, err := textproto.ReadHeader(bufio.NewReader(reader))
			if err != nil {
				return fmt.Errorf("could not read header: %v", err)
				return fmt.Errorf("could not read header: %w", err)
			}
			header := &mail.Header{message.Header{textprotoHeader}}
			header := &mail.Header{Header: message.Header{Header: textprotoHeader}}
			imapw.worker.PostMessage(&types.MessageInfo{
				Message: types.RespondTo(msg),
				Info: &models.MessageInfo{
@@ -91,13 +91,13 @@ func (imapw *IMAPWorker) handleFetchMessageBodyPart(
			headerReader := bufio.NewReader(_msg.GetBody(&partHeaderSection))
			h, err := textproto.ReadHeader(headerReader)
			if err != nil {
				return fmt.Errorf("failed to read part header: %v", err)
				return fmt.Errorf("failed to read part header: %w", err)
			}

			part, err := message.New(message.Header{h},
			part, err := message.New(message.Header{Header: h},
				_msg.GetBody(&partBodySection))
			if err != nil {
				return fmt.Errorf("failed to create message reader: %v", err)
				return fmt.Errorf("failed to create message reader: %w", err)
			}

			imapw.worker.PostMessage(&types.MessageBodyPart{
@@ -196,5 +196,5 @@ func (imapw *IMAPWorker) handleFetchMessages(
		return
	}
	imapw.worker.PostMessage(
		&types.Done{types.RespondTo(msg)}, nil)
		&types.Done{Message: types.RespondTo(msg)}, nil)
}
diff --git a/worker/imap/flags.go b/worker/imap/flags.go
index aef1019..4b4bde4 100644
--- a/worker/imap/flags.go
+++ b/worker/imap/flags.go
@@ -2,6 +2,7 @@ package imap

import (
	"fmt"

	"github.com/emersion/go-imap"

	"git.sr.ht/~sircmpwn/aerc/worker/types"
@@ -40,7 +41,7 @@ func (imapw *IMAPWorker) handleDeleteMessages(msg *types.DeleteMessages) {
			Message: types.RespondTo(msg),
			Uids:    deleted,
		}, nil)
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}

@@ -67,11 +68,11 @@ func (imapw *IMAPWorker) handleAnsweredMessages(msg *types.AnsweredMessages) {
	}, func(_msg types.WorkerMessage) {
		switch m := _msg.(type) {
		case *types.Error:
			err := fmt.Errorf("handleAnsweredMessages: %v", m.Error)
			err := fmt.Errorf("handleAnsweredMessages: %w", m.Error)
			imapw.worker.Logger.Printf("could not fetch headers: %s", err)
			emitErr(err)
		case *types.Done:
			imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
			imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
		}
	})
}
@@ -98,11 +99,11 @@ func (imapw *IMAPWorker) handleFlagMessages(msg *types.FlagMessages) {
	}, func(_msg types.WorkerMessage) {
		switch m := _msg.(type) {
		case *types.Error:
			err := fmt.Errorf("handleFlagMessages: %v", m.Error)
			err := fmt.Errorf("handleFlagMessages: %w", m.Error)
			imapw.worker.Logger.Printf("could not fetch headers: %s", err)
			emitErr(err)
		case *types.Done:
			imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
			imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
		}
	})
}
diff --git a/worker/imap/imap.go b/worker/imap/imap.go
index 29dbc10..832065a 100644
--- a/worker/imap/imap.go
+++ b/worker/imap/imap.go
@@ -24,9 +24,9 @@ func translateBodyStructure(bs *imap.BodyStructure) *models.BodyStructure {
	if bs == nil {
		return nil
	}
	var parts []*models.BodyStructure
	for _, part := range bs.Parts {
		parts = append(parts, translateBodyStructure(part))
	parts := make([]*models.BodyStructure, len(bs.Parts))
	for i, part := range bs.Parts {
		parts[i] = translateBodyStructure(part)
	}

	// TODO: is that all?
@@ -69,12 +69,12 @@ func translateEnvelope(e *imap.Envelope) *models.Envelope {
}

func translateAddresses(addrs []*imap.Address) []*mail.Address {
	var converted []*mail.Address
	for _, addr := range addrs {
		converted = append(converted, &mail.Address{
	converted := make([]*mail.Address, len(addrs))
	for i, addr := range addrs {
		converted[i] = &mail.Address{
			Name:    addr.PersonalName,
			Address: addr.Address(),
		})
		}
	}
	return converted
}
diff --git a/worker/imap/list.go b/worker/imap/list.go
index e69fc07..373944a 100644
--- a/worker/imap/list.go
+++ b/worker/imap/list.go
@@ -38,7 +38,7 @@ func (imapw *IMAPWorker) handleListDirectories(msg *types.ListDirectories) {
	} else {
		<-done
		imapw.worker.PostMessage(
			&types.Done{types.RespondTo(msg)}, nil)
			&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}

diff --git a/worker/imap/movecopy.go b/worker/imap/movecopy.go
index a53c7d4..087e74f 100644
--- a/worker/imap/movecopy.go
+++ b/worker/imap/movecopy.go
@@ -14,7 +14,7 @@ func (imapw *IMAPWorker) handleCopyMessages(msg *types.CopyMessages) {
			Error:   err,
		}, nil)
	} else {
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}

@@ -39,6 +39,6 @@ func (imapw *IMAPWorker) handleAppendMessage(msg *types.AppendMessage) {
			Error:   err,
		}, nil)
	} else {
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}
diff --git a/worker/imap/open.go b/worker/imap/open.go
index ae942ef..59cc3eb 100644
--- a/worker/imap/open.go
+++ b/worker/imap/open.go
@@ -19,7 +19,7 @@ func (imapw *IMAPWorker) handleOpenDirectory(msg *types.OpenDirectory) {
			Error:   err,
		}, nil)
	} else {
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
		if imapw.idleStop == nil {
			imapw.idleStop = make(chan struct{})
		}
@@ -74,7 +74,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
			Message: types.RespondTo(msg),
			Uids:    uids,
		}, nil)
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}

@@ -96,7 +96,7 @@ func translateSortCriterions(
	result := make([]sortthread.SortCriterion, 0, len(cs))
	for _, c := range cs {
		if f, ok := sortFieldMap[c.Field]; ok {
			result = append(result, sortthread.SortCriterion{f, c.Reverse})
			result = append(result, sortthread.SortCriterion{Field: f, Reverse: c.Reverse})
		}
	}
	return result
diff --git a/worker/imap/remove.go b/worker/imap/remove.go
index 47b1f43..3881c7d 100644
--- a/worker/imap/remove.go
+++ b/worker/imap/remove.go
@@ -14,6 +14,6 @@ func (imapw *IMAPWorker) handleRemoveDirectory(msg *types.RemoveDirectory) {
			Error:   err,
		}, nil)
	} else {
		imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		imapw.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	}
}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index c1c6109..afb4929 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -2,6 +2,7 @@ package imap

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net/url"
	"strings"
@@ -167,7 +168,7 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
			w.client.Close()
		}
		w.client = &imapClient{c, idle.NewClient(c), sortthread.NewSortClient(c)}
		w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
		w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
	case *types.ListDirectories:
		w.handleListDirectories(msg)
	case *types.OpenDirectory:
@@ -257,7 +258,7 @@ func (w *IMAPWorker) Run() {
		select {
		case msg := <-w.worker.Actions:
			msg = w.worker.ProcessAction(msg)
			if err := w.handleMessage(msg); err == errUnsupported {
			if err := w.handleMessage(msg); errors.Is(err, errUnsupported) {
				w.worker.PostMessage(&types.Unsupported{
					Message: types.RespondTo(msg),
				}, nil)
diff --git a/worker/lib/parse.go b/worker/lib/parse.go
index e07bdf0..f21a9dc 100644
--- a/worker/lib/parse.go
+++ b/worker/lib/parse.go
@@ -67,7 +67,7 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
	var body models.BodyStructure
	contentType, ctParams, err := e.Header.ContentType()
	if err != nil {
		return nil, fmt.Errorf("could not parse content type: %v", err)
		return nil, fmt.Errorf("could not parse content type: %w", err)
	}
	mimeType, mimeSubType := splitMIME(contentType)
	body.MIMEType = mimeType
@@ -78,7 +78,7 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
	if cd := e.Header.Get("content-disposition"); cd != "" {
		contentDisposition, cdParams, err := e.Header.ContentDisposition()
		if err != nil {
			return nil, fmt.Errorf("could not parse content disposition: %v", err)
			return nil, fmt.Errorf("could not parse content disposition: %w", err)
		}
		body.Disposition = contentDisposition
		body.DispositionParams = cdParams
@@ -87,7 +87,7 @@ func ParseEntityStructure(e *message.Entity) (*models.BodyStructure, error) {
	if mpr := e.MultipartReader(); mpr != nil {
		for {
			part, err := mpr.NextPart()
			if err == io.EOF {
			if errors.Is(err, io.EOF) {
				return &body, nil
			} else if err != nil {
				return nil, err
@@ -107,27 +107,27 @@ var ErrDateParse = errors.New("date parsing failed")
func parseEnvelope(h *mail.Header) (*models.Envelope, error) {
	from, err := parseAddressList(h, "from")
	if err != nil {
		return nil, fmt.Errorf("could not read from address: %v", err)
		return nil, fmt.Errorf("could not read from address: %w", err)
	}
	to, err := parseAddressList(h, "to")
	if err != nil {
		return nil, fmt.Errorf("could not read to address: %v", err)
		return nil, fmt.Errorf("could not read to address: %w", err)
	}
	cc, err := parseAddressList(h, "cc")
	if err != nil {
		return nil, fmt.Errorf("could not read cc address: %v", err)
		return nil, fmt.Errorf("could not read cc address: %w", err)
	}
	bcc, err := parseAddressList(h, "bcc")
	if err != nil {
		return nil, fmt.Errorf("could not read bcc address: %v", err)
		return nil, fmt.Errorf("could not read bcc address: %w", err)
	}
	replyTo, err := parseAddressList(h, "reply-to")
	if err != nil {
		return nil, fmt.Errorf("could not read reply-to address: %v", err)
		return nil, fmt.Errorf("could not read reply-to address: %w", err)
	}
	subj, err := h.Subject()
	if err != nil {
		return nil, fmt.Errorf("could not read subject: %v", err)
		return nil, fmt.Errorf("could not read subject: %w", err)
	}
	msgID, err := h.MessageID()
	if err != nil {
@@ -191,7 +191,7 @@ func parseDate(h *mail.Header) (time.Time, error) {
func parseReceivedHeader(h *mail.Header) (time.Time, error) {
	guess, err := h.Text("received")
	if err != nil {
		return time.Time{}, fmt.Errorf("received header not parseable: %v",
		return time.Time{}, fmt.Errorf("received header not parseable: %w",
			err)
	}
	return time.Parse(time.RFC1123Z, dateRe.FindString(guess))
@@ -227,18 +227,18 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
	}
	msg, err := message.Read(r)
	if err != nil {
		return nil, fmt.Errorf("could not read message: %v", err)
		return nil, fmt.Errorf("could not read message: %w", err)
	}
	bs, err := ParseEntityStructure(msg)
	if errors.As(err, new(message.UnknownEncodingError)) {
		parseErr = err
	} else if err != nil {
		return nil, fmt.Errorf("could not get structure: %v", err)
		return nil, fmt.Errorf("could not get structure: %w", err)
	}
	h := &mail.Header{msg.Header}
	h := &mail.Header{Header: msg.Header}
	env, err := parseEnvelope(h)
	if err != nil && !errors.Is(err, ErrDateParse) {
		return nil, fmt.Errorf("could not parse envelope: %v", err)
		return nil, fmt.Errorf("could not parse envelope: %w", err)
		// if only the date parsing failed we still get the rest of the
		// envelop structure in a valid state.
		// Date parsing errors are fairly common and it's better to be
@@ -264,7 +264,7 @@ func MessageInfo(raw RawMessage) (*models.MessageInfo, error) {
		Flags:         flags,
		Labels:        labels,
		InternalDate:  recDate,
		RFC822Headers: &mail.Header{msg.Header},
		RFC822Headers: &mail.Header{Header: msg.Header},
		Size:          0,
		Uid:           raw.UID(),
		Error:         parseErr,
diff --git a/worker/lib/sort.go b/worker/lib/sort.go
index 09bcf77..e179ae4 100644
--- a/worker/lib/sort.go
+++ b/worker/lib/sort.go
@@ -93,18 +93,18 @@ func sortAddresses(messageInfos []*models.MessageInfo, criterion *types.SortCrit

func sortFlags(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
	testFlag models.Flag) {
	var slice []*boolStore
	for _, msgInfo := range messageInfos {
	slice := make([]*boolStore, len(messageInfos))
	for i, msgInfo := range messageInfos {
		flagPresent := false
		for _, flag := range msgInfo.Flags {
			if flag == testFlag {
				flagPresent = true
			}
		}
		slice = append(slice, &boolStore{
		slice[i] = &boolStore{
			Value:   flagPresent,
			MsgInfo: msgInfo,
		})
		}
	}
	sortSlice(criterion, slice, func(i, j int) bool {
		valI, valJ := slice[i].Value, slice[j].Value
@@ -117,12 +117,12 @@ func sortFlags(messageInfos []*models.MessageInfo, criterion *types.SortCriterio

func sortStrings(messageInfos []*models.MessageInfo, criterion *types.SortCriterion,
	getValue func(*models.MessageInfo) string) {
	var slice []*lexiStore
	for _, msgInfo := range messageInfos {
		slice = append(slice, &lexiStore{
	slice := make([]*lexiStore, len(messageInfos))
	for i, msgInfo := range messageInfos {
		slice[i] = &lexiStore{
			Value:   getValue(msgInfo),
			MsgInfo: msgInfo,
		})
		}
	}
	sortSlice(criterion, slice, func(i, j int) bool {
		return slice[i].Value < slice[j].Value
diff --git a/worker/maildir/container.go b/worker/maildir/container.go
index 14815c9..09af7e7 100644
--- a/worker/maildir/container.go
+++ b/worker/maildir/container.go
@@ -42,7 +42,7 @@ func (c *Container) ListFolders() ([]string, error) {
	folders := []string{}
	err := filepath.Walk(c.dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("Invalid path '%s': error: %v", path, err)
			return fmt.Errorf("Invalid path '%s': error: %w", path, err)

		}
		if !info.IsDir() {
@@ -95,12 +95,12 @@ func (c *Container) Dir(name string) maildir.Dir {
func (c *Container) UIDs(d maildir.Dir) ([]uint32, error) {
	keys, err := d.Keys()
	if err != nil {
		return nil, fmt.Errorf("could not get keys for %s: %v", d, err)
		return nil, fmt.Errorf("could not get keys for %s: %w", d, err)
	}
	sort.Strings(keys)
	var uids []uint32
	for _, key := range keys {
		uids = append(uids, c.uids.GetOrInsert(key))
	uids := make([]uint32, len(keys))
	for i, key := range keys {
		uids[i] = c.uids.GetOrInsert(key)
	}
	return uids, nil
}
@@ -121,7 +121,7 @@ func (c *Container) Message(d maildir.Dir, uid uint32) (*Message, error) {
// DeleteAll deletes a set of messages by UID and returns the subset of UIDs
// which were successfully deleted, stopping upon the first error.
func (c *Container) DeleteAll(d maildir.Dir, uids []uint32) ([]uint32, error) {
	var success []uint32
	success := make([]uint32, 0, len(uids))
	for _, uid := range uids {
		msg, err := c.Message(d, uid)
		if err != nil {
@@ -139,7 +139,7 @@ func (c *Container) CopyAll(
	dest maildir.Dir, src maildir.Dir, uids []uint32) error {
	for _, uid := range uids {
		if err := c.copyMessage(dest, src, uid); err != nil {
			return fmt.Errorf("could not copy message %d: %v", uid, err)
			return fmt.Errorf("could not copy message %d: %w", uid, err)
		}
	}
	return nil
diff --git a/worker/maildir/message.go b/worker/maildir/message.go
index b3c2e8f..c353c1e 100644
--- a/worker/maildir/message.go
+++ b/worker/maildir/message.go
@@ -51,7 +51,7 @@ func (m Message) SetFlags(flags []maildir.Flag) error {
func (m Message) SetOneFlag(flag maildir.Flag, enable bool) error {
	flags, err := m.Flags()
	if err != nil {
		return fmt.Errorf("could not read previous flags: %v", err)
		return fmt.Errorf("could not read previous flags: %w", err)
	}
	if enable {
		flags = append(flags, flag)
@@ -92,7 +92,7 @@ func (m Message) NewBodyPartReader(requestedParts []int) (io.Reader, error) {
	defer f.Close()
	msg, err := message.Read(f)
	if err != nil {
		return nil, fmt.Errorf("could not read message: %v", err)
		return nil, fmt.Errorf("could not read message: %w", err)
	}
	return lib.FetchEntityPartReader(msg, requestedParts)
}
diff --git a/worker/maildir/worker.go b/worker/maildir/worker.go
index 87ebc97..75bf313 100644
--- a/worker/maildir/worker.go
+++ b/worker/maildir/worker.go
@@ -37,7 +37,7 @@ type Worker struct {
func NewWorker(worker *types.Worker) (types.Backend, error) {
	watch, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, fmt.Errorf("could not create file system watcher: %v", err)
		return nil, fmt.Errorf("could not create file system watcher: %w", err)
	}
	return &Worker{worker: worker, watcher: watch}, nil
}
@@ -56,7 +56,7 @@ func (w *Worker) Run() {

func (w *Worker) handleAction(action types.WorkerMessage) {
	msg := w.worker.ProcessAction(action)
	if err := w.handleMessage(msg); err == errUnsupported {
	if err := w.handleMessage(msg); errors.Is(err, errUnsupported) {
		w.worker.PostMessage(&types.Unsupported{
			Message: types.RespondTo(msg),
		}, nil)
@@ -105,7 +105,7 @@ func (w *Worker) handleFSEvent(ev fsnotify.Event) {
}

func (w *Worker) done(msg types.WorkerMessage) {
	w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
	w.worker.PostMessage(&types.Done{Message: types.RespondTo(msg)}, nil)
}

func (w *Worker) err(msg types.WorkerMessage, err error) {
@@ -220,7 +220,7 @@ func (w *Worker) handleConfigure(msg *types.Configure) error {
	if u.Host == "~" {
		home, err := os.UserHomeDir()
		if err != nil {
			return fmt.Errorf("could not resolve home directory: %v", err)
			return fmt.Errorf("could not resolve home directory: %w", err)
		}
		dir = filepath.Join(home, u.Path)
	}
@@ -282,7 +282,7 @@ func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error {
	if w.selected != nil {
		prevDir := filepath.Join(string(*w.selected), "new")
		if err := w.watcher.Remove(prevDir); err != nil {
			return fmt.Errorf("could not unwatch previous directory: %v", err)
			return fmt.Errorf("could not unwatch previous directory: %w", err)
		}
	}

@@ -292,11 +292,11 @@ func (w *Worker) handleOpenDirectory(msg *types.OpenDirectory) error {
	// add watch path
	newDir := filepath.Join(string(*w.selected), "new")
	if err := w.watcher.Add(newDir); err != nil {
		return fmt.Errorf("could not add watch to directory: %v", err)
		return fmt.Errorf("could not add watch to directory: %w", err)
	}

	if err := dir.Clean(); err != nil {
		return fmt.Errorf("could not clean directory: %v", err)
		return fmt.Errorf("could not clean directory: %w", err)
	}

	info := &types.DirectoryInfo{
@@ -330,7 +330,7 @@ func (w *Worker) sort(uids []uint32, criteria []*types.SortCriterion) ([]uint32,
	if len(criteria) == 0 {
		return uids, nil
	}
	var msgInfos []*models.MessageInfo
	msgInfos := make([]*models.MessageInfo, len(uids))
	for _, uid := range uids {
		m, err := w.c.Message(*w.selected, uid)
		if err != nil {
-- 
2.30.2