~rjarry/aerc-devel

aerc: Replace tcell v3 NEEDS REVISION

v2->v3:
- Rebase on master
- Fix applying underline styles in templates (mpldr)
- Update vaxis (term perf improvements, minor bugfixes)

v1->v2:
- Resend and hope the cover letter makes it this time
- Add relevant commit trailers

Hey everyone -

This series supersedes the previous tcell replacement series I sent. I restarted
the versioning because this series _fully_ replaces tcell by the last commit.

This series replaces tcell with Vaxis, a library I wrote which has some benefits
over tcell:
- More performant
- Doesn't rely on built in terminfo
- Doesn't rely on system terminfo
- Uses "standard" VT parser
- More features out of the box
- Developed with email based workflow :)
- Images with fallback rendering where appropriate (support for kitty and sixel,
  with cell based renderers as fallbacks)
- Kitty keyboard support (currently disabled in aerc)

Overall with this series you should expect to see very few changes, with the
exception of the image feature added. At some point in the future I expect that
we'll turn on the kitty keyboard feature, however I chose not to introduce too
many changes in this series.

Tim Culverhouse (17):
  parse/ansi: remove tcell/terminfo dependency
  ui: so long tcell
  ui: create and expose vaxis Window with Context
  msgviewer: implement inline image viewing
  aerc: change event interfaces to vaxis events
  ui: remove screen and viewports
  ui: initialize vaxis directly, drop tcell.Screen initialization
  fill: replace tcell.Style with vaxis.Style
  aerc: replace tcell keys with vaxis keys
  style: use vaxis style everywhere
  terminal: replace tcell-term with vaxis terminal
  paste: use vaxis paste events
  mouse: use vaxis mouse events
  aerc: remove tcell import
  aerc: set title using vaxis
  docs: update docs to remove tcell reference
  vaxis: update to v0.5.1

 app/account-wizard.go    |  27 +--
 app/account.go           |   5 +-
 app/aerc.go              |  78 ++++---
 app/authinfo.go          |   4 +-
 app/compose.go           |  20 +-
 app/dirlist.go           |  25 +--
 app/dirtree.go           |  14 +-
 app/exline.go            |  17 +-
 app/getpasswd.go         |  13 +-
 app/listbox.go           |  36 +--
 app/msglist.go           |  18 +-
 app/msgviewer.go         | 108 ++++++++-
 app/partswitcher.go      |  18 +-
 app/pgpinfo.go           |   4 +-
 app/selector.go          |  28 +--
 app/spinner.go           |   5 +-
 app/status.go            |   8 +-
 app/terminal.go          |  73 +++----
 commands/patch/list.go   |   4 +-
 commands/send-keys.go    |   7 +-
 commands/util.go         |   4 +-
 config/binds.go          | 458 +++++++++++++++++++--------------------
 config/binds_test.go     |  36 +--
 config/style.go          | 233 +++++++++++++++++---
 config/ui.go             |  16 +-
 doc/aerc-stylesets.7.scd |   3 +-
 go.mod                   |  20 +-
 go.sum                   |  39 ++--
 lib/parse/ansi.go        | 252 ++++++++++-----------
 lib/parse/ansi_test.go   |  60 ++---
 lib/ui/borders.go        |   5 +-
 lib/ui/box.go            |   6 +-
 lib/ui/context.go        |  94 ++++----
 lib/ui/fill.go           |   8 +-
 lib/ui/grid.go           |   6 +-
 lib/ui/interfaces.go     |   8 +-
 lib/ui/popover.go        |   4 +-
 lib/ui/stack.go          |   5 +-
 lib/ui/tab.go            |  18 +-
 lib/ui/table.go          |  18 +-
 lib/ui/text.go           |   6 +-
 lib/ui/textinput.go      |  62 +++---
 lib/ui/ui.go             |  81 +++----
 main.go                  |  32 ---
 44 files changed, 1103 insertions(+), 883 deletions(-)

-- 
2.43.0
#1129000 alpine-edge.yml success
#1129001 openbsd.yml success
aerc/patches: SUCCESS in 5m0s

[Replace tcell][0] v3 from [Tim Culverhouse][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/48376
[1]: mailto:tim@timculverhouse.com

✓ #1129000 SUCCESS aerc/patches/alpine-edge.yml https://builds.sr.ht/~rjarry/job/1129000
✓ #1129001 SUCCESS aerc/patches/openbsd.yml     https://builds.sr.ht/~rjarry/job/1129001
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/~rjarry/aerc-devel/patches/48376/mbox | git am -3
Learn more about email & git

[PATCH aerc v3 01/17] parse/ansi: remove tcell/terminfo dependency Export this patch

The parse library builds an ansi-escaped string based on a buffer of
styled cells. Use constants which aerc will still parse properly (and
are the same as the terminfo package was pulling in) to remove
dependency on tcell/terminfo. Additionally, we can use the internal go
"fmt" package to write strings instead of the terminfo.TParm method
(which is much slower at formatting strings).

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 lib/parse/ansi.go      | 78 +++++++++++++++++++++---------------------
 lib/parse/ansi_test.go | 60 ++++++++++++++++----------------
 2 files changed, 69 insertions(+), 69 deletions(-)

diff --git a/lib/parse/ansi.go b/lib/parse/ansi.go
index ff44f8ba5c51..3a802a51f78c 100644
--- a/lib/parse/ansi.go
+++ b/lib/parse/ansi.go
@@ -13,12 +13,28 @@ import (

	"git.sr.ht/~rjarry/aerc/log"
	"github.com/gdamore/tcell/v2"
	"github.com/gdamore/tcell/v2/terminfo"
	"github.com/mattn/go-runewidth"
)

var AnsiReg = regexp.MustCompile("\x1B\\[[0-?]*[ -/]*[@-~]")

const (
	setfgbgrgb    = "\x1b[38;2;%d;%d;%d;48;2;%d;%d;%dm"
	setfgrgb      = "\x1b[38;2;%d;%d;%dm"
	setbgrgb      = "\x1b[48;2;%d;%d;%dm"
	setfgbg       = "\x1b[38;5;%d;48;5;%dm"
	setfg         = "\x1b[38;5;%dm"
	setbg         = "\x1b[48;5;%dm"
	attrOff       = "\x1B[m"
	bold          = "\x1B[1m"
	dim           = "\x1B[2m"
	italic        = "\x1B[3m"
	underline     = "\x1B[4m"
	blink         = "\x1B[5m"
	reverse       = "\x1B[7m"
	strikethrough = "\x1B[9m"
)

// StripAnsi strips ansi escape codes from the reader
func StripAnsi(r io.Reader) io.Reader {
	buf := bytes.NewBuffer(nil)
@@ -101,16 +117,8 @@ func (rb *RuneBuffer) String() string {
// string returns a string no longer than n runes. If 'left' is true, the left
// side of the text is truncated. Pass 0 to return the full string
func (rb *RuneBuffer) string(n int, left bool, char rune) string {
	// Use xterm-256color to generate the string. Ultimately all output will
	// be re-parsed as 'xterm-256color' and tcell will handle the final
	// output sequences based on the user's TERM
	ti, err := terminfo.LookupTerminfo("xterm-256color")
	if err != nil {
		// Who knows what happened
		return ""
	}
	var (
		s        = strings.Builder{}
		s        = bytes.NewBuffer(nil)
		style    = tcell.StyleDefault
		hasStyle = false
		// w will track the length we have written, or would have
@@ -127,60 +135,52 @@ func (rb *RuneBuffer) string(n int, left bool, char rune) string {
		if style != r.Style {
			hasStyle = true
			style = r.Style
			s.WriteString(ti.AttrOff)
			s.WriteString(attrOff)
			fg, bg, attrs := style.Decompose()

			switch {
			case fg.IsRGB() && bg.IsRGB() && ti.SetFgBgRGB != "":
			case fg.IsRGB() && bg.IsRGB():
				fr, fg, fb := fg.RGB()
				br, bg, bb := bg.RGB()
				s.WriteString(ti.TParm(
					ti.SetFgBgRGB,
					int(fr),
					int(fg),
					int(fb),
					int(br),
					int(bg),
					int(bb),
				))
			case fg.IsRGB() && ti.SetFgRGB != "":
				fmt.Fprintf(s, setfgbgrgb, fr, fg, fb, br, bg, bb)
			case fg.IsRGB():
				// RGB
				r, g, b := fg.RGB()
				s.WriteString(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b)))
			case bg.IsRGB() && ti.SetBgRGB != "":
				fmt.Fprintf(s, setfgrgb, r, g, b)
			case bg.IsRGB():
				// RGB
				r, g, b := bg.RGB()
				s.WriteString(ti.TParm(ti.SetBgRGB, int(r), int(g), int(b)))
				fmt.Fprintf(s, setbgrgb, r, g, b)

				// Indexed
			case fg.Valid() && bg.Valid() && ti.SetFgBg != "":
				s.WriteString(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff)))
			case fg.Valid() && ti.SetFg != "":
				s.WriteString(ti.TParm(ti.SetFg, int(fg&0xff)))
			case bg.Valid() && ti.SetBg != "":
				s.WriteString(ti.TParm(ti.SetBg, int(bg&0xff)))
			case fg.Valid() && bg.Valid():
				fmt.Fprintf(s, setfgbg, fg&0xFF, bg&0xFF)
			case fg.Valid():
				fmt.Fprintf(s, setfg, fg&0xFF)
			case bg.Valid():
				fmt.Fprintf(s, setbg, bg&0xFF)
			}

			if attrs&tcell.AttrBold != 0 {
				s.WriteString(ti.Bold)
				s.WriteString(bold)
			}
			if attrs&tcell.AttrUnderline != 0 {
				s.WriteString(ti.Underline)
				s.WriteString(underline)
			}
			if attrs&tcell.AttrReverse != 0 {
				s.WriteString(ti.Reverse)
				s.WriteString(reverse)
			}
			if attrs&tcell.AttrBlink != 0 {
				s.WriteString(ti.Blink)
				s.WriteString(blink)
			}
			if attrs&tcell.AttrDim != 0 {
				s.WriteString(ti.Dim)
				s.WriteString(dim)
			}
			if attrs&tcell.AttrItalic != 0 {
				s.WriteString(ti.Italic)
				s.WriteString(italic)
			}
			if attrs&tcell.AttrStrikeThrough != 0 {
				s.WriteString(ti.StrikeThrough)
				s.WriteString(strikethrough)
			}
		}

@@ -200,7 +200,7 @@ func (rb *RuneBuffer) string(n int, left bool, char rune) string {
		}
	}
	if hasStyle {
		s.WriteString(ti.AttrOff)
		s.WriteString(attrOff)
	}
	return s.String()
}
diff --git a/lib/parse/ansi_test.go b/lib/parse/ansi_test.go
index f916412bbee6..6aa95b185ded 100644
--- a/lib/parse/ansi_test.go
+++ b/lib/parse/ansi_test.go
@@ -26,49 +26,49 @@ func TestParser(t *testing.T) {
		{
			name:           "bold",
			input:          "\x1b[1mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[1mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "dim",
			input:          "\x1b[2mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[2mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[2mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "bold and dim",
			input:          "\x1b[1;2mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[1m\x1b[2mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1m\x1b[2mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "italic",
			input:          "\x1b[3mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[3mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[3mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "underline",
			input:          "\x1b[4mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[4mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[4mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "blink",
			input:          "\x1b[5mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[5mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[5mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "fast blink",
			input:          "\x1b[6mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[5mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[5mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "reverse",
			input:          "\x1b[7mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[7mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[7mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
@@ -80,121 +80,121 @@ func TestParser(t *testing.T) {
		{
			name:           "strikethrough",
			input:          "\x1b[9mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[9mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[9mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "bold hello, normal world",
			input:          "\x1b[1mhello, \x1b[21mworld",
			expectedString: "\x1b(B\x1b[m\x1b[1mhello, \x1b(B\x1b[mworld\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1mhello, \x1b[mworld\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "bold hello, normal world v2",
			input:          "\x1b[1mhello, \x1b[mworld",
			expectedString: "\x1b(B\x1b[m\x1b[1mhello, \x1b(B\x1b[mworld\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1mhello, \x1b[mworld\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "8 bit color: foreground",
			input:          "\x1b[30mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[30mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "8 bit color: background",
			input:          "\x1b[41mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[41mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;5;1mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "8 bit color: foreground and background",
			input:          "\x1b[31;41mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[31;41mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;1;48;5;1mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "16 bit color: foreground",
			input:          "\x1b[90mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[90mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;8mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "16 bit color: background",
			input:          "\x1b[101mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[101mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;5;9mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "16 bit color: foreground and background",
			input:          "\x1b[91;101mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[91;101mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;9;48;5;9mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "256 color: foreground",
			input:          "\x1b[38;5;2mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[32mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;2mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "256 color: foreground",
			input:          "\x1b[38;5;132mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;5;132mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;132mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "256 color: background",
			input:          "\x1b[48;5;132mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[48;5;132mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;5;132mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "256 color: foreground and background",
			input:          "\x1b[38;5;20;48;5;20mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;5;20;48;5;20mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;20;48;5;20mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "256 color: background",
			input:          "\x1b[48;5;2mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[42mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;5;2mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: foreground",
			input:          "\x1b[38;2;0;0;0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: foreground with color space",
			input:          "\x1b[38;2;;0;0;0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: foreground with color space and colons",
			input:          "\x1b[38:2::0:0:0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: background",
			input:          "\x1b[48;2;0;0;0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[48;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: background with color space",
			input:          "\x1b[48;2;;0;0;0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[48;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[48;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
			name:           "true color: foreground and background",
			input:          "\x1b[38;2;200;200;200;48;2;0;0;0mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[38;2;200;200;200;48;2;0;0;0mhello, world\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[38;2;200;200;200;48;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
	}
@@ -222,7 +222,7 @@ func TestTruncate(t *testing.T) {
		{
			name:           "bold, truncate at 5",
			input:          "\x1b[1mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[1mhello\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1mhello\x1b[m",
		},
	}

@@ -249,7 +249,7 @@ func TestTruncateHead(t *testing.T) {
		{
			name:           "bold, truncate head at 5",
			input:          "\x1b[1mhello, world",
			expectedString: "\x1b(B\x1b[m\x1b[1mworld\x1b(B\x1b[m",
			expectedString: "\x1b[m\x1b[1mworld\x1b[m",
		},
	}

-- 
2.43.0

[PATCH aerc v3 02/17] ui: so long tcell Export this patch

Replace tcell with vaxis. Vaxis provides several new features (none of
which are included in this commit). All behavior should be exactly the
same as previous, with one exception:

   Vaxis does not have an internal terminfo library. Some terminals will
   now have RGB that didn't before, as well as any other feature that
   was falling back to some unknown state.

Changelog-changed: replaced tcell with vaxis
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 go.mod       | 18 +++++++++++-------
 go.sum       | 46 +++++++++++++++++++++++++++++++++-------------
 lib/ui/ui.go | 12 +++++++++++-
 3 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/go.mod b/go.mod
index 6e88e5ec14dd..87b7ad3adce6 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
	git.sr.ht/~rjarry/go-opt v1.3.0
	git.sr.ht/~rockorager/go-jmap v0.3.0
	git.sr.ht/~rockorager/tcell-term v0.10.0
	git.sr.ht/~rockorager/vaxis v0.4.7
	github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5
	github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
	github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
@@ -25,7 +26,7 @@ require (
	github.com/go-ini/ini v1.67.0
	github.com/lithammer/fuzzysearch v1.1.5
	github.com/mattn/go-isatty v0.0.18
	github.com/mattn/go-runewidth v0.0.14
	github.com/mattn/go-runewidth v0.0.15
	github.com/pkg/errors v0.9.1
	github.com/rivo/uniseg v0.4.4
	github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab
@@ -33,31 +34,34 @@ require (
	github.com/syndtr/goleveldb v1.0.0
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
	golang.org/x/oauth2 v0.7.0
	golang.org/x/sys v0.7.0
	golang.org/x/sys v0.13.0
	golang.org/x/tools v0.6.0
)

require (
	github.com/cloudflare/circl v1.3.2 // indirect
	github.com/containerd/console v1.0.3 // indirect
	github.com/creack/pty v1.1.18 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
	github.com/gdamore/encoding v1.0.0 // indirect
	github.com/golang/protobuf v1.5.3 // indirect
	github.com/golang/snappy v0.0.4 // indirect
	github.com/kr/pretty v0.3.0 // indirect
	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
	github.com/mattn/go-sixel v0.0.5 // indirect
	github.com/onsi/gomega v1.20.0 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/rogpeppe/go-internal v1.8.1 // indirect
	golang.org/x/crypto v0.8.0 // indirect
	github.com/soniakeys/quant v1.0.0 // indirect
	golang.org/x/crypto v0.7.0 // indirect
	golang.org/x/image v0.13.0 // indirect
	golang.org/x/mod v0.8.0 // indirect
	golang.org/x/net v0.9.0 // indirect
	golang.org/x/term v0.7.0 // indirect
	golang.org/x/text v0.12.0 // indirect
	golang.org/x/text v0.13.0 // indirect
	google.golang.org/appengine v1.6.7 // indirect
	google.golang.org/protobuf v1.30.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3

replace github.com/gdamore/tcell/v2 => git.sr.ht/~rockorager/vaxis-tcell v0.4.7
diff --git a/go.sum b/go.sum
index 2d657644a56c..48122d9f3f22 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,10 @@ git.sr.ht/~rockorager/go-jmap v0.3.0 h1:h2WuPcNyXRYFg9+W2HGf/mzIqC6ISy9EaS/BGa7Z
git.sr.ht/~rockorager/go-jmap v0.3.0/go.mod h1:aOTCtwpZSINpDDSOkLGpHU0Kbbm5lcSDMcobX3ZtOjY=
git.sr.ht/~rockorager/tcell-term v0.10.0 h1:BqxJjtCMmLIfS6fdIal8TSiH3qPiYTjATuIRIWNVPbo=
git.sr.ht/~rockorager/tcell-term v0.10.0/go.mod h1:Snxh5CrziiA2CjyLOZ6tGAg5vMPlE+REMWT3rtKuyyQ=
git.sr.ht/~rockorager/vaxis v0.4.7 h1:9VlkBBF9dxy62AMHnKAU8GqEs2/mUTlke/ZJ9o/6luk=
git.sr.ht/~rockorager/vaxis v0.4.7/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
git.sr.ht/~rockorager/vaxis-tcell v0.4.7 h1:ISMSnvbz1jnG9Ppi9y3NJKaLl7Nu67qMkpEXbXwHgmg=
git.sr.ht/~rockorager/vaxis-tcell v0.4.7/go.mod h1:mpNiMGQDJ3fiwVO8pvz0GENWCdCXEE50beqCbgGoXLc=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 h1:JW27/kGLQzeM1Fxg5YQhdkTEAU7HIAHMgSag35zVTnY=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@@ -17,6 +21,8 @@ github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXO
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk=
github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@@ -61,11 +67,7 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gatherstars-com/jwz v1.4.0 h1:HrCJmTss6/PTzBxxQUGbJ5f0aFYjnfrfqpGlyWH+R5A=
github.com/gatherstars-com/jwz v1.4.0/go.mod h1:twtXjMamfC5/NRCTJ9vDiHGeDivORkTAvOMUX/qo0Ik=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
@@ -82,6 +84,7 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
@@ -99,15 +102,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sixel v0.0.5 h1:55w2FR5ncuhKhXrM5ly1eiqMQfZsnAHIpYNGZX03Cv8=
github.com/mattn/go-sixel v0.0.5/go.mod h1:h2Sss+DiUEHy0pUqcIB6PFXo5Cy8sTQEFr3a9/5ZLNw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
@@ -133,6 +138,8 @@ github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -143,6 +150,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@@ -150,8 +158,13 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -159,6 +172,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
@@ -173,33 +187,39 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index 6a28eb9ce509..b5c66530caa6 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -6,6 +6,8 @@ import (
	"sync/atomic"
	"syscall"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

@@ -51,7 +53,10 @@ func Initialize(content DrawableInteractive) error {
		return err
	}

	if err = screen.Init(); err != nil {
	opts := vaxis.Options{
		DisableMouse: !config.Ui.MouseEnabled,
	}
	if err = screen.Init(opts); err != nil {
		return err
	}

@@ -140,6 +145,11 @@ func HandleEvent(event tcell.Event) {
		state.ctx = NewContext(width, height, state.screen, onPopover)
		Invalidate()
	}
	if event, ok := event.(tcell.VaxisEvent); ok {
		if _, ok := event.Vaxis().(vaxis.Redraw); ok {
			Invalidate()
		}
	}
	// if we have a popover, and it can handle the event, it does so
	if state.popover == nil || !state.popover.Event(event) {
		// otherwise, we send the event to the main content
-- 
2.43.0

[PATCH aerc v3 03/17] ui: create and expose vaxis Window with Context Export this patch

Create and expose a vaxis.Window object with each Context. vaxis.Windows
are used for creating local coordinates (similar to the views.View API
that tcell provides).

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 lib/ui/context.go | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/lib/ui/context.go b/lib/ui/context.go
index 9ca7cc9d56c0..39933fa30789 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -4,6 +4,7 @@ import (
	"fmt"

	"git.sr.ht/~rjarry/aerc/lib/parse"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/gdamore/tcell/v2/views"
)
@@ -12,6 +13,7 @@ import (
type Context struct {
	screen    tcell.Screen
	viewport  *views.ViewPort
	window    vaxis.Window
	x, y      int
	onPopover func(*Popover)
}
@@ -36,9 +38,15 @@ func (ctx *Context) Height() int {
	return height
}

// returns the vaxis Window for this context
func (ctx *Context) Window() vaxis.Window {
	return ctx.window
}

func NewContext(width, height int, screen tcell.Screen, p func(*Popover)) *Context {
	vp := views.NewViewPort(screen, 0, 0, width, height)
	return &Context{screen, vp, 0, 0, p}
	win := screen.Vaxis().Window()
	return &Context{screen, vp, win, 0, 0, p}
}

func (ctx *Context) Subcontext(x, y, width, height int) *Context {
@@ -50,7 +58,8 @@ func (ctx *Context) Subcontext(x, y, width, height int) *Context {
		panic(fmt.Errorf("Attempted to create context larger than parent"))
	}
	vp := views.NewViewPort(ctx.viewport, x, y, width, height)
	return &Context{ctx.screen, vp, ctx.x + x, ctx.y + y, ctx.onPopover}
	win := ctx.window.New(x, y, width, height)
	return &Context{ctx.screen, vp, win, ctx.x + x, ctx.y + y, ctx.onPopover}
}

func (ctx *Context) SetCell(x, y int, ch rune, style tcell.Style) {
-- 
2.43.0

[PATCH aerc v3 04/17] msgviewer: implement inline image viewing Export this patch

Implement inline image viewing for jpeg, png, bmp, tiff, and webp
formats. When a user has no configured image filter and the image is
supported and the terminal has either sixel or kitty image protocol
support, the image will be displayed in the message viewer.

Always clear the screen before each draw. This call is necessary in
vaxis to allow for images to be cleared properly between renders. There
is no performance impact: the call only resets each cell to a blank
cell, and aerc will redraw each one already.

Changelog-added: inline image previews using kitty, sixel, or cells
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/msgviewer.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++-
 go.mod           |   2 +-
 lib/ui/ui.go     |   1 +
 3 files changed, 101 insertions(+), 3 deletions(-)

diff --git a/app/msgviewer.go b/app/msgviewer.go
index e45a6d3ec75c..8d2e380a9987 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -4,6 +4,7 @@ import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"io"
	"os"
	"os/exec"
@@ -24,8 +25,28 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/go-opt"
	"git.sr.ht/~rockorager/vaxis"
	"git.sr.ht/~rockorager/vaxis/widgets/align"

	// Image support
	_ "image/jpeg"
	_ "image/png"

	_ "golang.org/x/image/bmp"
	_ "golang.org/x/image/tiff"
	_ "golang.org/x/image/webp"
)

// All imported image types need to be explicitly stated here. We want to check
// if we _can_ display something before we download it
var supportedImageTypes = []string{
	"image/jpeg",
	"image/png",
	"image/bmp",
	"image/tiff",
	"image/webp",
}

var _ ProvidesMessages = (*MessageViewer)(nil)

type MessageViewer struct {
@@ -405,6 +426,11 @@ type PartViewer struct {
	noFilter   *ui.Grid
	uiConfig   *config.UIConfig
	copying    int32
	inlineImg  bool
	image      image.Image
	graphic    vaxis.Image
	width      int
	height     int

	links []string
}
@@ -535,7 +561,27 @@ func NewPartViewer(

func (pv *PartViewer) SetSource(reader io.Reader) {
	pv.source = reader
	pv.attemptCopy()
	switch pv.inlineImg {
	case true:
		pv.decodeImage()
	default:
		pv.attemptCopy()
	}
}

func (pv *PartViewer) decodeImage() {
	atomic.StoreInt32(&pv.copying, copying)
	go func() {
		defer log.PanicHandler()
		defer pv.Invalidate()
		defer atomic.StoreInt32(&pv.copying, 0)
		img, _, err := image.Decode(pv.source)
		if err != nil {
			log.Errorf("error decoding image: %v", err)
			return
		}
		pv.image = img
	}()
}

func (pv *PartViewer) attemptCopy() {
@@ -711,7 +757,13 @@ func (pv *PartViewer) Invalidate() {

func (pv *PartViewer) Draw(ctx *ui.Context) {
	style := pv.uiConfig.GetStyle(config.STYLE_DEFAULT)
	if pv.filter == nil {
	switch {
	case pv.filter == nil && canInline(pv.part.FullMIMEType()) && pv.err == nil:
		pv.inlineImg = true
	case pv.filter == nil:
		// No filter, can't inline, and/or we attempted to inline an image
		// and resulted in an error (maybe because of a bad encoding or
		// the terminal doesn't support any graphics protocol).
		ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', style)
		pv.noFilter.Draw(ctx)
		return
@@ -728,12 +780,48 @@ func (pv *PartViewer) Draw(ctx *ui.Context) {
	if pv.term != nil {
		pv.term.Draw(ctx)
	}
	if pv.image != nil && (pv.resized(ctx) || pv.graphic == nil) {
		// This path should only occur on resizes or the first pass
		// after the image is downloaded and could be slow due to
		// encoding the image to either sixel or uploading via the kitty
		// protocol. Generally it's pretty fast since we will only ever
		// be downsizing images
		vx := ctx.Window().Vx
		if pv.graphic == nil {
			var err error
			pv.graphic, err = vx.NewImage(pv.image)
			if err != nil {
				log.Errorf("Couldn't create image: %v", err)
				return
			}
		}
		pv.graphic.Resize(pv.width, pv.height)
	}
	if pv.graphic != nil {
		w, h := pv.graphic.CellSize()
		win := align.Center(ctx.Window(), w, h)
		pv.graphic.Draw(win)
	}
}

func (pv *PartViewer) Cleanup() {
	if pv.term != nil {
		pv.term.Close()
	}
	if pv.graphic != nil {
		pv.graphic.Destroy()
	}
}

func (pv *PartViewer) resized(ctx *ui.Context) bool {
	w := ctx.Width()
	h := ctx.Height()
	if pv.width != w || pv.height != h {
		pv.width = w
		pv.height = h
		return true
	}
	return false
}

func (pv *PartViewer) Event(event tcell.Event) bool {
@@ -779,3 +867,12 @@ func (hv *HeaderView) Draw(ctx *ui.Context) {
func (hv *HeaderView) Invalidate() {
	ui.Invalidate()
}

func canInline(mime string) bool {
	for _, ext := range supportedImageTypes {
		if mime == ext {
			return true
		}
	}
	return false
}
diff --git a/go.mod b/go.mod
index 87b7ad3adce6..63f3e7ba3646 100644
--- a/go.mod
+++ b/go.mod
@@ -33,6 +33,7 @@ require (
	github.com/stretchr/testify v1.8.4
	github.com/syndtr/goleveldb v1.0.0
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
	golang.org/x/image v0.13.0
	golang.org/x/oauth2 v0.7.0
	golang.org/x/sys v0.13.0
	golang.org/x/tools v0.6.0
@@ -53,7 +54,6 @@ require (
	github.com/rogpeppe/go-internal v1.8.1 // indirect
	github.com/soniakeys/quant v1.0.0 // indirect
	golang.org/x/crypto v0.7.0 // indirect
	golang.org/x/image v0.13.0 // indirect
	golang.org/x/mod v0.8.0 // indirect
	golang.org/x/net v0.9.0 // indirect
	golang.org/x/text v0.13.0 // indirect
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index b5c66530caa6..b80309499248 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -123,6 +123,7 @@ func Close() {

func Render() {
	if atomic.SwapUint32(&state.dirty, 0) != 0 {
		state.screen.Clear()
		// reset popover for the next Draw
		state.popover = nil
		state.content.Draw(state.ctx)
-- 
2.43.0

[PATCH aerc v3 05/17] aerc: change event interfaces to vaxis events Export this patch

Modify the function signature of Event and MouseEvent interfaces to
accept vaxis events. Note that because a vaxis event is an empty
interface, the implementations are not affected and the events are
delivered as they were before

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/account-wizard.go  |  3 ++-
 app/account.go         |  5 ++---
 app/aerc.go            |  3 ++-
 app/compose.go         | 11 ++++++-----
 app/dirlist.go         |  3 ++-
 app/dirtree.go         |  3 ++-
 app/exline.go          |  3 ++-
 app/getpasswd.go       |  3 ++-
 app/listbox.go         |  3 ++-
 app/msglist.go         |  3 ++-
 app/msgviewer.go       |  7 +++----
 app/partswitcher.go    |  5 +++--
 app/selector.go        |  5 +++--
 app/terminal.go        |  7 ++++---
 commands/patch/list.go |  4 ++--
 commands/util.go       |  4 ++--
 lib/ui/borders.go      |  5 ++---
 lib/ui/box.go          |  6 +++---
 lib/ui/grid.go         |  3 ++-
 lib/ui/interfaces.go   |  6 +++---
 lib/ui/popover.go      |  4 ++--
 lib/ui/tab.go          |  3 ++-
 lib/ui/textinput.go    |  5 +++--
 lib/ui/ui.go           |  2 +-
 24 files changed, 59 insertions(+), 47 deletions(-)

diff --git a/app/account-wizard.go b/app/account-wizard.go
index 9ee69627745c..e47acc53a856 100644
--- a/app/account-wizard.go
+++ b/app/account-wizard.go
@@ -22,6 +22,7 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/lib/xdg"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
)

const (
@@ -747,7 +748,7 @@ func (wizard *AccountWizard) Focus(focus bool) {
	}
}

func (wizard *AccountWizard) Event(event tcell.Event) bool {
func (wizard *AccountWizard) Event(event vaxis.Event) bool {
	interactive := wizard.getInteractive()
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
diff --git a/app/account.go b/app/account.go
index 5b5405238725..4b5fa30b92db 100644
--- a/app/account.go
+++ b/app/account.go
@@ -7,8 +7,6 @@ import (
	"sync"
	"time"

	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
	"git.sr.ht/~rjarry/aerc/lib/hooks"
@@ -21,6 +19,7 @@ import (
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
)

var _ ProvidesMessages = (*AccountView)(nil)
@@ -168,7 +167,7 @@ func (acct *AccountView) Draw(ctx *ui.Context) {
	acct.grid.Draw(ctx)
}

func (acct *AccountView) MouseEvent(localX int, localY int, event tcell.Event) {
func (acct *AccountView) MouseEvent(localX int, localY int, event vaxis.Event) {
	acct.grid.MouseEvent(localX, localY, event)
}

diff --git a/app/aerc.go b/app/aerc.go
index c73075401bbe..291142836c24 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -11,6 +11,7 @@ import (
	"time"

	"git.sr.ht/~rjarry/go-opt"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
@@ -291,7 +292,7 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
	}
}

func (aerc *Aerc) Event(event tcell.Event) bool {
func (aerc *Aerc) Event(event vaxis.Event) bool {
	if aerc.dialog != nil {
		return aerc.dialog.Event(event)
	}
diff --git a/app/compose.go b/app/compose.go
index 35fdd9bfae79..a73127c9c3fd 100644
--- a/app/compose.go
+++ b/app/compose.go
@@ -32,6 +32,7 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
)

type Composer struct {
@@ -834,7 +835,7 @@ func (c *Composer) focusActiveWidget(focus bool) {
	}
}

func (c *Composer) Event(event tcell.Event) bool {
func (c *Composer) Event(event vaxis.Event) bool {
	c.Lock()
	defer c.Unlock()
	if w := c.focusedWidget(); c.editor != nil && w != nil {
@@ -843,7 +844,7 @@ func (c *Composer) Event(event tcell.Event) bool {
	return false
}

func (c *Composer) MouseEvent(localX int, localY int, event tcell.Event) {
func (c *Composer) MouseEvent(localX int, localY int, event vaxis.Event) {
	c.Lock()
	for _, e := range c.focusable {
		he, ok := e.(*headerEditor)
@@ -1222,7 +1223,7 @@ func (c *Composer) resetReview() {
	}
}

func (c *Composer) termEvent(event tcell.Event) bool {
func (c *Composer) termEvent(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventMouse); ok {
		if event.Buttons() == tcell.Button1 {
			c.FocusTerminal()
@@ -1643,7 +1644,7 @@ func (he *headerEditor) Draw(ctx *ui.Context) {
	he.input.Draw(ctx.Subcontext(size, 0, ctx.Width()-size, 1))
}

func (he *headerEditor) MouseEvent(localX int, localY int, event tcell.Event) {
func (he *headerEditor) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		if event.Buttons() == tcell.Button1 {
			he.focused = true
@@ -1665,7 +1666,7 @@ func (he *headerEditor) Focus(focused bool) {
	he.input.Focus(focused)
}

func (he *headerEditor) Event(event tcell.Event) bool {
func (he *headerEditor) Event(event vaxis.Event) bool {
	return he.input.Event(event)
}

diff --git a/app/dirlist.go b/app/dirlist.go
index d260d7c195f3..53aeed9d15de 100644
--- a/app/dirlist.go
+++ b/app/dirlist.go
@@ -19,6 +19,7 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
)

type DirectoryLister interface {
@@ -374,7 +375,7 @@ func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
	ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}

func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
diff --git a/app/dirtree.go b/app/dirtree.go
index 559a717e1008..c3ad2ef6246b 100644
--- a/app/dirtree.go
+++ b/app/dirtree.go
@@ -14,6 +14,7 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

@@ -166,7 +167,7 @@ func (dt *DirectoryTree) Draw(ctx *ui.Context) {
	}
}

func (dt *DirectoryTree) MouseEvent(localX int, localY int, event tcell.Event) {
func (dt *DirectoryTree) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
diff --git a/app/exline.go b/app/exline.go
index 61fc340a7a2f..7eb6fde3ea26 100644
--- a/app/exline.go
+++ b/app/exline.go
@@ -6,6 +6,7 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
)

type ExLine struct {
@@ -81,7 +82,7 @@ func (ex *ExLine) Focus(focus bool) {
	ex.input.Focus(focus)
}

func (ex *ExLine) Event(event tcell.Event) bool {
func (ex *ExLine) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyEnter, tcell.KeyCtrlJ:
diff --git a/app/getpasswd.go b/app/getpasswd.go
index 9a3dbf3b1c02..8781bce79a87 100644
--- a/app/getpasswd.go
+++ b/app/getpasswd.go
@@ -7,6 +7,7 @@ import (

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
)

type GetPasswd struct {
@@ -44,7 +45,7 @@ func (gp *GetPasswd) Invalidate() {
	ui.Invalidate()
}

func (gp *GetPasswd) Event(event tcell.Event) bool {
func (gp *GetPasswd) Event(event vaxis.Event) bool {
	switch event := event.(type) {
	case *tcell.EventKey:
		switch event.Key() {
diff --git a/app/listbox.go b/app/listbox.go
index 5a80261e8925..f6b97e23dd29 100644
--- a/app/listbox.go
+++ b/app/listbox.go
@@ -8,6 +8,7 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
)
@@ -213,7 +214,7 @@ func (lb *ListBox) Invalidate() {
	ui.Invalidate()
}

func (lb *ListBox) Event(event tcell.Event) bool {
func (lb *ListBox) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyLeft:
diff --git a/app/msglist.go b/app/msglist.go
index 7948e6a169d4..9217423941ce 100644
--- a/app/msglist.go
+++ b/app/msglist.go
@@ -17,6 +17,7 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
)

type MessageList struct {
@@ -253,7 +254,7 @@ func (ml *MessageList) drawScrollbar(ctx *ui.Context) {
	ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}

func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) {
func (ml *MessageList) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
diff --git a/app/msgviewer.go b/app/msgviewer.go
index 8d2e380a9987..a22d2c15df70 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -13,7 +13,6 @@ import (

	"github.com/danwakefield/fnmatch"
	"github.com/emersion/go-message/textproto"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -286,7 +285,7 @@ func (mv *MessageViewer) Draw(ctx *ui.Context) {
	mv.grid.Draw(ctx)
}

func (mv *MessageViewer) MouseEvent(localX int, localY int, event tcell.Event) {
func (mv *MessageViewer) MouseEvent(localX int, localY int, event vaxis.Event) {
	if mv.err != nil {
		return
	}
@@ -398,7 +397,7 @@ func (mv *MessageViewer) Close() {
	}
}

func (mv *MessageViewer) Event(event tcell.Event) bool {
func (mv *MessageViewer) Event(event vaxis.Event) bool {
	return mv.switcher.Event(event)
}

@@ -824,7 +823,7 @@ func (pv *PartViewer) resized(ctx *ui.Context) bool {
	return false
}

func (pv *PartViewer) Event(event tcell.Event) bool {
func (pv *PartViewer) Event(event vaxis.Event) bool {
	if pv.term != nil {
		return pv.term.Event(event)
	}
diff --git a/app/partswitcher.go b/app/partswitcher.go
index 3f070c44883c..8552d32b4682 100644
--- a/app/partswitcher.go
+++ b/app/partswitcher.go
@@ -5,6 +5,7 @@ import (

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
)
@@ -79,7 +80,7 @@ func (ps *PartSwitcher) Show(visible bool) {
	}
}

func (ps *PartSwitcher) Event(event tcell.Event) bool {
func (ps *PartSwitcher) Event(event vaxis.Event) bool {
	return ps.parts[ps.selected].Event(event)
}

@@ -161,7 +162,7 @@ func (ps *PartSwitcher) drawScrollbar(ctx *ui.Context) {
	ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}

func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) {
func (ps *PartSwitcher) MouseEvent(localX int, localY int, event vaxis.Event) {
	if localY < ps.offset && ps.parts[ps.selected].term != nil {
		ps.parts[ps.selected].term.MouseEvent(localX, localY, event)
		return
diff --git a/app/selector.go b/app/selector.go
index 7f4730d07f7e..fe8c4d963d5c 100644
--- a/app/selector.go
+++ b/app/selector.go
@@ -9,6 +9,7 @@ import (

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
)

type Selector struct {
@@ -143,7 +144,7 @@ func (sel *Selector) Focus(focus bool) {
	sel.Invalidate()
}

func (sel *Selector) Event(event tcell.Event) bool {
func (sel *Selector) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyCtrlH:
@@ -239,7 +240,7 @@ func (gp *SelectorDialog) Invalidate() {
	ui.Invalidate()
}

func (gp *SelectorDialog) Event(event tcell.Event) bool {
func (gp *SelectorDialog) Event(event vaxis.Event) bool {
	switch event := event.(type) {
	case *tcell.EventKey:
		switch event.Key() {
diff --git a/app/terminal.go b/app/terminal.go
index f0335de32815..c99af26ab9c1 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -8,6 +8,7 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	tcellterm "git.sr.ht/~rockorager/tcell-term"
	"git.sr.ht/~rockorager/vaxis"

	"github.com/gdamore/tcell/v2"
)
@@ -26,7 +27,7 @@ type Terminal struct {
	running bool

	OnClose func(err error)
	OnEvent func(event tcell.Event) bool
	OnEvent func(event vaxis.Event) bool
	OnStart func()
	OnTitle func(title string)
}
@@ -124,7 +125,7 @@ func (term *Terminal) Terminal() *Terminal {
	return term
}

func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
func (term *Terminal) MouseEvent(localX int, localY int, event vaxis.Event) {
	ev, ok := event.(*tcell.EventMouse)
	if !ok {
		return
@@ -176,7 +177,7 @@ func (term *Terminal) HandleEvent(ev tcell.Event) {
	}
}

func (term *Terminal) Event(event tcell.Event) bool {
func (term *Terminal) Event(event vaxis.Event) bool {
	if term.OnEvent != nil {
		if term.OnEvent(event) {
			return true
diff --git a/commands/patch/list.go b/commands/patch/list.go
index 6dcc06e806e6..61f577067abd 100644
--- a/commands/patch/list.go
+++ b/commands/patch/list.go
@@ -13,7 +13,7 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/pama/models"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/go-opt"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
)

type List struct {
@@ -65,7 +65,7 @@ func (l List) Execute(args []string) error {
				app.CloseDialog()
				return
			}
			term.OnEvent = func(_ tcell.Event) bool {
			term.OnEvent = func(_ vaxis.Event) bool {
				app.CloseDialog()
				return true
			}
diff --git a/commands/util.go b/commands/util.go
index 6247f579776c..5b5e093e0754 100644
--- a/commands/util.go
+++ b/commands/util.go
@@ -19,7 +19,7 @@ import (
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rjarry/go-opt"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
)

// QuickTerm is an ephemeral terminal for running a single command and quitting.
@@ -43,7 +43,7 @@ func QuickTerm(args []string, stdin io.Reader) (*app.Terminal, error) {
		} else {
			app.PushStatus("Process complete, press any key to close.",
				10*time.Second)
			term.OnEvent = func(event tcell.Event) bool {
			term.OnEvent = func(event vaxis.Event) bool {
				app.RemoveTab(term, true)
				return true
			}
diff --git a/lib/ui/borders.go b/lib/ui/borders.go
index 81d5db61c029..fb3db6b128f5 100644
--- a/lib/ui/borders.go
+++ b/lib/ui/borders.go
@@ -1,9 +1,8 @@
package ui

import (
	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rockorager/vaxis"
)

const (
@@ -69,7 +68,7 @@ func (bordered *Bordered) Draw(ctx *Context) {
	bordered.content.Draw(subctx)
}

func (bordered *Bordered) MouseEvent(localX int, localY int, event tcell.Event) {
func (bordered *Bordered) MouseEvent(localX int, localY int, event vaxis.Event) {
	if content, ok := bordered.content.(Mouseable); ok {
		content.MouseEvent(localX, localY, event)
	}
diff --git a/lib/ui/box.go b/lib/ui/box.go
index 96b95d590556..9f6f9442f9dd 100644
--- a/lib/ui/box.go
+++ b/lib/ui/box.go
@@ -4,7 +4,7 @@ import (
	"strings"

	"git.sr.ht/~rjarry/aerc/config"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/mattn/go-runewidth"
)

@@ -57,13 +57,13 @@ func (b *Box) Invalidate() {
	b.content.Invalidate()
}

func (b *Box) MouseEvent(localX int, localY int, event tcell.Event) {
func (b *Box) MouseEvent(localX int, localY int, event vaxis.Event) {
	if content, ok := b.content.(Mouseable); ok {
		content.MouseEvent(localX, localY, event)
	}
}

func (b *Box) Event(e tcell.Event) bool {
func (b *Box) Event(e vaxis.Event) bool {
	if content, ok := b.content.(Interactive); ok {
		return content.Event(e)
	}
diff --git a/lib/ui/grid.go b/lib/ui/grid.go
index 28640d033a63..00f759bf9cc3 100644
--- a/lib/ui/grid.go
+++ b/lib/ui/grid.go
@@ -4,6 +4,7 @@ import (
	"math"
	"sync"

	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

@@ -128,7 +129,7 @@ func (grid *Grid) Draw(ctx *Context) {
	}
}

func (grid *Grid) MouseEvent(localX int, localY int, event tcell.Event) {
func (grid *Grid) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {

		grid.mutex.RLock()
diff --git a/lib/ui/interfaces.go b/lib/ui/interfaces.go
index a8520ff5db22..8ed727ccedb9 100644
--- a/lib/ui/interfaces.go
+++ b/lib/ui/interfaces.go
@@ -1,7 +1,7 @@
package ui

import (
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
)

// Drawable is a UI component that can draw. Unless specified, all methods must
@@ -24,7 +24,7 @@ type Visible interface {

type Interactive interface {
	// Returns true if the event was handled by this component
	Event(event tcell.Event) bool
	Event(event vaxis.Event) bool
	// Indicates whether or not this control will receive input events
	Focus(focus bool)
}
@@ -53,7 +53,7 @@ type Container interface {

type MouseHandler interface {
	// Handle a mouse event which occurred at the local x and y positions
	MouseEvent(localX int, localY int, event tcell.Event)
	MouseEvent(localX int, localY int, event vaxis.Event)
}

// A drawable that can be interacted with by the mouse
diff --git a/lib/ui/popover.go b/lib/ui/popover.go
index 9cc491dab7a3..5a6d05426951 100644
--- a/lib/ui/popover.go
+++ b/lib/ui/popover.go
@@ -1,6 +1,6 @@
package ui

import "github.com/gdamore/tcell/v2"
import "git.sr.ht/~rockorager/vaxis"

type Popover struct {
	x, y, width, height int
@@ -39,7 +39,7 @@ func (p *Popover) Draw(ctx *Context) {
	p.content.Draw(subcontext)
}

func (p *Popover) Event(e tcell.Event) bool {
func (p *Popover) Event(e vaxis.Event) bool {
	if di, ok := p.content.(DrawableInteractive); ok {
		return di.Event(e)
	}
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index 43b6e14f6cdb..07b304ee500e 100644
--- a/lib/ui/tab.go
+++ b/lib/ui/tab.go
@@ -7,6 +7,7 @@ import (
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rockorager/vaxis"
)

const tabRuneWidth int = 32 // TODO: make configurable
@@ -482,7 +483,7 @@ func (content *TabContent) Draw(ctx *Context) {
	tab.Content.Draw(ctx)
}

func (content *TabContent) MouseEvent(localX int, localY int, event tcell.Event) {
func (content *TabContent) MouseEvent(localX int, localY int, event vaxis.Event) {
	content.parent.m.Lock()
	tab := content.tabs[content.curIndex]
	content.parent.m.Unlock()
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 32a177e150be..0bdcd4359a95 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -11,6 +11,7 @@ import (

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
)

// TODO: Attach history providers
@@ -332,7 +333,7 @@ func (ti *TextInput) OnFocusLost(onFocusLost func(ti *TextInput)) {
	ti.focusLost = append(ti.focusLost, onFocusLost)
}

func (ti *TextInput) Event(event tcell.Event) bool {
func (ti *TextInput) Event(event vaxis.Event) bool {
	ti.Lock()
	defer ti.Unlock()
	if event, ok := event.(*tcell.EventKey); ok {
@@ -480,7 +481,7 @@ func (c *completions) exec() {
	Invalidate()
}

func (c *completions) Event(e tcell.Event) bool {
func (c *completions) Event(e vaxis.Event) bool {
	if e, ok := e.(*tcell.EventKey); ok {
		k := c.ti.completeKey
		if k != nil && k.Key == e.Key() && k.Modifiers == e.Modifiers() {
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index b80309499248..e13f4f5cf6e7 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -139,7 +139,7 @@ func EnableMouse() {
	state.screen.EnableMouse()
}

func HandleEvent(event tcell.Event) {
func HandleEvent(event vaxis.Event) {
	if event, ok := event.(*tcell.EventResize); ok {
		state.screen.Clear()
		width, height := event.Size()
-- 
2.43.0

[PATCH aerc v3 06/17] ui: remove screen and viewports Export this patch

Remove references to tcell.Screen or views.Viewports. Convert Contexts
and the core UI struct to use Vaxis objects only.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/terminal.go     | 12 +++---
 lib/ui/context.go   | 89 +++++++++++++++++++++++----------------------
 lib/ui/textinput.go |  4 +-
 lib/ui/ui.go        | 49 +++++++++++++------------
 main.go             |  4 --
 5 files changed, 78 insertions(+), 80 deletions(-)

diff --git a/app/terminal.go b/app/terminal.go
index c99af26ab9c1..33478206f166 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -83,11 +83,11 @@ func (term *Terminal) Invalidate() {
}

func (term *Terminal) Draw(ctx *ui.Context) {
	term.vterm.SetSurface(ctx.View())
	term.vterm.SetSurface(ctx)

	w, h := ctx.View().Size()
	w, h := ctx.Size()
	if !term.isClosed() && term.ctx != nil {
		ow, oh := term.ctx.View().Size()
		ow, oh := term.ctx.Size()
		if w != ow || h != oh {
			term.vterm.Resize(w, h)
		}
@@ -109,8 +109,7 @@ func (term *Terminal) Draw(ctx *ui.Context) {
	if term.focus {
		y, x, style, vis := term.vterm.Cursor()
		if vis && !term.isClosed() {
			ctx.SetCursor(x, y)
			ctx.SetCursorStyle(style)
			ctx.SetCursor(x, y, vaxis.CursorStyle(style))
		} else {
			ctx.HideCursor()
		}
@@ -150,8 +149,7 @@ func (term *Terminal) Focus(focus bool) {
			term.ctx.HideCursor()
		} else {
			y, x, style, _ := term.vterm.Cursor()
			term.ctx.SetCursor(x, y)
			term.ctx.SetCursorStyle(style)
			term.ctx.SetCursor(x, y, vaxis.CursorStyle(style))
			term.Invalidate()
		}
	}
diff --git a/lib/ui/context.go b/lib/ui/context.go
index 39933fa30789..10089179c6c3 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -6,35 +6,22 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/parse"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/gdamore/tcell/v2/views"
)

// A context allows you to draw in a sub-region of the terminal
type Context struct {
	screen    tcell.Screen
	viewport  *views.ViewPort
	window    vaxis.Window
	x, y      int
	onPopover func(*Popover)
}

func (ctx *Context) X() int {
	x, _, _, _ := ctx.viewport.GetPhysical()
	return x
}

func (ctx *Context) Y() int {
	_, y, _, _ := ctx.viewport.GetPhysical()
	return y
}

func (ctx *Context) Width() int {
	width, _ := ctx.viewport.Size()
	width, _ := ctx.window.Size()
	return width
}

func (ctx *Context) Height() int {
	_, height := ctx.viewport.Size()
	_, height := ctx.window.Size()
	return height
}

@@ -43,39 +30,37 @@ func (ctx *Context) Window() vaxis.Window {
	return ctx.window
}

func NewContext(width, height int, screen tcell.Screen, p func(*Popover)) *Context {
	vp := views.NewViewPort(screen, 0, 0, width, height)
	win := screen.Vaxis().Window()
	return &Context{screen, vp, win, 0, 0, p}
func NewContext(width, height int, vx *vaxis.Vaxis, p func(*Popover)) *Context {
	win := vx.Window()
	return &Context{win, 0, 0, p}
}

func (ctx *Context) Subcontext(x, y, width, height int) *Context {
	vp_width, vp_height := ctx.viewport.Size()
	if x < 0 || y < 0 {
		panic(fmt.Errorf("Attempted to create context with negative offset"))
	}
	if x+width > vp_width || y+height > vp_height {
		panic(fmt.Errorf("Attempted to create context larger than parent"))
	}
	vp := views.NewViewPort(ctx.viewport, x, y, width, height)
	win := ctx.window.New(x, y, width, height)
	return &Context{ctx.screen, vp, win, ctx.x + x, ctx.y + y, ctx.onPopover}
	return &Context{win, x, y, ctx.onPopover}
}

func (ctx *Context) SetCell(x, y int, ch rune, style tcell.Style) {
	width, height := ctx.viewport.Size()
	width, height := ctx.window.Size()
	if x >= width || y >= height {
		// no-op when dims are inadequate
		return
	}
	crunes := []rune{}
	ctx.viewport.SetContent(x, y, ch, crunes, style)
	ctx.window.SetCell(x, y, vaxis.Cell{
		Character: vaxis.Character{
			Grapheme: string(ch),
		},
		Style: tcell.VaxisStyle(style),
	})
}

func (ctx *Context) Printf(x, y int, style tcell.Style,
	format string, a ...interface{},
) int {
	width, height := ctx.viewport.Size()
	width, height := ctx.window.Size()

	if x >= width || y >= height {
		// no-op when dims are inadequate
@@ -103,8 +88,13 @@ func (ctx *Context) Printf(x, y int, style tcell.Style,
		case '\r':
			x = old_x
		default:
			crunes := []rune{}
			ctx.viewport.SetContent(x, y, sr.Value, crunes, sr.Style)
			ctx.window.SetCell(x, y, vaxis.Cell{
				Character: vaxis.Character{
					Grapheme: string(sr.Value),
					Width:    sr.Width,
				},
				Style: tcell.VaxisStyle(sr.Style),
			})
			x += sr.Width
			if x == old_x+width {
				if !newline() {
@@ -118,20 +108,22 @@ func (ctx *Context) Printf(x, y int, style tcell.Style,
}

func (ctx *Context) Fill(x, y, width, height int, rune rune, style tcell.Style) {
	vp := views.NewViewPort(ctx.viewport, x, y, width, height)
	vp.Fill(rune, style)
	win := ctx.window.New(x, y, width, height)
	win.Fill(vaxis.Cell{
		Character: vaxis.Character{
			Grapheme: string(rune),
			Width:    1,
		},
		Style: tcell.VaxisStyle(style),
	})
}

func (ctx *Context) SetCursor(x, y int) {
	ctx.screen.ShowCursor(ctx.x+x, ctx.y+y)
}

func (ctx *Context) SetCursorStyle(cs tcell.CursorStyle) {
	ctx.screen.SetCursorStyle(cs)
func (ctx *Context) SetCursor(x, y int, style vaxis.CursorStyle) {
	ctx.window.ShowCursor(x, y, style)
}

func (ctx *Context) HideCursor() {
	ctx.screen.HideCursor()
	ctx.window.Vx.HideCursor()
}

func (ctx *Context) Popover(x, y, width, height int, d Drawable) {
@@ -144,10 +136,19 @@ func (ctx *Context) Popover(x, y, width, height int, d Drawable) {
	})
}

func (ctx *Context) View() *views.ViewPort {
	return ctx.viewport
// SetContent is used to update the content of the Surface at the given
// location.
func (ctx *Context) SetContent(x int, y int, ch rune, comb []rune, style tcell.Style) {
	g := []rune{ch}
	g = append(g, comb...)
	ctx.window.SetCell(x, y, vaxis.Cell{
		Character: vaxis.Character{
			Grapheme: string(g),
		},
		Style: tcell.VaxisStyle(style),
	})
}

func (ctx *Context) Show() {
	ctx.screen.Show()
func (ctx *Context) Size() (int, int) {
	return ctx.window.Size()
}
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 0bdcd4359a95..107d354c1980 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -120,7 +120,7 @@ func (ti *TextInput) Draw(ctx *Context) {
	}
	cells := runewidth.StringWidth(string(text[:sindex]) + ti.prompt)
	if ti.focus {
		ctx.SetCursor(cells, 0)
		ctx.SetCursor(cells, 0, vaxis.CursorDefault)
		ti.drawPopover(ctx)
	}
}
@@ -163,7 +163,7 @@ func (ti *TextInput) Focus(focus bool) {
	ti.focus = focus
	if focus && ti.ctx != nil {
		cells := runewidth.StringWidth(string(ti.text[:ti.index]))
		ti.ctx.SetCursor(cells+1, 0)
		ti.ctx.SetCursor(cells+1, 0, vaxis.CursorDefault)
	} else if !focus && ti.ctx != nil {
		ti.ctx.HideCursor()
	}
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index e13f4f5cf6e7..02e945c040ef 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -7,14 +7,15 @@ import (
	"syscall"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

// Use unbuffered channels (always blocking unless somebody can read
// immediately) We are merely using this as a proxy to tcell screen internal
// event channel.
var Events = make(chan tcell.Event)
// immediately) We are merely using this as a proxy to the internal vaxis event
// channel.
var Events = make(chan vaxis.Event)

var Quit = make(chan struct{})

@@ -40,7 +41,7 @@ func Invalidate() {
var state struct {
	content DrawableInteractive
	ctx     *Context
	screen  tcell.Screen
	vx      *vaxis.Vaxis
	popover *Popover
	dirty   uint32 // == 1 if render has been queued in Redraw channel
	// == 1 if suspend is pending
@@ -60,15 +61,16 @@ func Initialize(content DrawableInteractive) error {
		return err
	}

	screen.Clear()
	screen.HideCursor()
	screen.EnablePaste()
	vx := screen.Vaxis()

	width, height := screen.Size()
	vx.Window().Clear()
	vx.HideCursor()

	width, height := vx.Window().Size()

	state.content = content
	state.screen = screen
	state.ctx = NewContext(width, height, state.screen, onPopover)
	state.vx = vx
	state.ctx = NewContext(width, height, state.vx, onPopover)

	Invalidate()
	if beeper, ok := content.(DrawableInteractiveBeeper); ok {
@@ -76,7 +78,12 @@ func Initialize(content DrawableInteractive) error {
	}
	content.Focus(true)

	go state.screen.ChannelEvents(Events, Quit)
	go func() {
		defer log.PanicHandler()
		for event := range vx.Events() {
			Events <- tcell.TcellEvent(event)
		}
	}()

	return nil
}
@@ -100,7 +107,7 @@ func QueueSuspend() {
func Suspend() error {
	var err error
	if atomic.SwapUint32(&state.suspending, 0) != 0 {
		err = state.screen.Suspend()
		err = state.vx.Suspend()
		if err == nil {
			sigcont := make(chan os.Signal, 1)
			signal.Notify(sigcont, syscall.SIGCONT)
@@ -109,21 +116,21 @@ func Suspend() error {
				<-sigcont
			}
			signal.Reset(syscall.SIGCONT)
			err = state.screen.Resume()
			err = state.vx.Resume()
			state.content.Draw(state.ctx)
			state.screen.Show()
			state.vx.Render()
		}
	}
	return err
}

func Close() {
	state.screen.Fini()
	state.vx.Close()
}

func Render() {
	if atomic.SwapUint32(&state.dirty, 0) != 0 {
		state.screen.Clear()
		state.vx.Window().Clear()
		// reset popover for the next Draw
		state.popover = nil
		state.content.Draw(state.ctx)
@@ -131,19 +138,15 @@ func Render() {
			// if the Draw resulted in a popover, draw it
			state.popover.Draw(state.ctx)
		}
		state.screen.Show()
		state.vx.Render()
	}
}

func EnableMouse() {
	state.screen.EnableMouse()
}

func HandleEvent(event vaxis.Event) {
	if event, ok := event.(*tcell.EventResize); ok {
		state.screen.Clear()
		state.vx.Window().Clear()
		width, height := event.Size()
		state.ctx = NewContext(width, height, state.screen, onPopover)
		state.ctx = NewContext(width, height, state.vx, onPopover)
		Invalidate()
	}
	if event, ok := event.(tcell.VaxisEvent); ok {
diff --git a/main.go b/main.go
index a7e2980deede..f592189adce2 100644
--- a/main.go
+++ b/main.go
@@ -301,10 +301,6 @@ func main() {
	}
	close(deferLoop)

	if config.Ui.MouseEnabled {
		ui.EnableMouse()
	}

	as, err := ipc.StartServer(app.IPCHandler())
	if err != nil {
		log.Warnf("Failed to start Unix server: %v", err)
-- 
2.43.0

[PATCH aerc v3 07/17] ui: initialize vaxis directly, drop tcell.Screen initialization Export this patch

Use Vaxis library directly to initialize the UI, dropping the need for a
tcell Screen implementation

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 lib/ui/context.go    |  2 +-
 lib/ui/interfaces.go |  2 +-
 lib/ui/ui.go         | 47 ++++++++++++++++++--------------------------
 3 files changed, 21 insertions(+), 30 deletions(-)

diff --git a/lib/ui/context.go b/lib/ui/context.go
index 10089179c6c3..f1cc2a951349 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -30,7 +30,7 @@ func (ctx *Context) Window() vaxis.Window {
	return ctx.window
}

func NewContext(width, height int, vx *vaxis.Vaxis, p func(*Popover)) *Context {
func NewContext(vx *vaxis.Vaxis, p func(*Popover)) *Context {
	win := vx.Window()
	return &Context{win, 0, 0, p}
}
diff --git a/lib/ui/interfaces.go b/lib/ui/interfaces.go
index 8ed727ccedb9..3f2f95091036 100644
--- a/lib/ui/interfaces.go
+++ b/lib/ui/interfaces.go
@@ -30,7 +30,7 @@ type Interactive interface {
}

type Beeper interface {
	OnBeep(func() error)
	OnBeep(func())
}

type DrawableInteractive interface {
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index 02e945c040ef..f17ca4b383d5 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -49,39 +49,32 @@ var state struct {
}

func Initialize(content DrawableInteractive) error {
	screen, err := tcell.NewScreen()
	opts := vaxis.Options{
		DisableMouse:         !config.Ui.MouseEnabled,
		DisableKittyKeyboard: true,
	}
	vx, err := vaxis.New(opts)
	if err != nil {
		return err
	}

	opts := vaxis.Options{
		DisableMouse: !config.Ui.MouseEnabled,
	}
	if err = screen.Init(opts); err != nil {
		return err
	}

	vx := screen.Vaxis()

	vx.Window().Clear()
	vx.HideCursor()

	width, height := vx.Window().Size()

	state.content = content
	state.vx = vx
	state.ctx = NewContext(width, height, state.vx, onPopover)
	state.ctx = NewContext(state.vx, onPopover)

	Invalidate()
	if beeper, ok := content.(DrawableInteractiveBeeper); ok {
		beeper.OnBeep(screen.Beep)
		beeper.OnBeep(vx.Bell)
	}
	content.Focus(true)

	go func() {
		defer log.PanicHandler()
		for event := range vx.Events() {
			Events <- tcell.TcellEvent(event)
			Events <- event
		}
	}()

@@ -143,20 +136,18 @@ func Render() {
}

func HandleEvent(event vaxis.Event) {
	if event, ok := event.(*tcell.EventResize); ok {
		state.vx.Window().Clear()
		width, height := event.Size()
		state.ctx = NewContext(width, height, state.vx, onPopover)
	switch event := event.(type) {
	case vaxis.Resize:
		state.ctx = NewContext(state.vx, onPopover)
		Invalidate()
	}
	if event, ok := event.(tcell.VaxisEvent); ok {
		if _, ok := event.Vaxis().(vaxis.Redraw); ok {
			Invalidate()
	case vaxis.Redraw:
		Invalidate()
	default:
		event = tcell.TcellEvent(event)
		// if we have a popover, and it can handle the event, it does so
		if state.popover == nil || !state.popover.Event(event) {
			// otherwise, we send the event to the main content
			state.content.Event(event)
		}
	}
	// if we have a popover, and it can handle the event, it does so
	if state.popover == nil || !state.popover.Event(event) {
		// otherwise, we send the event to the main content
		state.content.Event(event)
	}
}
-- 
2.43.0

[PATCH aerc v3 08/17] fill: replace tcell.Style with vaxis.Style Export this patch

Replace the Fill implementation with vaxis style objects

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/account-wizard.go | 2 +-
 app/aerc.go           | 4 ++--
 app/compose.go        | 2 +-
 app/msgviewer.go      | 5 +++--
 lib/ui/context.go     | 4 ++--
 lib/ui/fill.go        | 8 +++-----
 6 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/app/account-wizard.go b/app/account-wizard.go
index e47acc53a856..0fd426ca8b70 100644
--- a/app/account-wizard.go
+++ b/app/account-wizard.go
@@ -128,7 +128,7 @@ func (s *configStep) Grid() *ui.Grid {
	grid := ui.NewGrid().Rows(spec).Columns([]ui.GridSpec{justify})

	intro := ui.NewText(introduction, config.Ui.GetStyle(config.STYLE_DEFAULT))
	fill := ui.NewFill(' ', tcell.StyleDefault)
	fill := ui.NewFill(' ', vaxis.Style{})

	grid.AddChild(fill).At(0, 0)
	grid.AddChild(intro).At(1, 0)
diff --git a/app/aerc.go b/app/aerc.go
index 291142836c24..495545015913 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -860,9 +860,9 @@ func errorScreen(s string) ui.Drawable {
	}).Columns([]ui.GridSpec{
		{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
	})
	grid.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(0, 0)
	grid.AddChild(ui.NewFill(' ', vaxis.Style{})).At(0, 0)
	grid.AddChild(text).At(1, 0)
	grid.AddChild(ui.NewFill(' ', tcell.StyleDefault)).At(2, 0)
	grid.AddChild(ui.NewFill(' ', vaxis.Style{})).At(2, 0)
	return grid
}

diff --git a/app/compose.go b/app/compose.go
index a73127c9c3fd..14341ef6f166 100644
--- a/app/compose.go
+++ b/app/compose.go
@@ -1547,7 +1547,7 @@ func (c *Composer) updateGrid() {
	borderChar := c.acct.UiConfig().BorderCharHorizontal
	grid.AddChild(heditors).At(0, 0)
	grid.AddChild(c.crypto).At(1, 0)
	grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(2, 0)
	grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(2, 0)
	if c.review != nil {
		grid.AddChild(c.review).At(3, 0)
	} else if c.editor != nil {
diff --git a/app/msgviewer.go b/app/msgviewer.go
index a22d2c15df70..179deca410d2 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -13,6 +13,7 @@ import (

	"github.com/danwakefield/fnmatch"
	"github.com/emersion/go-message/textproto"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -145,10 +146,10 @@ func NewMessageViewer(
	grid.AddChild(header).At(0, 0)
	if msg.MessageDetails() != nil || acct.UiConfig().IconUnencrypted != "" {
		grid.AddChild(NewPGPInfo(msg.MessageDetails(), acct.UiConfig())).At(1, 0)
		grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(2, 0)
		grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(2, 0)
		grid.AddChild(switcher).At(3, 0)
	} else {
		grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(1, 0)
		grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(1, 0)
		grid.AddChild(switcher).At(2, 0)
	}

diff --git a/lib/ui/context.go b/lib/ui/context.go
index f1cc2a951349..12621c8af97a 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -43,7 +43,7 @@ func (ctx *Context) Subcontext(x, y, width, height int) *Context {
	return &Context{win, x, y, ctx.onPopover}
}

func (ctx *Context) SetCell(x, y int, ch rune, style tcell.Style) {
func (ctx *Context) SetCell(x, y int, ch rune, style vaxis.Style) {
	width, height := ctx.window.Size()
	if x >= width || y >= height {
		// no-op when dims are inadequate
@@ -53,7 +53,7 @@ func (ctx *Context) SetCell(x, y int, ch rune, style tcell.Style) {
		Character: vaxis.Character{
			Grapheme: string(ch),
		},
		Style: tcell.VaxisStyle(style),
		Style: style,
	})
}

diff --git a/lib/ui/fill.go b/lib/ui/fill.go
index ff248905223c..eedb481e177d 100644
--- a/lib/ui/fill.go
+++ b/lib/ui/fill.go
@@ -1,15 +1,13 @@
package ui

import (
	"github.com/gdamore/tcell/v2"
)
import "git.sr.ht/~rockorager/vaxis"

type Fill struct {
	Rune  rune
	Style tcell.Style
	Style vaxis.Style
}

func NewFill(f rune, s tcell.Style) Fill {
func NewFill(f rune, s vaxis.Style) Fill {
	return Fill{f, s}
}

-- 
2.43.0

[PATCH aerc v3 09/17] aerc: replace tcell keys with vaxis keys Export this patch

Replace all instances of tcell key usage with vaxis keys

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/account-wizard.go |  22 +-
 app/aerc.go           |  51 +++--
 app/exline.go         |  14 +-
 app/getpasswd.go      |  10 +-
 app/listbox.go        |  28 +--
 app/selector.go       |  23 +--
 app/terminal.go       |   1 +
 commands/send-keys.go |   7 +-
 config/binds.go       | 458 +++++++++++++++++++++---------------------
 config/binds_test.go  |  36 ++--
 lib/ui/textinput.go   |  46 +++--
 lib/ui/ui.go          |   2 -
 12 files changed, 352 insertions(+), 346 deletions(-)

diff --git a/app/account-wizard.go b/app/account-wizard.go
index 0fd426ca8b70..36bc0392c1bd 100644
--- a/app/account-wizard.go
+++ b/app/account-wizard.go
@@ -13,7 +13,6 @@ import (
	"sync"

	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/go-ini/ini"
	"golang.org/x/sys/unix"

@@ -750,13 +749,11 @@ func (wizard *AccountWizard) Focus(focus bool) {

func (wizard *AccountWizard) Event(event vaxis.Event) bool {
	interactive := wizard.getInteractive()
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyUp:
			fallthrough
		case tcell.KeyBacktab:
			fallthrough
		case tcell.KeyCtrlK:
	if key, ok := event.(vaxis.Key); ok {
		switch {
		case key.Matches('k', vaxis.ModCtrl),
			key.Matches(vaxis.KeyTab, vaxis.ModShift),
			key.Matches(vaxis.KeyUp):
			if interactive != nil {
				interactive[wizard.focus].Focus(false)
				wizard.focus--
@@ -767,11 +764,10 @@ func (wizard *AccountWizard) Event(event vaxis.Event) bool {
			}
			wizard.Invalidate()
			return true
		case tcell.KeyDown:
			fallthrough
		case tcell.KeyTab:
			fallthrough
		case tcell.KeyCtrlJ:
		case key.Matches('j', vaxis.ModCtrl),
			key.Matches(vaxis.KeyTab),
			key.Matches(vaxis.KeyDown):

			if interactive != nil {
				interactive[wizard.focus].Focus(false)
				wizard.focus++
diff --git a/app/aerc.go b/app/aerc.go
index 495545015913..00f1cb5fc385 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -9,6 +9,7 @@ import (
	"sort"
	"strings"
	"time"
	"unicode"

	"git.sr.ht/~rjarry/go-opt"
	"git.sr.ht/~rockorager/vaxis"
@@ -222,7 +223,7 @@ func (aerc *Aerc) HumanReadableBindings() []string {
	}
	result = append(result, fmt.Sprintf(fmtStr,
		"$ex",
		fmt.Sprintf("'%c'", binds.ExKey.Rune),
		fmt.Sprintf("'%c'", binds.ExKey.Key),
	))
	result = append(result, fmt.Sprintf(fmtStr,
		"Globals",
@@ -277,8 +278,23 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
	aerc.pendingKeys = []config.KeyStroke{}
	aerc.simulating += 1
	for _, stroke := range strokes {
		simulated := tcell.NewEventKey(
			stroke.Key, stroke.Rune, tcell.ModNone)
		simulated := vaxis.Key{
			Keycode:   stroke.Key,
			Modifiers: stroke.Modifiers,
		}
		if unicode.IsUpper(stroke.Key) {
			simulated.Keycode = unicode.ToLower(stroke.Key)
			simulated.Modifiers |= vaxis.ModShift
		}
		// If none of these mods are present, set the text field to
		// enable matching keys like ":"
		if stroke.Modifiers&vaxis.ModCtrl == 0 &&
			stroke.Modifiers&vaxis.ModAlt == 0 &&
			stroke.Modifiers&vaxis.ModSuper == 0 &&
			stroke.Modifiers&vaxis.ModHyper == 0 {

			simulated.Text = string(stroke.Key)
		}
		aerc.Event(simulated)
	}
	aerc.simulating -= 1
@@ -288,7 +304,7 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
			return aerc.complete(cmd)
		})
		// send tab to text input to trigger completion
		exline.Event(tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone))
		exline.Event(vaxis.Key{Keycode: vaxis.KeyTab})
	}
}

@@ -302,7 +318,8 @@ func (aerc *Aerc) Event(event vaxis.Event) bool {
	}

	switch event := event.(type) {
	case *tcell.EventKey:
	// TODO: more vaxis events handling
	case vaxis.Key:
		// If we are in a bracketed paste, don't process the keys for
		// bindings
		if aerc.pasting {
@@ -313,11 +330,17 @@ func (aerc *Aerc) Event(event vaxis.Event) bool {
			return false
		}
		aerc.statusline.Expire()
		aerc.pendingKeys = append(aerc.pendingKeys, config.KeyStroke{
			Modifiers: event.Modifiers(),
			Key:       event.Key(),
			Rune:      event.Rune(),
		})
		stroke := config.KeyStroke{
			Modifiers: event.Modifiers,
		}
		switch {
		case event.ShiftedCode != 0:
			stroke.Key = event.ShiftedCode
			stroke.Modifiers &^= vaxis.ModShift
		default:
			stroke.Key = event.Keycode
		}
		aerc.pendingKeys = append(aerc.pendingKeys, stroke)
		ui.Invalidate()
		bindings := aerc.getBindings()
		incomplete := false
@@ -866,12 +889,8 @@ func errorScreen(s string) ui.Drawable {
	return grid
}

func (aerc *Aerc) isExKey(event *tcell.EventKey, exKey config.KeyStroke) bool {
	if event.Key() == tcell.KeyRune {
		// Compare runes if it's a KeyRune
		return event.Modifiers() == exKey.Modifiers && event.Rune() == exKey.Rune
	}
	return event.Modifiers() == exKey.Modifiers && event.Key() == exKey.Key
func (aerc *Aerc) isExKey(key vaxis.Key, exKey config.KeyStroke) bool {
	return key.Matches(exKey.Key, exKey.Modifiers)
}

// CmdFallbackSearch checks cmds for the first executable availabe in PATH. An error is
diff --git a/app/exline.go b/app/exline.go
index 7eb6fde3ea26..e8b0069ea7f0 100644
--- a/app/exline.go
+++ b/app/exline.go
@@ -1,8 +1,6 @@
package app

import (
	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
	"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -83,20 +81,20 @@ func (ex *ExLine) Focus(focus bool) {
}

func (ex *ExLine) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyEnter, tcell.KeyCtrlJ:
	if key, ok := event.(vaxis.Key); ok {
		switch {
		case key.Matches(vaxis.KeyEnter), key.Matches('j', vaxis.ModCtrl):
			cmd := ex.input.String()
			ex.input.Focus(false)
			ex.commit(cmd)
			ex.finish()
		case tcell.KeyUp:
		case key.Matches(vaxis.KeyUp):
			ex.input.Set(ex.cmdHistory.Prev())
			ex.Invalidate()
		case tcell.KeyDown:
		case key.Matches(vaxis.KeyDown):
			ex.input.Set(ex.cmdHistory.Next())
			ex.Invalidate()
		case tcell.KeyEsc, tcell.KeyCtrlC:
		case key.Matches(vaxis.KeyEsc), key.Matches('c', vaxis.ModCtrl):
			ex.input.Focus(false)
			ex.cmdHistory.Reset()
			ex.finish()
diff --git a/app/getpasswd.go b/app/getpasswd.go
index 8781bce79a87..e5726d91a083 100644
--- a/app/getpasswd.go
+++ b/app/getpasswd.go
@@ -3,8 +3,6 @@ package app
import (
	"fmt"

	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
@@ -47,12 +45,12 @@ func (gp *GetPasswd) Invalidate() {

func (gp *GetPasswd) Event(event vaxis.Event) bool {
	switch event := event.(type) {
	case *tcell.EventKey:
		switch event.Key() {
		case tcell.KeyEnter:
	case vaxis.Key:
		switch {
		case event.Matches(vaxis.KeyEnter):
			gp.input.Focus(false)
			gp.callback(gp.input.String(), nil)
		case tcell.KeyEsc:
		case event.Matches(vaxis.KeyEsc):
			gp.input.Focus(false)
			gp.callback("", fmt.Errorf("no password provided"))
		default:
diff --git a/app/listbox.go b/app/listbox.go
index f6b97e23dd29..d891b24dff08 100644
--- a/app/listbox.go
+++ b/app/listbox.go
@@ -215,17 +215,17 @@ func (lb *ListBox) Invalidate() {
}

func (lb *ListBox) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyLeft:
	if key, ok := event.(vaxis.Key); ok {
		switch {
		case key.Matches(vaxis.KeyLeft):
			lb.moveHorizontal(-1)
			lb.Invalidate()
			return true
		case tcell.KeyRight:
		case key.Matches(vaxis.KeyRight):
			lb.moveHorizontal(+1)
			lb.Invalidate()
			return true
		case tcell.KeyCtrlB:
		case key.Matches('b', vaxis.ModCtrl):
			line := lb.selected[:lb.horizPos]
			fds := strings.Fields(line)
			if len(fds) > 1 {
@@ -237,7 +237,7 @@ func (lb *ListBox) Event(event vaxis.Event) bool {
			}
			lb.Invalidate()
			return true
		case tcell.KeyCtrlW:
		case key.Matches('w', vaxis.ModCtrl):
			line := lb.selected[lb.horizPos+1:]
			fds := strings.Fields(line)
			if len(fds) > 1 {
@@ -245,37 +245,37 @@ func (lb *ListBox) Event(event vaxis.Event) bool {
			}
			lb.Invalidate()
			return true
		case tcell.KeyCtrlA, tcell.KeyHome:
		case key.Matches('a', vaxis.ModCtrl), key.Matches(vaxis.KeyHome):
			lb.horizPos = 0
			lb.Invalidate()
			return true
		case tcell.KeyCtrlE, tcell.KeyEnd:
		case key.Matches('e', vaxis.ModCtrl), key.Matches(vaxis.KeyEnd):
			lb.horizPos = len(lb.selected)
			lb.Invalidate()
			return true
		case tcell.KeyCtrlP, tcell.KeyUp:
		case key.Matches('p', vaxis.ModCtrl), key.Matches(vaxis.KeyUp):
			lb.moveCursor(-1)
			lb.Invalidate()
			return true
		case tcell.KeyCtrlN, tcell.KeyDown:
		case key.Matches('n', vaxis.ModCtrl), key.Matches(vaxis.KeyDown):
			lb.moveCursor(+1)
			lb.Invalidate()
			return true
		case tcell.KeyPgUp:
		case key.Matches(vaxis.KeyPgUp):
			if lb.jump >= 0 {
				lb.moveCursor(-lb.jump)
				lb.Invalidate()
			}
			return true
		case tcell.KeyPgDn:
		case key.Matches(vaxis.KeyPgDown):
			if lb.jump >= 0 {
				lb.moveCursor(+lb.jump)
				lb.Invalidate()
			}
			return true
		case tcell.KeyEnter:
		case key.Matches(vaxis.KeyEnter):
			return lb.quit(lb.selected)
		case tcell.KeyEsc:
		case key.Matches(vaxis.KeyEsc):
			return lb.quit("")
		}
	}
diff --git a/app/selector.go b/app/selector.go
index fe8c4d963d5c..252616f05486 100644
--- a/app/selector.go
+++ b/app/selector.go
@@ -4,7 +4,6 @@ import (
	"fmt"
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -145,11 +144,11 @@ func (sel *Selector) Focus(focus bool) {
}

func (sel *Selector) Event(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventKey); ok {
		switch event.Key() {
		case tcell.KeyCtrlH:
	if key, ok := event.(vaxis.Key); ok {
		switch {
		case key.Matches('h', vaxis.ModCtrl):
			fallthrough
		case tcell.KeyLeft:
		case key.Matches(vaxis.KeyLeft):
			if sel.focus > 0 {
				sel.focus--
				sel.Invalidate()
@@ -157,9 +156,9 @@ func (sel *Selector) Event(event vaxis.Event) bool {
			if sel.onSelect != nil {
				sel.onSelect(sel.Selected())
			}
		case tcell.KeyCtrlL:
		case key.Matches('l', vaxis.ModCtrl):
			fallthrough
		case tcell.KeyRight:
		case key.Matches(vaxis.KeyRight):
			if sel.focus < len(sel.options)-1 {
				sel.focus++
				sel.Invalidate()
@@ -167,7 +166,7 @@ func (sel *Selector) Event(event vaxis.Event) bool {
			if sel.onSelect != nil {
				sel.onSelect(sel.Selected())
			}
		case tcell.KeyEnter:
		case key.Matches(vaxis.KeyEnter):
			if sel.onChoose != nil {
				sel.onChoose(sel.Selected())
			}
@@ -242,12 +241,12 @@ func (gp *SelectorDialog) Invalidate() {

func (gp *SelectorDialog) Event(event vaxis.Event) bool {
	switch event := event.(type) {
	case *tcell.EventKey:
		switch event.Key() {
		case tcell.KeyEnter:
	case vaxis.Key:
		switch {
		case event.Matches(vaxis.KeyEnter):
			gp.selector.Focus(false)
			gp.callback(gp.selector.Selected(), nil)
		case tcell.KeyEsc:
		case event.Matches(vaxis.KeyEsc):
			gp.selector.Focus(false)
			gp.callback("", ErrNoOptionSelected)
		default:
diff --git a/app/terminal.go b/app/terminal.go
index 33478206f166..97cd4410591b 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -176,6 +176,7 @@ func (term *Terminal) HandleEvent(ev tcell.Event) {
}

func (term *Terminal) Event(event vaxis.Event) bool {
	event = tcell.TcellEvent(event)
	if term.OnEvent != nil {
		if term.OnEvent(event) {
			return true
diff --git a/commands/send-keys.go b/commands/send-keys.go
index bb872a3342ad..ff79a8652ada 100644
--- a/commands/send-keys.go
+++ b/commands/send-keys.go
@@ -3,7 +3,7 @@ package commands
import (
	"git.sr.ht/~rjarry/aerc/app"
	"git.sr.ht/~rjarry/aerc/config"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/pkg/errors"
)

@@ -36,7 +36,10 @@ func (s SendKeys) Execute(args []string) error {
	}

	for _, key := range keys2send {
		ev := tcell.NewEventKey(key.Key, key.Rune, key.Modifiers)
		ev := vaxis.Key{
			Keycode:   key.Key,
			Modifiers: key.Modifiers,
		}
		term.Event(ev)
	}

diff --git a/config/binds.go b/config/binds.go
index 8eda890d6e36..7ea2f00efcea 100644
--- a/config/binds.go
+++ b/config/binds.go
@@ -9,9 +9,10 @@ import (
	"path"
	"regexp"
	"strings"
	"unicode"

	"git.sr.ht/~rjarry/aerc/log"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/go-ini/ini"
)

@@ -41,9 +42,8 @@ type BindingConfigContext struct {
}

type KeyStroke struct {
	Modifiers tcell.ModMask
	Key       tcell.Key
	Rune      rune
	Modifiers vaxis.ModifierMask
	Key       rune
}

type Binding struct {
@@ -82,7 +82,7 @@ type BindingSearchResult int
func defaultBindsConfig() *BindingConfig {
	// These bindings are not configurable
	wizard := NewKeyBindings()
	wizard.ExKey = KeyStroke{Key: tcell.KeyCtrlE}
	wizard.ExKey = KeyStroke{Key: 'e', Modifiers: vaxis.ModCtrl}
	wizard.Globals = false
	quit, _ := ParseBinding("<C-q>", ":quit<Enter>")
	wizard.Add(quit)
@@ -275,8 +275,8 @@ func LoadBinds(binds *ini.File, baseName string, baseGroup **KeyBindings) error

func NewKeyBindings() *KeyBindings {
	return &KeyBindings{
		ExKey:            KeyStroke{tcell.ModNone, tcell.KeyRune, ':'},
		CompleteKey:      KeyStroke{tcell.ModNone, tcell.KeyTab, 0},
		ExKey:            KeyStroke{0, ':'},
		CompleteKey:      KeyStroke{0, vaxis.KeyTab},
		Globals:          true,
		contextualCache:  make(map[bindsContextKey]*KeyBindings),
		contextualCounts: make(map[bindsContextType]int),
@@ -404,11 +404,6 @@ func (bindings *KeyBindings) GetBinding(
			if stroke.Key != binding.Input[i].Key {
				goto next
			}
			if stroke.Key == tcell.KeyRune &&
				stroke.Rune != binding.Input[i].Rune {

				goto next
			}
		}
		if len(binding.Input) != len(input) {
			incomplete = true
@@ -437,9 +432,6 @@ func (bindings *KeyBindings) GetReverseBindings(output []KeyStroke) [][]KeyStrok
			if stroke.Key != binding.Output[i].Key {
				goto next
			}
			if stroke.Key == tcell.KeyRune && stroke.Rune != binding.Output[i].Rune {
				goto next
			}
		}
		inputs = append(inputs, binding.Input)
	next:
@@ -453,7 +445,7 @@ func FormatKeyStrokes(keystrokes []KeyStroke) string {
	for _, stroke := range keystrokes {
		s := ""
		for name, ks := range keyNames {
			if ks.Modifiers == stroke.Modifiers && ks.Key == stroke.Key && ks.Rune == stroke.Rune {
			if ks.Modifiers == stroke.Modifiers && ks.Key == stroke.Key {
				switch name {
				case "cr", "c-m":
					s = "<enter>"
@@ -469,8 +461,8 @@ func FormatKeyStrokes(keystrokes []KeyStroke) string {
				break
			}
		}
		if s == "" && stroke.Key == tcell.KeyRune {
			s = string(stroke.Rune)
		if s == "" && stroke.Key < unicode.MaxRune {
			s = string(stroke.Key)
		}
		sb.WriteString(s)
	}
@@ -490,219 +482,218 @@ func FormatKeyStrokes(keystrokes []KeyStroke) string {
var spaceTrimRe = regexp.MustCompile(`^(\s*)(.*?)(\s*)$`)

var keyNames = map[string]KeyStroke{
	"space":     {tcell.ModNone, tcell.KeyRune, ' '},
	"semicolon": {tcell.ModNone, tcell.KeyRune, ';'},
	"enter":     {tcell.ModNone, tcell.KeyEnter, 0},
	"c-enter":   {tcell.ModCtrl, tcell.KeyEnter, 0},
	"a-enter":   {tcell.ModAlt, tcell.KeyEnter, 0},
	"up":        {tcell.ModNone, tcell.KeyUp, 0},
	"c-up":      {tcell.ModCtrl, tcell.KeyUp, 0},
	"a-up":      {tcell.ModAlt, tcell.KeyUp, 0},
	"down":      {tcell.ModNone, tcell.KeyDown, 0},
	"c-down":    {tcell.ModCtrl, tcell.KeyDown, 0},
	"a-down":    {tcell.ModAlt, tcell.KeyDown, 0},
	"right":     {tcell.ModNone, tcell.KeyRight, 0},
	"c-right":   {tcell.ModCtrl, tcell.KeyRight, 0},
	"a-right":   {tcell.ModAlt, tcell.KeyRight, 0},
	"left":      {tcell.ModNone, tcell.KeyLeft, 0},
	"c-left":    {tcell.ModCtrl, tcell.KeyLeft, 0},
	"a-left":    {tcell.ModAlt, tcell.KeyLeft, 0},
	"upleft":    {tcell.ModNone, tcell.KeyUpLeft, 0},
	"upright":   {tcell.ModNone, tcell.KeyUpRight, 0},
	"downleft":  {tcell.ModNone, tcell.KeyDownLeft, 0},
	"downright": {tcell.ModNone, tcell.KeyDownRight, 0},
	"center":    {tcell.ModNone, tcell.KeyCenter, 0},
	"pgup":      {tcell.ModNone, tcell.KeyPgUp, 0},
	"c-pgup":    {tcell.ModCtrl, tcell.KeyPgUp, 0},
	"a-pgup":    {tcell.ModAlt, tcell.KeyPgUp, 0},
	"pgdn":      {tcell.ModNone, tcell.KeyPgDn, 0},
	"c-pgdn":    {tcell.ModCtrl, tcell.KeyPgDn, 0},
	"a-pgdn":    {tcell.ModAlt, tcell.KeyPgDn, 0},
	"home":      {tcell.ModNone, tcell.KeyHome, 0},
	"end":       {tcell.ModNone, tcell.KeyEnd, 0},
	"insert":    {tcell.ModNone, tcell.KeyInsert, 0},
	"delete":    {tcell.ModNone, tcell.KeyDelete, 0},
	"c-delete":  {tcell.ModCtrl, tcell.KeyDelete, 0},
	"a-delete":  {tcell.ModAlt, tcell.KeyDelete, 0},
	"backspace": {tcell.ModNone, tcell.KeyBackspace2, 0},
	"help":      {tcell.ModNone, tcell.KeyHelp, 0},
	"exit":      {tcell.ModNone, tcell.KeyExit, 0},
	"clear":     {tcell.ModNone, tcell.KeyClear, 0},
	"cancel":    {tcell.ModNone, tcell.KeyCancel, 0},
	"print":     {tcell.ModNone, tcell.KeyPrint, 0},
	"pause":     {tcell.ModNone, tcell.KeyPause, 0},
	"backtab":   {tcell.ModNone, tcell.KeyBacktab, 0},
	"f1":        {tcell.ModNone, tcell.KeyF1, 0},
	"f2":        {tcell.ModNone, tcell.KeyF2, 0},
	"f3":        {tcell.ModNone, tcell.KeyF3, 0},
	"f4":        {tcell.ModNone, tcell.KeyF4, 0},
	"f5":        {tcell.ModNone, tcell.KeyF5, 0},
	"f6":        {tcell.ModNone, tcell.KeyF6, 0},
	"f7":        {tcell.ModNone, tcell.KeyF7, 0},
	"f8":        {tcell.ModNone, tcell.KeyF8, 0},
	"f9":        {tcell.ModNone, tcell.KeyF9, 0},
	"f10":       {tcell.ModNone, tcell.KeyF10, 0},
	"f11":       {tcell.ModNone, tcell.KeyF11, 0},
	"f12":       {tcell.ModNone, tcell.KeyF12, 0},
	"f13":       {tcell.ModNone, tcell.KeyF13, 0},
	"f14":       {tcell.ModNone, tcell.KeyF14, 0},
	"f15":       {tcell.ModNone, tcell.KeyF15, 0},
	"f16":       {tcell.ModNone, tcell.KeyF16, 0},
	"f17":       {tcell.ModNone, tcell.KeyF17, 0},
	"f18":       {tcell.ModNone, tcell.KeyF18, 0},
	"f19":       {tcell.ModNone, tcell.KeyF19, 0},
	"f20":       {tcell.ModNone, tcell.KeyF20, 0},
	"f21":       {tcell.ModNone, tcell.KeyF21, 0},
	"f22":       {tcell.ModNone, tcell.KeyF22, 0},
	"f23":       {tcell.ModNone, tcell.KeyF23, 0},
	"f24":       {tcell.ModNone, tcell.KeyF24, 0},
	"f25":       {tcell.ModNone, tcell.KeyF25, 0},
	"f26":       {tcell.ModNone, tcell.KeyF26, 0},
	"f27":       {tcell.ModNone, tcell.KeyF27, 0},
	"f28":       {tcell.ModNone, tcell.KeyF28, 0},
	"f29":       {tcell.ModNone, tcell.KeyF29, 0},
	"f30":       {tcell.ModNone, tcell.KeyF30, 0},
	"f31":       {tcell.ModNone, tcell.KeyF31, 0},
	"f32":       {tcell.ModNone, tcell.KeyF32, 0},
	"f33":       {tcell.ModNone, tcell.KeyF33, 0},
	"f34":       {tcell.ModNone, tcell.KeyF34, 0},
	"f35":       {tcell.ModNone, tcell.KeyF35, 0},
	"f36":       {tcell.ModNone, tcell.KeyF36, 0},
	"f37":       {tcell.ModNone, tcell.KeyF37, 0},
	"f38":       {tcell.ModNone, tcell.KeyF38, 0},
	"f39":       {tcell.ModNone, tcell.KeyF39, 0},
	"f40":       {tcell.ModNone, tcell.KeyF40, 0},
	"f41":       {tcell.ModNone, tcell.KeyF41, 0},
	"f42":       {tcell.ModNone, tcell.KeyF42, 0},
	"f43":       {tcell.ModNone, tcell.KeyF43, 0},
	"f44":       {tcell.ModNone, tcell.KeyF44, 0},
	"f45":       {tcell.ModNone, tcell.KeyF45, 0},
	"f46":       {tcell.ModNone, tcell.KeyF46, 0},
	"f47":       {tcell.ModNone, tcell.KeyF47, 0},
	"f48":       {tcell.ModNone, tcell.KeyF48, 0},
	"f49":       {tcell.ModNone, tcell.KeyF49, 0},
	"f50":       {tcell.ModNone, tcell.KeyF50, 0},
	"f51":       {tcell.ModNone, tcell.KeyF51, 0},
	"f52":       {tcell.ModNone, tcell.KeyF52, 0},
	"f53":       {tcell.ModNone, tcell.KeyF53, 0},
	"f54":       {tcell.ModNone, tcell.KeyF54, 0},
	"f55":       {tcell.ModNone, tcell.KeyF55, 0},
	"f56":       {tcell.ModNone, tcell.KeyF56, 0},
	"f57":       {tcell.ModNone, tcell.KeyF57, 0},
	"f58":       {tcell.ModNone, tcell.KeyF58, 0},
	"f59":       {tcell.ModNone, tcell.KeyF59, 0},
	"f60":       {tcell.ModNone, tcell.KeyF60, 0},
	"f61":       {tcell.ModNone, tcell.KeyF61, 0},
	"f62":       {tcell.ModNone, tcell.KeyF62, 0},
	"f63":       {tcell.ModNone, tcell.KeyF63, 0},
	"f64":       {tcell.ModNone, tcell.KeyF64, 0},
	"c-space":   {tcell.ModCtrl, tcell.KeyCtrlSpace, 0},
	"c-a":       {tcell.ModCtrl, tcell.KeyCtrlA, 0},
	"c-b":       {tcell.ModCtrl, tcell.KeyCtrlB, 0},
	"c-c":       {tcell.ModCtrl, tcell.KeyCtrlC, 0},
	"c-d":       {tcell.ModCtrl, tcell.KeyCtrlD, 0},
	"c-e":       {tcell.ModCtrl, tcell.KeyCtrlE, 0},
	"c-f":       {tcell.ModCtrl, tcell.KeyCtrlF, 0},
	"c-g":       {tcell.ModCtrl, tcell.KeyCtrlG, 0},
	"c-h":       {tcell.ModNone, tcell.KeyCtrlH, 0},
	"c-i":       {tcell.ModNone, tcell.KeyCtrlI, 0},
	"c-j":       {tcell.ModCtrl, tcell.KeyCtrlJ, 0},
	"c-k":       {tcell.ModCtrl, tcell.KeyCtrlK, 0},
	"c-l":       {tcell.ModCtrl, tcell.KeyCtrlL, 0},
	"c-m":       {tcell.ModNone, tcell.KeyCtrlM, 0},
	"c-n":       {tcell.ModCtrl, tcell.KeyCtrlN, 0},
	"c-o":       {tcell.ModCtrl, tcell.KeyCtrlO, 0},
	"c-p":       {tcell.ModCtrl, tcell.KeyCtrlP, 0},
	"c-q":       {tcell.ModCtrl, tcell.KeyCtrlQ, 0},
	"c-r":       {tcell.ModCtrl, tcell.KeyCtrlR, 0},
	"c-s":       {tcell.ModCtrl, tcell.KeyCtrlS, 0},
	"c-t":       {tcell.ModCtrl, tcell.KeyCtrlT, 0},
	"c-u":       {tcell.ModCtrl, tcell.KeyCtrlU, 0},
	"c-v":       {tcell.ModCtrl, tcell.KeyCtrlV, 0},
	"c-w":       {tcell.ModCtrl, tcell.KeyCtrlW, 0},
	"c-x":       {tcell.ModCtrl, tcell.KeyCtrlX, rune(tcell.KeyCAN)},
	"c-y":       {tcell.ModCtrl, tcell.KeyCtrlY, 0}, // TODO: runes for the rest
	"c-z":       {tcell.ModCtrl, tcell.KeyCtrlZ, 0},
	"c-]":       {tcell.ModCtrl, tcell.KeyCtrlRightSq, 0},
	"c-\\":      {tcell.ModCtrl, tcell.KeyCtrlBackslash, 0},
	"c-[":       {tcell.ModCtrl, tcell.KeyCtrlLeftSq, 0},
	"c-^":       {tcell.ModCtrl, tcell.KeyCtrlCarat, 0},
	"c-_":       {tcell.ModCtrl, tcell.KeyCtrlUnderscore, 0},
	"a-space":   {tcell.ModAlt, tcell.KeyRune, ' '},
	"a-0":       {tcell.ModAlt, tcell.KeyRune, '0'},
	"a-1":       {tcell.ModAlt, tcell.KeyRune, '1'},
	"a-2":       {tcell.ModAlt, tcell.KeyRune, '2'},
	"a-3":       {tcell.ModAlt, tcell.KeyRune, '3'},
	"a-4":       {tcell.ModAlt, tcell.KeyRune, '4'},
	"a-5":       {tcell.ModAlt, tcell.KeyRune, '5'},
	"a-6":       {tcell.ModAlt, tcell.KeyRune, '6'},
	"a-7":       {tcell.ModAlt, tcell.KeyRune, '7'},
	"a-8":       {tcell.ModAlt, tcell.KeyRune, '8'},
	"a-9":       {tcell.ModAlt, tcell.KeyRune, '9'},
	"a-a":       {tcell.ModAlt, tcell.KeyRune, 'a'},
	"a-b":       {tcell.ModAlt, tcell.KeyRune, 'b'},
	"a-c":       {tcell.ModAlt, tcell.KeyRune, 'c'},
	"a-d":       {tcell.ModAlt, tcell.KeyRune, 'd'},
	"a-e":       {tcell.ModAlt, tcell.KeyRune, 'e'},
	"a-f":       {tcell.ModAlt, tcell.KeyRune, 'f'},
	"a-g":       {tcell.ModAlt, tcell.KeyRune, 'g'},
	"a-h":       {tcell.ModAlt, tcell.KeyRune, 'h'},
	"a-i":       {tcell.ModAlt, tcell.KeyRune, 'i'},
	"a-j":       {tcell.ModAlt, tcell.KeyRune, 'j'},
	"a-k":       {tcell.ModAlt, tcell.KeyRune, 'k'},
	"a-l":       {tcell.ModAlt, tcell.KeyRune, 'l'},
	"a-m":       {tcell.ModAlt, tcell.KeyRune, 'm'},
	"a-n":       {tcell.ModAlt, tcell.KeyRune, 'n'},
	"a-o":       {tcell.ModAlt, tcell.KeyRune, 'o'},
	"a-p":       {tcell.ModAlt, tcell.KeyRune, 'p'},
	"a-q":       {tcell.ModAlt, tcell.KeyRune, 'q'},
	"a-r":       {tcell.ModAlt, tcell.KeyRune, 'r'},
	"a-s":       {tcell.ModAlt, tcell.KeyRune, 's'},
	"a-t":       {tcell.ModAlt, tcell.KeyRune, 't'},
	"a-u":       {tcell.ModAlt, tcell.KeyRune, 'u'},
	"a-v":       {tcell.ModAlt, tcell.KeyRune, 'v'},
	"a-w":       {tcell.ModAlt, tcell.KeyRune, 'w'},
	"a-x":       {tcell.ModAlt, tcell.KeyRune, 'x'},
	"a-y":       {tcell.ModAlt, tcell.KeyRune, 'y'},
	"a-z":       {tcell.ModAlt, tcell.KeyRune, 'z'},
	"a-]":       {tcell.ModAlt, tcell.KeyRune, ']'},
	"a-\\":      {tcell.ModAlt, tcell.KeyRune, '\\'},
	"a-[":       {tcell.ModAlt, tcell.KeyRune, '['},
	"a-^":       {tcell.ModAlt, tcell.KeyRune, '^'},
	"a-_":       {tcell.ModAlt, tcell.KeyRune, '_'},
	"nul":       {tcell.ModNone, tcell.KeyNUL, 0},
	"soh":       {tcell.ModNone, tcell.KeySOH, 0},
	"stx":       {tcell.ModNone, tcell.KeySTX, 0},
	"etx":       {tcell.ModNone, tcell.KeyETX, 0},
	"eot":       {tcell.ModNone, tcell.KeyEOT, 0},
	"enq":       {tcell.ModNone, tcell.KeyENQ, 0},
	"ack":       {tcell.ModNone, tcell.KeyACK, 0},
	"bel":       {tcell.ModNone, tcell.KeyBEL, 0},
	"bs":        {tcell.ModNone, tcell.KeyBS, 0},
	"tab":       {tcell.ModNone, tcell.KeyTAB, 0},
	"lf":        {tcell.ModNone, tcell.KeyLF, 0},
	"vt":        {tcell.ModNone, tcell.KeyVT, 0},
	"ff":        {tcell.ModNone, tcell.KeyFF, 0},
	"cr":        {tcell.ModNone, tcell.KeyCR, 0},
	"so":        {tcell.ModNone, tcell.KeySO, 0},
	"si":        {tcell.ModNone, tcell.KeySI, 0},
	"dle":       {tcell.ModNone, tcell.KeyDLE, 0},
	"dc1":       {tcell.ModNone, tcell.KeyDC1, 0},
	"dc2":       {tcell.ModNone, tcell.KeyDC2, 0},
	"dc3":       {tcell.ModNone, tcell.KeyDC3, 0},
	"dc4":       {tcell.ModNone, tcell.KeyDC4, 0},
	"nak":       {tcell.ModNone, tcell.KeyNAK, 0},
	"syn":       {tcell.ModNone, tcell.KeySYN, 0},
	"etb":       {tcell.ModNone, tcell.KeyETB, 0},
	"can":       {tcell.ModNone, tcell.KeyCAN, 0},
	"em":        {tcell.ModNone, tcell.KeyEM, 0},
	"sub":       {tcell.ModNone, tcell.KeySUB, 0},
	"esc":       {tcell.ModNone, tcell.KeyESC, 0},
	"fs":        {tcell.ModNone, tcell.KeyFS, 0},
	"gs":        {tcell.ModNone, tcell.KeyGS, 0},
	"rs":        {tcell.ModNone, tcell.KeyRS, 0},
	"us":        {tcell.ModNone, tcell.KeyUS, 0},
	"del":       {tcell.ModNone, tcell.KeyDEL, 0},
	"space":     {vaxis.ModifierMask(0), ' '},
	"semicolon": {vaxis.ModifierMask(0), ';'},
	"enter":     {vaxis.ModifierMask(0), vaxis.KeyEnter},
	"c-enter":   {vaxis.ModCtrl, vaxis.KeyEnter},
	"a-enter":   {vaxis.ModAlt, vaxis.KeyEnter},
	"up":        {vaxis.ModifierMask(0), vaxis.KeyUp},
	"c-up":      {vaxis.ModCtrl, vaxis.KeyUp},
	"a-up":      {vaxis.ModAlt, vaxis.KeyUp},
	"down":      {vaxis.ModifierMask(0), vaxis.KeyDown},
	"c-down":    {vaxis.ModCtrl, vaxis.KeyDown},
	"a-down":    {vaxis.ModAlt, vaxis.KeyDown},
	"right":     {vaxis.ModifierMask(0), vaxis.KeyRight},
	"c-right":   {vaxis.ModCtrl, vaxis.KeyRight},
	"a-right":   {vaxis.ModAlt, vaxis.KeyRight},
	"left":      {vaxis.ModifierMask(0), vaxis.KeyLeft},
	"c-left":    {vaxis.ModCtrl, vaxis.KeyLeft},
	"a-left":    {vaxis.ModAlt, vaxis.KeyLeft},
	"upleft":    {vaxis.ModifierMask(0), vaxis.KeyUpLeft},
	"upright":   {vaxis.ModifierMask(0), vaxis.KeyUpRight},
	"downleft":  {vaxis.ModifierMask(0), vaxis.KeyDownLeft},
	"downright": {vaxis.ModifierMask(0), vaxis.KeyDownRight},
	"center":    {vaxis.ModifierMask(0), vaxis.KeyCenter},
	"pgup":      {vaxis.ModifierMask(0), vaxis.KeyPgUp},
	"c-pgup":    {vaxis.ModCtrl, vaxis.KeyPgUp},
	"a-pgup":    {vaxis.ModAlt, vaxis.KeyPgUp},
	"pgdn":      {vaxis.ModifierMask(0), vaxis.KeyPgDown},
	"c-pgdn":    {vaxis.ModCtrl, vaxis.KeyPgDown},
	"a-pgdn":    {vaxis.ModAlt, vaxis.KeyPgDown},
	"home":      {vaxis.ModifierMask(0), vaxis.KeyHome},
	"end":       {vaxis.ModifierMask(0), vaxis.KeyEnd},
	"insert":    {vaxis.ModifierMask(0), vaxis.KeyInsert},
	"delete":    {vaxis.ModifierMask(0), vaxis.KeyDelete},
	"c-delete":  {vaxis.ModCtrl, vaxis.KeyDelete},
	"a-delete":  {vaxis.ModAlt, vaxis.KeyDelete},
	"backspace": {vaxis.ModifierMask(0), vaxis.KeyBackspace},
	// "help":      {vaxis.ModifierMask(0), vaxis.KeyHelp},
	"exit":    {vaxis.ModifierMask(0), vaxis.KeyExit},
	"clear":   {vaxis.ModifierMask(0), vaxis.KeyClear},
	"cancel":  {vaxis.ModifierMask(0), vaxis.KeyCancel},
	"print":   {vaxis.ModifierMask(0), vaxis.KeyPrint},
	"pause":   {vaxis.ModifierMask(0), vaxis.KeyPause},
	"backtab": {vaxis.ModShift, vaxis.KeyTab},
	"f1":      {vaxis.ModifierMask(0), vaxis.KeyF01},
	"f2":      {vaxis.ModifierMask(0), vaxis.KeyF02},
	"f3":      {vaxis.ModifierMask(0), vaxis.KeyF03},
	"f4":      {vaxis.ModifierMask(0), vaxis.KeyF04},
	"f5":      {vaxis.ModifierMask(0), vaxis.KeyF05},
	"f6":      {vaxis.ModifierMask(0), vaxis.KeyF06},
	"f7":      {vaxis.ModifierMask(0), vaxis.KeyF07},
	"f8":      {vaxis.ModifierMask(0), vaxis.KeyF08},
	"f9":      {vaxis.ModifierMask(0), vaxis.KeyF09},
	"f10":     {vaxis.ModifierMask(0), vaxis.KeyF10},
	"f11":     {vaxis.ModifierMask(0), vaxis.KeyF11},
	"f12":     {vaxis.ModifierMask(0), vaxis.KeyF12},
	"f13":     {vaxis.ModifierMask(0), vaxis.KeyF13},
	"f14":     {vaxis.ModifierMask(0), vaxis.KeyF14},
	"f15":     {vaxis.ModifierMask(0), vaxis.KeyF15},
	"f16":     {vaxis.ModifierMask(0), vaxis.KeyF16},
	"f17":     {vaxis.ModifierMask(0), vaxis.KeyF17},
	"f18":     {vaxis.ModifierMask(0), vaxis.KeyF18},
	"f19":     {vaxis.ModifierMask(0), vaxis.KeyF19},
	"f20":     {vaxis.ModifierMask(0), vaxis.KeyF20},
	"f21":     {vaxis.ModifierMask(0), vaxis.KeyF21},
	"f22":     {vaxis.ModifierMask(0), vaxis.KeyF22},
	"f23":     {vaxis.ModifierMask(0), vaxis.KeyF23},
	"f24":     {vaxis.ModifierMask(0), vaxis.KeyF24},
	"f25":     {vaxis.ModifierMask(0), vaxis.KeyF25},
	"f26":     {vaxis.ModifierMask(0), vaxis.KeyF26},
	"f27":     {vaxis.ModifierMask(0), vaxis.KeyF27},
	"f28":     {vaxis.ModifierMask(0), vaxis.KeyF28},
	"f29":     {vaxis.ModifierMask(0), vaxis.KeyF29},
	"f30":     {vaxis.ModifierMask(0), vaxis.KeyF30},
	"f31":     {vaxis.ModifierMask(0), vaxis.KeyF31},
	"f32":     {vaxis.ModifierMask(0), vaxis.KeyF32},
	"f33":     {vaxis.ModifierMask(0), vaxis.KeyF33},
	"f34":     {vaxis.ModifierMask(0), vaxis.KeyF34},
	"f35":     {vaxis.ModifierMask(0), vaxis.KeyF35},
	"f36":     {vaxis.ModifierMask(0), vaxis.KeyF36},
	"f37":     {vaxis.ModifierMask(0), vaxis.KeyF37},
	"f38":     {vaxis.ModifierMask(0), vaxis.KeyF38},
	"f39":     {vaxis.ModifierMask(0), vaxis.KeyF39},
	"f40":     {vaxis.ModifierMask(0), vaxis.KeyF40},
	"f41":     {vaxis.ModifierMask(0), vaxis.KeyF41},
	"f42":     {vaxis.ModifierMask(0), vaxis.KeyF42},
	"f43":     {vaxis.ModifierMask(0), vaxis.KeyF43},
	"f44":     {vaxis.ModifierMask(0), vaxis.KeyF44},
	"f45":     {vaxis.ModifierMask(0), vaxis.KeyF45},
	"f46":     {vaxis.ModifierMask(0), vaxis.KeyF46},
	"f47":     {vaxis.ModifierMask(0), vaxis.KeyF47},
	"f48":     {vaxis.ModifierMask(0), vaxis.KeyF48},
	"f49":     {vaxis.ModifierMask(0), vaxis.KeyF49},
	"f50":     {vaxis.ModifierMask(0), vaxis.KeyF50},
	"f51":     {vaxis.ModifierMask(0), vaxis.KeyF51},
	"f52":     {vaxis.ModifierMask(0), vaxis.KeyF52},
	"f53":     {vaxis.ModifierMask(0), vaxis.KeyF53},
	"f54":     {vaxis.ModifierMask(0), vaxis.KeyF54},
	"f55":     {vaxis.ModifierMask(0), vaxis.KeyF55},
	"f56":     {vaxis.ModifierMask(0), vaxis.KeyF56},
	"f57":     {vaxis.ModifierMask(0), vaxis.KeyF57},
	"f58":     {vaxis.ModifierMask(0), vaxis.KeyF58},
	"f59":     {vaxis.ModifierMask(0), vaxis.KeyF59},
	"f60":     {vaxis.ModifierMask(0), vaxis.KeyF60},
	"f61":     {vaxis.ModifierMask(0), vaxis.KeyF61},
	"f62":     {vaxis.ModifierMask(0), vaxis.KeyF62},
	"f63":     {vaxis.ModifierMask(0), vaxis.KeyF63},
	"c-space": {vaxis.ModCtrl, ' '},
	"c-a":     {vaxis.ModCtrl, 'a'},
	"c-b":     {vaxis.ModCtrl, 'b'},
	"c-c":     {vaxis.ModCtrl, 'c'},
	"c-d":     {vaxis.ModCtrl, 'd'},
	"c-e":     {vaxis.ModCtrl, 'e'},
	"c-f":     {vaxis.ModCtrl, 'f'},
	"c-g":     {vaxis.ModCtrl, 'g'},
	"c-h":     {vaxis.ModCtrl, 'h'},
	"c-i":     {vaxis.ModCtrl, 'i'},
	"c-j":     {vaxis.ModCtrl, 'j'},
	"c-k":     {vaxis.ModCtrl, 'k'},
	"c-l":     {vaxis.ModCtrl, 'l'},
	"c-m":     {vaxis.ModCtrl, 'm'},
	"c-n":     {vaxis.ModCtrl, 'n'},
	"c-o":     {vaxis.ModCtrl, 'o'},
	"c-p":     {vaxis.ModCtrl, 'p'},
	"c-q":     {vaxis.ModCtrl, 'q'},
	"c-r":     {vaxis.ModCtrl, 'r'},
	"c-s":     {vaxis.ModCtrl, 's'},
	"c-t":     {vaxis.ModCtrl, 't'},
	"c-u":     {vaxis.ModCtrl, 'u'},
	"c-v":     {vaxis.ModCtrl, 'v'},
	"c-w":     {vaxis.ModCtrl, 'w'},
	"c-x":     {vaxis.ModCtrl, 'x'},
	"c-y":     {vaxis.ModCtrl, 'y'},
	"c-z":     {vaxis.ModCtrl, 'z'},
	"c-]":     {vaxis.ModCtrl, ']'},
	"c-\\":    {vaxis.ModCtrl, '\\'},
	"c-[":     {vaxis.ModCtrl, '['},
	"c-^":     {vaxis.ModCtrl, '^'},
	"c-_":     {vaxis.ModCtrl, '_'},
	"a-space": {vaxis.ModAlt, ' '},
	"a-0":     {vaxis.ModAlt, '0'},
	"a-1":     {vaxis.ModAlt, '1'},
	"a-2":     {vaxis.ModAlt, '2'},
	"a-3":     {vaxis.ModAlt, '3'},
	"a-4":     {vaxis.ModAlt, '4'},
	"a-5":     {vaxis.ModAlt, '5'},
	"a-6":     {vaxis.ModAlt, '6'},
	"a-7":     {vaxis.ModAlt, '7'},
	"a-8":     {vaxis.ModAlt, '8'},
	"a-9":     {vaxis.ModAlt, '9'},
	"a-a":     {vaxis.ModAlt, 'a'},
	"a-b":     {vaxis.ModAlt, 'b'},
	"a-c":     {vaxis.ModAlt, 'c'},
	"a-d":     {vaxis.ModAlt, 'd'},
	"a-e":     {vaxis.ModAlt, 'e'},
	"a-f":     {vaxis.ModAlt, 'f'},
	"a-g":     {vaxis.ModAlt, 'g'},
	"a-h":     {vaxis.ModAlt, 'h'},
	"a-i":     {vaxis.ModAlt, 'i'},
	"a-j":     {vaxis.ModAlt, 'j'},
	"a-k":     {vaxis.ModAlt, 'k'},
	"a-l":     {vaxis.ModAlt, 'l'},
	"a-m":     {vaxis.ModAlt, 'm'},
	"a-n":     {vaxis.ModAlt, 'n'},
	"a-o":     {vaxis.ModAlt, 'o'},
	"a-p":     {vaxis.ModAlt, 'p'},
	"a-q":     {vaxis.ModAlt, 'q'},
	"a-r":     {vaxis.ModAlt, 'r'},
	"a-s":     {vaxis.ModAlt, 's'},
	"a-t":     {vaxis.ModAlt, 't'},
	"a-u":     {vaxis.ModAlt, 'u'},
	"a-v":     {vaxis.ModAlt, 'v'},
	"a-w":     {vaxis.ModAlt, 'w'},
	"a-x":     {vaxis.ModAlt, 'x'},
	"a-y":     {vaxis.ModAlt, 'y'},
	"a-z":     {vaxis.ModAlt, 'z'},
	"a-]":     {vaxis.ModAlt, ']'},
	"a-\\":    {vaxis.ModAlt, '\\'},
	"a-[":     {vaxis.ModAlt, '['},
	"a-^":     {vaxis.ModAlt, '^'},
	"a-_":     {vaxis.ModAlt, '_'},
	"nul":     {vaxis.ModCtrl, ' '},
	"soh":     {vaxis.ModCtrl, 'a'},
	"stx":     {vaxis.ModCtrl, 'b'},
	"etx":     {vaxis.ModCtrl, 'c'},
	"eot":     {vaxis.ModCtrl, 'd'},
	"enq":     {vaxis.ModCtrl, 'e'},
	"ack":     {vaxis.ModCtrl, 'f'},
	"bel":     {vaxis.ModCtrl, 'g'},
	"bs":      {vaxis.ModCtrl, 'h'},
	"tab":     {vaxis.ModifierMask(0), vaxis.KeyTab},
	"lf":      {vaxis.ModCtrl, 'j'},
	"vt":      {vaxis.ModCtrl, 'k'},
	"ff":      {vaxis.ModCtrl, 'l'},
	"cr":      {vaxis.ModifierMask(0), vaxis.KeyEnter},
	"so":      {vaxis.ModCtrl, 'n'},
	"si":      {vaxis.ModCtrl, 'o'},
	"dle":     {vaxis.ModCtrl, 'p'},
	"dc1":     {vaxis.ModCtrl, 'q'},
	"dc2":     {vaxis.ModCtrl, 'r'},
	"dc3":     {vaxis.ModCtrl, 's'},
	"dc4":     {vaxis.ModCtrl, 't'},
	"nak":     {vaxis.ModCtrl, 'u'},
	"syn":     {vaxis.ModCtrl, 'v'},
	"etb":     {vaxis.ModCtrl, 'w'},
	"can":     {vaxis.ModCtrl, 'x'},
	"em":      {vaxis.ModCtrl, 'y'},
	"sub":     {vaxis.ModCtrl, 'z'},
	"esc":     {vaxis.ModifierMask(0), vaxis.KeyEsc},
	"fs":      {vaxis.ModCtrl, '\\'},
	"gs":      {vaxis.ModCtrl, ']'},
	"rs":      {vaxis.ModCtrl, '^'},
	"us":      {vaxis.ModCtrl, '_'},
	"del":     {vaxis.ModifierMask(0), vaxis.KeyDelete},
}

func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) {
@@ -746,9 +737,8 @@ func ParseKeyStrokes(keystrokes string) ([]KeyStroke, error) {
			fallthrough
		default:
			strokes = append(strokes, KeyStroke{
				Modifiers: tcell.ModNone,
				Key:       tcell.KeyRune,
				Rune:      tok,
				Modifiers: vaxis.ModifierMask(0),
				Key:       tok,
			})
		}
	}
diff --git a/config/binds_test.go b/config/binds_test.go
index dab3b9f14290..c5f573ee44ea 100644
--- a/config/binds_test.go
+++ b/config/binds_test.go
@@ -4,7 +4,7 @@ import (
	"fmt"
	"testing"

	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/stretchr/testify/assert"
)

@@ -32,26 +32,26 @@ func TestGetBinding(t *testing.T) {
	}

	test([]KeyStroke{
		{tcell.ModNone, tcell.KeyRune, 'a'},
		{vaxis.ModifierMask(0), 'a'},
	}, BINDING_INCOMPLETE, "")
	test([]KeyStroke{
		{tcell.ModNone, tcell.KeyRune, 'a'},
		{tcell.ModNone, tcell.KeyRune, 'b'},
		{tcell.ModNone, tcell.KeyRune, 'c'},
		{vaxis.ModifierMask(0), 'a'},
		{vaxis.ModifierMask(0), 'b'},
		{vaxis.ModifierMask(0), 'c'},
	}, BINDING_FOUND, ":abc")
	test([]KeyStroke{
		{tcell.ModNone, tcell.KeyRune, 'c'},
		{tcell.ModNone, tcell.KeyRune, 'b'},
		{tcell.ModNone, tcell.KeyRune, 'a'},
		{vaxis.ModifierMask(0), 'c'},
		{vaxis.ModifierMask(0), 'b'},
		{vaxis.ModifierMask(0), 'a'},
	}, BINDING_FOUND, ":cba")
	test([]KeyStroke{
		{tcell.ModNone, tcell.KeyRune, 'f'},
		{tcell.ModNone, tcell.KeyRune, 'o'},
		{vaxis.ModifierMask(0), 'f'},
		{vaxis.ModifierMask(0), 'o'},
	}, BINDING_INCOMPLETE, "")
	test([]KeyStroke{
		{tcell.ModNone, tcell.KeyRune, '4'},
		{tcell.ModNone, tcell.KeyRune, '0'},
		{tcell.ModNone, tcell.KeyRune, '4'},
		{vaxis.ModifierMask(0), '4'},
		{vaxis.ModifierMask(0), '0'},
		{vaxis.ModifierMask(0), '4'},
	}, BINDING_NOT_FOUND, "")

	add("<C-a>", "c-a")
@@ -59,18 +59,18 @@ func TestGetBinding(t *testing.T) {
	add("<C-PgUp>", ":prev")
	add("<C-Enter>", ":open")
	test([]KeyStroke{
		{tcell.ModCtrl, tcell.KeyCtrlA, 0},
		{vaxis.ModCtrl, 'a'},
	}, BINDING_FOUND, "c-a")
	test([]KeyStroke{
		{tcell.ModCtrl, tcell.KeyDown, 0},
		{vaxis.ModCtrl, vaxis.KeyDown},
	}, BINDING_FOUND, ":next")
	test([]KeyStroke{
		{tcell.ModCtrl, tcell.KeyPgUp, 0},
		{vaxis.ModCtrl, vaxis.KeyPgUp},
	}, BINDING_FOUND, ":prev")
	test([]KeyStroke{
		{tcell.ModCtrl, tcell.KeyPgDn, 0},
		{vaxis.ModCtrl, vaxis.KeyPgDown},
	}, BINDING_NOT_FOUND, "")
	test([]KeyStroke{
		{tcell.ModCtrl, tcell.KeyEnter, 0},
		{vaxis.ModCtrl, vaxis.KeyEnter},
	}, BINDING_FOUND, ":open")
}
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index 107d354c1980..d52ee07a687c 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -336,50 +336,52 @@ func (ti *TextInput) OnFocusLost(onFocusLost func(ti *TextInput)) {
func (ti *TextInput) Event(event vaxis.Event) bool {
	ti.Lock()
	defer ti.Unlock()
	if event, ok := event.(*tcell.EventKey); ok {
	if key, ok := event.(vaxis.Key); ok {
		c := ti.completeKey
		if c != nil && c.Key == event.Key() && c.Modifiers == event.Modifiers() {
		if c != nil && key.Matches(c.Key, c.Modifiers) {
			ti.showCompletions(true)
			return true
		}

		ti.invalidateCompletions()

		switch event.Key() {
		case tcell.KeyBackspace, tcell.KeyBackspace2:
		switch {
		case key.Matches(vaxis.KeyBackspace):
			ti.backspace()
		case tcell.KeyCtrlD, tcell.KeyDelete:
		case key.Matches('d', vaxis.ModCtrl), key.Matches(vaxis.KeyDelete):
			ti.deleteChar()
		case tcell.KeyCtrlB, tcell.KeyLeft:
		case key.Matches('b', vaxis.ModCtrl), key.Matches(vaxis.KeyLeft):
			if ti.index > 0 {
				ti.index--
				ti.ensureScroll()
				ti.Invalidate()
			}
		case tcell.KeyCtrlF, tcell.KeyRight:
		case key.Matches('f', vaxis.ModCtrl), key.Matches(vaxis.KeyRight):
			if ti.index < len(ti.text) {
				ti.index++
				ti.ensureScroll()
				ti.Invalidate()
			}
		case tcell.KeyCtrlA, tcell.KeyHome:
		case key.Matches('a', vaxis.ModCtrl), key.Matches(vaxis.KeyHome):
			ti.index = 0
			ti.ensureScroll()
			ti.Invalidate()
		case tcell.KeyCtrlE, tcell.KeyEnd:
		case key.Matches('e', vaxis.ModCtrl), key.Matches(vaxis.KeyEnd):
			ti.index = len(ti.text)
			ti.ensureScroll()
			ti.Invalidate()
		case tcell.KeyCtrlK:
		case key.Matches('k', vaxis.ModCtrl):
			ti.deleteLineForward()
		case tcell.KeyCtrlW:
		case key.Matches('w', vaxis.ModCtrl):
			ti.deleteWord()
		case tcell.KeyCtrlU:
		case key.Matches('u', vaxis.ModCtrl):
			ti.deleteLineBackward()
		case tcell.KeyESC:
		case key.Matches(vaxis.KeyEsc):
			ti.Invalidate()
		case tcell.KeyRune:
			ti.insert(event.Rune())
		case key.Text != "":
			for _, ch := range key.Text {
				ti.insert(ch)
			}
		}
	}
	return true
@@ -482,9 +484,9 @@ func (c *completions) exec() {
}

func (c *completions) Event(e vaxis.Event) bool {
	if e, ok := e.(*tcell.EventKey); ok {
	if e, ok := e.(vaxis.Key); ok {
		k := c.ti.completeKey
		if k != nil && k.Key == e.Key() && k.Modifiers == e.Modifiers() {
		if k != nil && e.Matches(k.Key, k.Modifiers) {
			if len(c.ti.completions) == 1 {
				c.ti.completeIndex = 0
				c.exec()
@@ -498,14 +500,16 @@ func (c *completions) Event(e vaxis.Event) bool {
			return true
		}

		switch e.Key() {
		case tcell.KeyCtrlN, tcell.KeyDown:
		switch {
		case e.Matches('n', vaxis.ModCtrl), e.Matches(vaxis.KeyDown):
			c.next()
			return true
		case tcell.KeyBacktab, tcell.KeyCtrlP, tcell.KeyUp:
		case e.Matches(vaxis.KeyTab, vaxis.ModShift),
			e.Matches('p', vaxis.ModCtrl),
			e.Matches(vaxis.KeyUp):
			c.prev()
			return true
		case tcell.KeyEnter:
		case e.Matches(vaxis.KeyEnter):
			if c.index() >= 0 {
				c.exec()
				return true
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index f17ca4b383d5..84c34459f038 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -9,7 +9,6 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

// Use unbuffered channels (always blocking unless somebody can read
@@ -143,7 +142,6 @@ func HandleEvent(event vaxis.Event) {
	case vaxis.Redraw:
		Invalidate()
	default:
		event = tcell.TcellEvent(event)
		// if we have a popover, and it can handle the event, it does so
		if state.popover == nil || !state.popover.Event(event) {
			// otherwise, we send the event to the main content
-- 
2.43.0

[PATCH aerc v3 10/17] style: use vaxis style everywhere Export this patch

Replace all tcell.Style objects with vaxis.Style objects

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/authinfo.go        |   4 +-
 app/compose.go         |   2 +-
 app/dirlist.go         |  10 +-
 app/listbox.go         |   5 +-
 app/msglist.go         |   4 +-
 app/msgviewer.go       |   5 +-
 app/partswitcher.go    |   2 +-
 app/pgpinfo.go         |   4 +-
 app/spinner.go         |   5 +-
 app/status.go          |   8 +-
 config/style.go        | 233 ++++++++++++++++++++++++++++++++++-------
 config/ui.go           |  16 +--
 lib/parse/ansi.go      | 202 ++++++++++++++++++-----------------
 lib/parse/ansi_test.go |   8 +-
 lib/ui/context.go      |   8 +-
 lib/ui/table.go        |  18 ++--
 lib/ui/text.go         |   6 +-
 17 files changed, 354 insertions(+), 186 deletions(-)

diff --git a/app/authinfo.go b/app/authinfo.go
index 982edcb2de11..d201aa8d8edd 100644
--- a/app/authinfo.go
+++ b/app/authinfo.go
@@ -6,7 +6,7 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/auth"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/mattn/go-runewidth"
)

@@ -36,7 +36,7 @@ func (a *AuthInfo) Draw(ctx *ui.Context) {
		checkBounds := func(x int) bool {
			return x < ctx.Width()
		}
		setResult := func(result auth.Result) (string, tcell.Style) {
		setResult := func(result auth.Result) (string, vaxis.Style) {
			switch result {
			case auth.ResultNone:
				return "none", defaultStyle
diff --git a/app/compose.go b/app/compose.go
index 14341ef6f166..a73127c9c3fd 100644
--- a/app/compose.go
+++ b/app/compose.go
@@ -1547,7 +1547,7 @@ func (c *Composer) updateGrid() {
	borderChar := c.acct.UiConfig().BorderCharHorizontal
	grid.AddChild(heditors).At(0, 0)
	grid.AddChild(c.crypto).At(1, 0)
	grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(2, 0)
	grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(2, 0)
	if c.review != nil {
		grid.AddChild(c.review).At(3, 0)
	} else if c.editor != nil {
diff --git a/app/dirlist.go b/app/dirlist.go
index 53aeed9d15de..e92283740ed9 100644
--- a/app/dirlist.go
+++ b/app/dirlist.go
@@ -295,12 +295,12 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
func (dirlist *DirectoryList) renderDir(
	path string, conf *config.UIConfig, data models.TemplateData,
	selected bool, width int,
) (string, string, tcell.Style) {
) (string, string, vaxis.Style) {
	var left, right string
	var buf bytes.Buffer

	var styles []config.StyleObject
	var style tcell.Style
	var style vaxis.Style

	r, u, _ := dirlist.GetRUECount(path)
	if u > 0 {
@@ -353,7 +353,7 @@ func (dirlist *DirectoryList) renderDir(
		left = lbuf.Truncate(lwidth-1, '…')
	} else {
		for i := 0; i < (width - lwidth - rwidth - 1); i += 1 {
			lbuf.Write(' ', tcell.StyleDefault)
			lbuf.Write(' ', vaxis.Style{})
		}
		left = lbuf.String()
		right = rbuf.String()
@@ -363,8 +363,8 @@ func (dirlist *DirectoryList) renderDir(
}

func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
	gutterStyle := tcell.StyleDefault
	pillStyle := tcell.StyleDefault.Reverse(true)
	gutterStyle := vaxis.Style{}
	pillStyle := vaxis.Style{Attribute: vaxis.AttrReverse}

	// gutter
	ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
diff --git a/app/listbox.go b/app/listbox.go
index d891b24dff08..93d78828e6d7 100644
--- a/app/listbox.go
+++ b/app/listbox.go
@@ -9,7 +9,6 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
)

@@ -197,8 +196,8 @@ func (lb *ListBox) drawBox(ctx *ui.Context) {
}

func (lb *ListBox) drawScrollbar(ctx *ui.Context) {
	gutterStyle := tcell.StyleDefault
	pillStyle := tcell.StyleDefault.Reverse(true)
	gutterStyle := vaxis.Style{}
	pillStyle := vaxis.Style{Attribute: vaxis.AttrReverse}

	// gutter
	h := ctx.Height()
diff --git a/app/msglist.go b/app/msglist.go
index 9217423941ce..8106c3cfc694 100644
--- a/app/msglist.go
+++ b/app/msglist.go
@@ -107,8 +107,8 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
		return false
	}

	getRowStyle := func(t *ui.Table, r int) tcell.Style {
		var style tcell.Style
	getRowStyle := func(t *ui.Table, r int) vaxis.Style {
		var style vaxis.Style
		row := &t.Rows[r]
		params, _ := row.Priv.(messageRowParams)
		if params.uid == store.SelectedUid() {
diff --git a/app/msgviewer.go b/app/msgviewer.go
index 179deca410d2..a22d2c15df70 100644
--- a/app/msgviewer.go
+++ b/app/msgviewer.go
@@ -13,7 +13,6 @@ import (

	"github.com/danwakefield/fnmatch"
	"github.com/emersion/go-message/textproto"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -146,10 +145,10 @@ func NewMessageViewer(
	grid.AddChild(header).At(0, 0)
	if msg.MessageDetails() != nil || acct.UiConfig().IconUnencrypted != "" {
		grid.AddChild(NewPGPInfo(msg.MessageDetails(), acct.UiConfig())).At(1, 0)
		grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(2, 0)
		grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(2, 0)
		grid.AddChild(switcher).At(3, 0)
	} else {
		grid.AddChild(ui.NewFill(borderChar, tcell.VaxisStyle(borderStyle))).At(1, 0)
		grid.AddChild(ui.NewFill(borderChar, borderStyle)).At(1, 0)
		grid.AddChild(switcher).At(2, 0)
	}

diff --git a/app/partswitcher.go b/app/partswitcher.go
index 8552d32b4682..5dc996f783d5 100644
--- a/app/partswitcher.go
+++ b/app/partswitcher.go
@@ -103,7 +103,7 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
	ps.UpdateScroller(ps.height, n)
	ps.EnsureScroll(ps.selected)

	var styleSwitcher, styleFile, styleMime tcell.Style
	var styleSwitcher, styleFile, styleMime vaxis.Style

	scrollbarWidth := 0
	if ps.NeedScrollbar() {
diff --git a/app/pgpinfo.go b/app/pgpinfo.go
index d5bbc696a3c2..d2361a01afef 100644
--- a/app/pgpinfo.go
+++ b/app/pgpinfo.go
@@ -8,7 +8,7 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/models"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
)

type PGPInfo struct {
@@ -27,7 +27,7 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
	defaultStyle := p.uiConfig.GetStyle(config.STYLE_DEFAULT)

	var icon string
	var indicatorStyle, textstyle tcell.Style
	var indicatorStyle, textstyle vaxis.Style
	textstyle = defaultStyle
	var indicatorText, messageText string
	// TODO: Nicer prompt for TOFU, fetch from keyserver, etc
diff --git a/app/spinner.go b/app/spinner.go
index bcf8168ad350..2c675e3c5495 100644
--- a/app/spinner.go
+++ b/app/spinner.go
@@ -5,11 +5,10 @@ import (
	"sync/atomic"
	"time"

	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
)

type Spinner struct {
@@ -17,7 +16,7 @@ type Spinner struct {
	frames   []string
	interval time.Duration
	stop     chan struct{}
	style    tcell.Style
	style    vaxis.Style
}

func NewSpinner(uiConf *config.UIConfig) *Spinner {
diff --git a/app/status.go b/app/status.go
index dbedea7e8f06..952a3b98a1fd 100644
--- a/app/status.go
+++ b/app/status.go
@@ -5,7 +5,6 @@ import (
	"sync"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -13,6 +12,7 @@ import (
	"git.sr.ht/~rjarry/aerc/lib/templates"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
)

type StatusLine struct {
@@ -23,7 +23,7 @@ type StatusLine struct {
}

type StatusMessage struct {
	style   tcell.Style
	style   vaxis.Style
	message string
}

@@ -60,7 +60,7 @@ func (status *StatusLine) Draw(ctx *ui.Context) {
			config.Statusline.StatusColumns,
			config.Statusline.ColumnSeparator,
			nil,
			func(*ui.Table, int) tcell.Style { return style },
			func(*ui.Table, int) vaxis.Style { return style },
		)
		var buf bytes.Buffer
		cells := make([]string, len(table.Columns))
@@ -156,6 +156,6 @@ func (status *StatusLine) uiConfig() *config.UIConfig {
	return SelectedAccountUiConfig()
}

func (msg *StatusMessage) Color(style tcell.Style) {
func (msg *StatusMessage) Color(style vaxis.Style) {
	msg.style = style
}
diff --git a/config/style.go b/config/style.go
index 8a88dcfc9e1b..efe847220767 100644
--- a/config/style.go
+++ b/config/style.go
@@ -9,8 +9,8 @@ import (
	"strings"

	"git.sr.ht/~rjarry/aerc/lib/xdg"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/go-ini/ini"
)

@@ -116,8 +116,8 @@ var StyleNames = map[string]StyleObject{
}

type Style struct {
	Fg        tcell.Color
	Bg        tcell.Color
	Fg        vaxis.Color
	Bg        vaxis.Color
	Bold      bool
	Blink     bool
	Underline bool
@@ -129,16 +129,30 @@ type Style struct {
	re        *regexp.Regexp // only for msglist
}

func (s Style) Get() tcell.Style {
	return tcell.StyleDefault.
		Foreground(s.Fg).
		Background(s.Bg).
		Bold(s.Bold).
		Blink(s.Blink).
		Underline(s.Underline).
		Reverse(s.Reverse).
		Italic(s.Italic).
		Dim(s.Dim)
func (s Style) Get() vaxis.Style {
	vx := vaxis.Style{
		Foreground: s.Fg,
		Background: s.Bg,
	}
	if s.Bold {
		vx.Attribute |= vaxis.AttrBold
	}
	if s.Blink {
		vx.Attribute |= vaxis.AttrBlink
	}
	if s.Underline {
		vx.UnderlineStyle |= vaxis.UnderlineSingle
	}
	if s.Reverse {
		vx.Attribute |= vaxis.AttrReverse
	}
	if s.Italic {
		vx.Attribute |= vaxis.AttrItalic
	}
	if s.Dim {
		vx.Attribute |= vaxis.AttrDim
	}
	return vx
}

func (s *Style) Normal() {
@@ -151,8 +165,8 @@ func (s *Style) Normal() {
}

func (s *Style) Default() *Style {
	s.Fg = tcell.ColorDefault
	s.Bg = tcell.ColorDefault
	s.Fg = 0
	s.Bg = 0
	return s
}

@@ -176,15 +190,22 @@ func boolSwitch(val string, cur_val bool) (bool, error) {
	}
}

func extractColor(val string) tcell.Color {
func extractColor(val string) vaxis.Color {
	// Check if the string can be interpreted as a number, indicating a
	// reference to the color number. Otherwise retrieve the number based
	// on the name.
	if i, err := strconv.ParseUint(val, 10, 8); err == nil {
		return tcell.PaletteColor(int(i))
	} else {
		return tcell.GetColor(val)
		return vaxis.IndexColor(uint8(i))
	}
	if strings.HasPrefix(val, "#") {
		val = strings.TrimPrefix(val, "#")
		hex, err := strconv.ParseUint(val, 16, 32)
		if err != nil {
			return 0
		}
		return vaxis.HexColor(uint32(hex))
	}
	return colorNames[val]
}

func (s *Style) Set(attr, val string) error {
@@ -243,10 +264,10 @@ func (s *Style) Set(attr, val string) error {
func (s Style) composeWith(styles []*Style) Style {
	newStyle := s
	for _, st := range styles {
		if st.Fg != s.Fg && st.Fg != tcell.ColorDefault {
		if st.Fg != s.Fg && st.Fg != 0 {
			newStyle.Fg = st.Fg
		}
		if st.Bg != s.Bg && st.Bg != tcell.ColorDefault {
		if st.Bg != s.Bg && st.Bg != 0 {
			newStyle.Bg = st.Bg
		}
		if st.Bold != s.Bold {
@@ -297,13 +318,13 @@ func NewStyleSet() StyleSet {
			// *error.bold=true
			conf.base.Bold = true
			// error.fg=red
			conf.base.Fg = tcell.ColorRed
			conf.base.Fg = vaxis.IndexColor(1)
		case STYLE_WARNING:
			// warning.fg=yellow
			conf.base.Fg = tcell.ColorYellow
			conf.base.Fg = vaxis.IndexColor(3)
		case STYLE_SUCCESS:
			// success.fg=green
			conf.base.Fg = tcell.ColorGreen
			conf.base.Fg = vaxis.IndexColor(2)
		case STYLE_TITLE:
			// title.reverse=true
			conf.base.Reverse = true
@@ -315,28 +336,28 @@ func NewStyleSet() StyleSet {
			conf.base.Reverse = true
		case STYLE_STATUSLINE_ERROR:
			// *error.bold=true
			conf.base.Fg = tcell.ColorRed
			conf.base.Fg = vaxis.IndexColor(1)
			// statusline_error.fg=red
			conf.base.Bold = true
			// statusline_error.reverse=true
			conf.base.Reverse = true
		case STYLE_STATUSLINE_WARNING:
			// statusline_warning.fg=yellow
			conf.base.Fg = tcell.ColorYellow
			conf.base.Fg = vaxis.IndexColor(3)
			// statusline_warning.reverse=true
			conf.base.Reverse = true
		case STYLE_STATUSLINE_SUCCESS:
			conf.base.Fg = tcell.ColorGreen
			conf.base.Fg = vaxis.IndexColor(2)
			conf.base.Reverse = true
		case STYLE_MSGLIST_UNREAD:
			// msglist_unread.bold=true
			conf.base.Bold = true
		case STYLE_MSGLIST_DELETED:
			// msglist_deleted.fg=gray
			conf.base.Fg = tcell.ColorGray
			conf.base.Fg = vaxis.IndexColor(8)
		case STYLE_MSGLIST_RESULT:
			// msglist_result.fg=green
			conf.base.Fg = tcell.ColorGreen
			conf.base.Fg = vaxis.IndexColor(2)
		case STYLE_MSGLIST_PILL:
			// msglist_pill.reverse=true
			conf.base.Reverse = true
@@ -391,24 +412,24 @@ func (c *StyleConf) getStyle(h *mail.Header) *Style {
	return &c.base
}

func (ss StyleSet) Get(so StyleObject, h *mail.Header) tcell.Style {
func (ss StyleSet) Get(so StyleObject, h *mail.Header) vaxis.Style {
	return ss.objects[so].getStyle(h).Get()
}

func (ss StyleSet) Selected(so StyleObject, h *mail.Header) tcell.Style {
func (ss StyleSet) Selected(so StyleObject, h *mail.Header) vaxis.Style {
	return ss.selected[so].getStyle(h).Get()
}

func (ss StyleSet) UserStyle(name string) tcell.Style {
func (ss StyleSet) UserStyle(name string) vaxis.Style {
	if style, found := ss.user[name]; found {
		return style.Get()
	}
	return tcell.StyleDefault
	return vaxis.Style{}
}

func (ss StyleSet) Compose(
	so StyleObject, sos []StyleObject, h *mail.Header,
) tcell.Style {
) vaxis.Style {
	base := *ss.objects[so].getStyle(h)
	styles := make([]*Style, len(sos))
	for i, so := range sos {
@@ -420,7 +441,7 @@ func (ss StyleSet) Compose(

func (ss StyleSet) ComposeSelected(
	so StyleObject, sos []StyleObject, h *mail.Header,
) tcell.Style {
) vaxis.Style {
	base := *ss.selected[so].getStyle(h)
	styles := make([]*Style, len(sos))
	for i, so := range sos {
@@ -590,3 +611,145 @@ func fnmatchToRegex(pattern string) (*regexp.Regexp, error) {
	p = strings.ReplaceAll(p, `\*`, `.*`)
	return regexp.Compile(strings.ReplaceAll(p, `\?`, `.`))
}

var colorNames = map[string]vaxis.Color{
	"black":                vaxis.IndexColor(0),
	"maroon":               vaxis.IndexColor(1),
	"green":                vaxis.IndexColor(2),
	"olive":                vaxis.IndexColor(3),
	"navy":                 vaxis.IndexColor(4),
	"purple":               vaxis.IndexColor(5),
	"teal":                 vaxis.IndexColor(6),
	"silver":               vaxis.IndexColor(7),
	"gray":                 vaxis.IndexColor(8),
	"red":                  vaxis.IndexColor(9),
	"lime":                 vaxis.IndexColor(10),
	"yellow":               vaxis.IndexColor(11),
	"blue":                 vaxis.IndexColor(12),
	"fuchsia":              vaxis.IndexColor(13),
	"aqua":                 vaxis.IndexColor(14),
	"white":                vaxis.IndexColor(15),
	"aliceblue":            vaxis.HexColor(0xF0F8FF),
	"antiquewhite":         vaxis.HexColor(0xFAEBD7),
	"aquamarine":           vaxis.HexColor(0x7FFFD4),
	"azure":                vaxis.HexColor(0xF0FFFF),
	"beige":                vaxis.HexColor(0xF5F5DC),
	"bisque":               vaxis.HexColor(0xFFE4C4),
	"blanchedalmond":       vaxis.HexColor(0xFFEBCD),
	"blueviolet":           vaxis.HexColor(0x8A2BE2),
	"brown":                vaxis.HexColor(0xA52A2A),
	"burlywood":            vaxis.HexColor(0xDEB887),
	"cadetblue":            vaxis.HexColor(0x5F9EA0),
	"chartreuse":           vaxis.HexColor(0x7FFF00),
	"chocolate":            vaxis.HexColor(0xD2691E),
	"coral":                vaxis.HexColor(0xFF7F50),
	"cornflowerblue":       vaxis.HexColor(0x6495ED),
	"cornsilk":             vaxis.HexColor(0xFFF8DC),
	"crimson":              vaxis.HexColor(0xDC143C),
	"darkblue":             vaxis.HexColor(0x00008B),
	"darkcyan":             vaxis.HexColor(0x008B8B),
	"darkgoldenrod":        vaxis.HexColor(0xB8860B),
	"darkgray":             vaxis.HexColor(0xA9A9A9),
	"darkgreen":            vaxis.HexColor(0x006400),
	"darkkhaki":            vaxis.HexColor(0xBDB76B),
	"darkmagenta":          vaxis.HexColor(0x8B008B),
	"darkolivegreen":       vaxis.HexColor(0x556B2F),
	"darkorange":           vaxis.HexColor(0xFF8C00),
	"darkorchid":           vaxis.HexColor(0x9932CC),
	"darkred":              vaxis.HexColor(0x8B0000),
	"darksalmon":           vaxis.HexColor(0xE9967A),
	"darkseagreen":         vaxis.HexColor(0x8FBC8F),
	"darkslateblue":        vaxis.HexColor(0x483D8B),
	"darkslategray":        vaxis.HexColor(0x2F4F4F),
	"darkturquoise":        vaxis.HexColor(0x00CED1),
	"darkviolet":           vaxis.HexColor(0x9400D3),
	"deeppink":             vaxis.HexColor(0xFF1493),
	"deepskyblue":          vaxis.HexColor(0x00BFFF),
	"dimgray":              vaxis.HexColor(0x696969),
	"dodgerblue":           vaxis.HexColor(0x1E90FF),
	"firebrick":            vaxis.HexColor(0xB22222),
	"floralwhite":          vaxis.HexColor(0xFFFAF0),
	"forestgreen":          vaxis.HexColor(0x228B22),
	"gainsboro":            vaxis.HexColor(0xDCDCDC),
	"ghostwhite":           vaxis.HexColor(0xF8F8FF),
	"gold":                 vaxis.HexColor(0xFFD700),
	"goldenrod":            vaxis.HexColor(0xDAA520),
	"greenyellow":          vaxis.HexColor(0xADFF2F),
	"honeydew":             vaxis.HexColor(0xF0FFF0),
	"hotpink":              vaxis.HexColor(0xFF69B4),
	"indianred":            vaxis.HexColor(0xCD5C5C),
	"indigo":               vaxis.HexColor(0x4B0082),
	"ivory":                vaxis.HexColor(0xFFFFF0),
	"khaki":                vaxis.HexColor(0xF0E68C),
	"lavender":             vaxis.HexColor(0xE6E6FA),
	"lavenderblush":        vaxis.HexColor(0xFFF0F5),
	"lawngreen":            vaxis.HexColor(0x7CFC00),
	"lemonchiffon":         vaxis.HexColor(0xFFFACD),
	"lightblue":            vaxis.HexColor(0xADD8E6),
	"lightcoral":           vaxis.HexColor(0xF08080),
	"lightcyan":            vaxis.HexColor(0xE0FFFF),
	"lightgoldenrodyellow": vaxis.HexColor(0xFAFAD2),
	"lightgray":            vaxis.HexColor(0xD3D3D3),
	"lightgreen":           vaxis.HexColor(0x90EE90),
	"lightpink":            vaxis.HexColor(0xFFB6C1),
	"lightsalmon":          vaxis.HexColor(0xFFA07A),
	"lightseagreen":        vaxis.HexColor(0x20B2AA),
	"lightskyblue":         vaxis.HexColor(0x87CEFA),
	"lightslategray":       vaxis.HexColor(0x778899),
	"lightsteelblue":       vaxis.HexColor(0xB0C4DE),
	"lightyellow":          vaxis.HexColor(0xFFFFE0),
	"limegreen":            vaxis.HexColor(0x32CD32),
	"linen":                vaxis.HexColor(0xFAF0E6),
	"mediumaquamarine":     vaxis.HexColor(0x66CDAA),
	"mediumblue":           vaxis.HexColor(0x0000CD),
	"mediumorchid":         vaxis.HexColor(0xBA55D3),
	"mediumpurple":         vaxis.HexColor(0x9370DB),
	"mediumseagreen":       vaxis.HexColor(0x3CB371),
	"mediumslateblue":      vaxis.HexColor(0x7B68EE),
	"mediumspringgreen":    vaxis.HexColor(0x00FA9A),
	"mediumturquoise":      vaxis.HexColor(0x48D1CC),
	"mediumvioletred":      vaxis.HexColor(0xC71585),
	"midnightblue":         vaxis.HexColor(0x191970),
	"mintcream":            vaxis.HexColor(0xF5FFFA),
	"mistyrose":            vaxis.HexColor(0xFFE4E1),
	"moccasin":             vaxis.HexColor(0xFFE4B5),
	"navajowhite":          vaxis.HexColor(0xFFDEAD),
	"oldlace":              vaxis.HexColor(0xFDF5E6),
	"olivedrab":            vaxis.HexColor(0x6B8E23),
	"orange":               vaxis.HexColor(0xFFA500),
	"orangered":            vaxis.HexColor(0xFF4500),
	"orchid":               vaxis.HexColor(0xDA70D6),
	"palegoldenrod":        vaxis.HexColor(0xEEE8AA),
	"palegreen":            vaxis.HexColor(0x98FB98),
	"paleturquoise":        vaxis.HexColor(0xAFEEEE),
	"palevioletred":        vaxis.HexColor(0xDB7093),
	"papayawhip":           vaxis.HexColor(0xFFEFD5),
	"peachpuff":            vaxis.HexColor(0xFFDAB9),
	"peru":                 vaxis.HexColor(0xCD853F),
	"pink":                 vaxis.HexColor(0xFFC0CB),
	"plum":                 vaxis.HexColor(0xDDA0DD),
	"powderblue":           vaxis.HexColor(0xB0E0E6),
	"rebeccapurple":        vaxis.HexColor(0x663399),
	"rosybrown":            vaxis.HexColor(0xBC8F8F),
	"royalblue":            vaxis.HexColor(0x4169E1),
	"saddlebrown":          vaxis.HexColor(0x8B4513),
	"salmon":               vaxis.HexColor(0xFA8072),
	"sandybrown":           vaxis.HexColor(0xF4A460),
	"seagreen":             vaxis.HexColor(0x2E8B57),
	"seashell":             vaxis.HexColor(0xFFF5EE),
	"sienna":               vaxis.HexColor(0xA0522D),
	"skyblue":              vaxis.HexColor(0x87CEEB),
	"slateblue":            vaxis.HexColor(0x6A5ACD),
	"slategray":            vaxis.HexColor(0x708090),
	"snow":                 vaxis.HexColor(0xFFFAFA),
	"springgreen":          vaxis.HexColor(0x00FF7F),
	"steelblue":            vaxis.HexColor(0x4682B4),
	"tan":                  vaxis.HexColor(0xD2B48C),
	"thistle":              vaxis.HexColor(0xD8BFD8),
	"tomato":               vaxis.HexColor(0xFF6347),
	"turquoise":            vaxis.HexColor(0x40E0D0),
	"violet":               vaxis.HexColor(0xEE82EE),
	"wheat":                vaxis.HexColor(0xF5DEB3),
	"whitesmoke":           vaxis.HexColor(0xF5F5F5),
	"yellowgreen":          vaxis.HexColor(0x9ACD32),
}
diff --git a/config/ui.go b/config/ui.go
index f339066e1160..f19e563609d3 100644
--- a/config/ui.go
+++ b/config/ui.go
@@ -13,8 +13,8 @@ import (

	"git.sr.ht/~rjarry/aerc/lib/templates"
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/go-ini/ini"
)

@@ -520,39 +520,39 @@ func (base *UIConfig) mergeContextual(
	return base
}

func (uiConfig *UIConfig) GetUserStyle(name string) tcell.Style {
func (uiConfig *UIConfig) GetUserStyle(name string) vaxis.Style {
	return uiConfig.style.UserStyle(name)
}

func (uiConfig *UIConfig) GetStyle(so StyleObject) tcell.Style {
func (uiConfig *UIConfig) GetStyle(so StyleObject) vaxis.Style {
	return uiConfig.style.Get(so, nil)
}

func (uiConfig *UIConfig) GetStyleSelected(so StyleObject) tcell.Style {
func (uiConfig *UIConfig) GetStyleSelected(so StyleObject) vaxis.Style {
	return uiConfig.style.Selected(so, nil)
}

func (uiConfig *UIConfig) GetComposedStyle(base StyleObject,
	styles []StyleObject,
) tcell.Style {
) vaxis.Style {
	return uiConfig.style.Compose(base, styles, nil)
}

func (uiConfig *UIConfig) GetComposedStyleSelected(
	base StyleObject, styles []StyleObject,
) tcell.Style {
) vaxis.Style {
	return uiConfig.style.ComposeSelected(base, styles, nil)
}

func (uiConfig *UIConfig) MsgComposedStyle(
	base StyleObject, styles []StyleObject, h *mail.Header,
) tcell.Style {
) vaxis.Style {
	return uiConfig.style.Compose(base, styles, h)
}

func (uiConfig *UIConfig) MsgComposedStyleSelected(
	base StyleObject, styles []StyleObject, h *mail.Header,
) tcell.Style {
) vaxis.Style {
	return uiConfig.style.ComposeSelected(base, styles, h)
}

diff --git a/lib/parse/ansi.go b/lib/parse/ansi.go
index 3a802a51f78c..7fc378344f76 100644
--- a/lib/parse/ansi.go
+++ b/lib/parse/ansi.go
@@ -12,7 +12,7 @@ import (
	"strings"

	"git.sr.ht/~rjarry/aerc/log"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/mattn/go-runewidth"
)

@@ -63,10 +63,10 @@ func StripAnsi(r io.Reader) io.Reader {
type StyledRune struct {
	Value rune
	Width int
	Style tcell.Style
	Style vaxis.Style
}

// RuneBuffer is a buffer of runes styled with tcell.Style objects
// RuneBuffer is a buffer of runes styled with vaxis.Style objects
type RuneBuffer struct {
	buf []*StyledRune
}
@@ -77,13 +77,13 @@ func (rb *RuneBuffer) Runes() []*StyledRune {
}

// Write writes a rune and it's associated style to the RuneBuffer
func (rb *RuneBuffer) Write(r rune, style tcell.Style) {
func (rb *RuneBuffer) Write(r rune, style vaxis.Style) {
	w := runewidth.RuneWidth(r)
	rb.buf = append(rb.buf, &StyledRune{r, w, style})
}

// Prepend inserts the rune at the beginning of the rune buffer
func (rb *RuneBuffer) PadLeft(width int, r rune, style tcell.Style) {
func (rb *RuneBuffer) PadLeft(width int, r rune, style vaxis.Style) {
	w := rb.Len()
	if w >= width {
		return
@@ -96,7 +96,7 @@ func (rb *RuneBuffer) PadLeft(width int, r rune, style tcell.Style) {
	}
}

func (rb *RuneBuffer) PadRight(width int, r rune, style tcell.Style) {
func (rb *RuneBuffer) PadRight(width int, r rune, style vaxis.Style) {
	w := rb.Len()
	if w >= width {
		return
@@ -119,7 +119,7 @@ func (rb *RuneBuffer) String() string {
func (rb *RuneBuffer) string(n int, left bool, char rune) string {
	var (
		s        = bytes.NewBuffer(nil)
		style    = tcell.StyleDefault
		style    = vaxis.Style{}
		hasStyle = false
		// w will track the length we have written, or would have
		// written in the case of left truncate
@@ -136,52 +136,55 @@ func (rb *RuneBuffer) string(n int, left bool, char rune) string {
			hasStyle = true
			style = r.Style
			s.WriteString(attrOff)
			fg, bg, attrs := style.Decompose()

			switch {
			case fg.IsRGB() && bg.IsRGB():
				fr, fg, fb := fg.RGB()
				br, bg, bb := bg.RGB()
				fmt.Fprintf(s, setfgbgrgb, fr, fg, fb, br, bg, bb)
			case fg.IsRGB():
				// RGB
				r, g, b := fg.RGB()
				fmt.Fprintf(s, setfgrgb, r, g, b)
			case bg.IsRGB():
				// RGB
				r, g, b := bg.RGB()
				fmt.Fprintf(s, setbgrgb, r, g, b)

				// Indexed
			case fg.Valid() && bg.Valid():
				fmt.Fprintf(s, setfgbg, fg&0xFF, bg&0xFF)
			case fg.Valid():
				fmt.Fprintf(s, setfg, fg&0xFF)
			case bg.Valid():
				fmt.Fprintf(s, setbg, bg&0xFF)
			// fg, bg, attrs := style.Decompose()
			fg := style.Foreground.Params()
			switch len(fg) {
			case 0:
				// default
			case 1:
				// indexed
				fmt.Fprintf(s, setfg, fg[0])
			case 3:
				// rgb
				fmt.Fprintf(s, setfgrgb, fg[0], fg[1], fg[2])
			}

			if attrs&tcell.AttrBold != 0 {
			bg := style.Background.Params()
			switch len(bg) {
			case 0:
				// default
			case 1:
				// indexed
				fmt.Fprintf(s, setbg, bg[0])
			case 3:
				// rgb
				fmt.Fprintf(s, setbgrgb, bg[0], bg[1], bg[2])
			}

			attrs := style.Attribute

			if attrs&vaxis.AttrBold != 0 {
				s.WriteString(bold)
			}
			if attrs&tcell.AttrUnderline != 0 {
				s.WriteString(underline)
			}
			if attrs&tcell.AttrReverse != 0 {
			if attrs&vaxis.AttrReverse != 0 {
				s.WriteString(reverse)
			}
			if attrs&tcell.AttrBlink != 0 {
			if attrs&vaxis.AttrBlink != 0 {
				s.WriteString(blink)
			}
			if attrs&tcell.AttrDim != 0 {
			if attrs&vaxis.AttrDim != 0 {
				s.WriteString(dim)
			}
			if attrs&tcell.AttrItalic != 0 {
			if attrs&vaxis.AttrItalic != 0 {
				s.WriteString(italic)
			}
			if attrs&tcell.AttrStrikeThrough != 0 {
			if attrs&vaxis.AttrStrikethrough != 0 {
				s.WriteString(strikethrough)
			}

			if style.UnderlineStyle != vaxis.UnderlineOff {
				s.WriteString(underline)
			}
		}

		w += r.Width
@@ -230,9 +233,10 @@ func (rb *RuneBuffer) TruncateHead(n int, char rune) string {

// Applies a style to the buffer. Any currently applied styles will not be
// overwritten
func (rb *RuneBuffer) ApplyStyle(style tcell.Style) {
func (rb *RuneBuffer) ApplyStyle(style vaxis.Style) {
	d := vaxis.Style{}
	for _, sr := range rb.buf {
		if sr.Style == tcell.StyleDefault {
		if sr.Style == d {
			sr.Style = style
		}
	}
@@ -240,26 +244,30 @@ func (rb *RuneBuffer) ApplyStyle(style tcell.Style) {

// ApplyAttrs applies the style, and if another style is present ORs the
// attributes
func (rb *RuneBuffer) ApplyAttrs(style tcell.Style) {
	fg, bg, attrs := style.Decompose()
func (rb *RuneBuffer) ApplyAttrs(style vaxis.Style) {
	for _, sr := range rb.buf {
		srFg, srBg, srAttrs := sr.Style.Decompose()
		if fg != tcell.ColorDefault {
			srFg = fg
		if style.Foreground != 0 {
			sr.Style.Foreground = style.Foreground
		}
		if bg != tcell.ColorDefault {
			srBg = bg
		if style.Background != 0 {
			sr.Style.Background = style.Background
		}
		sr.Style.Attribute |= style.Attribute
		if style.UnderlineColor != 0 {
			sr.Style.UnderlineColor = style.UnderlineColor
		}
		if style.UnderlineStyle != vaxis.UnderlineOff {
			sr.Style.UnderlineStyle = style.UnderlineStyle
		}
		sr.Style = sr.Style.Attributes(attrs | srAttrs).
			Foreground(srFg).Background(srBg)
	}
}

// Applies a style to a string. Any currently applied styles will not be overwritten
func ApplyStyle(style tcell.Style, str string) string {
func ApplyStyle(style vaxis.Style, str string) string {
	rb := ParseANSI(str)
	d := vaxis.Style{}
	for _, sr := range rb.buf {
		if sr.Style == tcell.StyleDefault {
		if sr.Style == d {
			sr.Style = style
		}
	}
@@ -270,7 +278,7 @@ func ApplyStyle(style tcell.Style, str string) string {
func ParseANSI(s string) *RuneBuffer {
	p := &parser{
		buf:      &RuneBuffer{},
		curStyle: tcell.StyleDefault,
		curStyle: vaxis.Style{},
	}
	rdr := strings.NewReader(s)

@@ -292,7 +300,7 @@ func ParseANSI(s string) *RuneBuffer {
// A parser parses a string into a RuneBuffer
type parser struct {
	buf      *RuneBuffer
	curStyle tcell.Style
	curStyle vaxis.Style
}

func (p *parser) handleSeq(rdr io.RuneReader) {
@@ -357,97 +365,97 @@ outer:
		param := params[i]
		switch param {
		case 0:
			p.curStyle = tcell.StyleDefault
			p.curStyle = vaxis.Style{}
		case 1:
			p.curStyle = p.curStyle.Bold(true)
			p.curStyle.Attribute |= vaxis.AttrBold
		case 2:
			p.curStyle = p.curStyle.Dim(true)
			p.curStyle.Attribute |= vaxis.AttrDim
		case 3:
			p.curStyle = p.curStyle.Italic(true)
			p.curStyle.Attribute |= vaxis.AttrItalic
		case 4:
			p.curStyle = p.curStyle.Underline(true)
			p.curStyle.UnderlineStyle = vaxis.UnderlineSingle
		case 5:
			p.curStyle = p.curStyle.Blink(true)
			p.curStyle.Attribute |= vaxis.AttrBlink
		case 6:
			// rapid blink, not supported by tcell. fallback to slow
			// rapid blink, not supported by vaxis. fallback to slow
			// blink
			p.curStyle = p.curStyle.Blink(true)
			p.curStyle.Attribute |= vaxis.AttrBlink
		case 7:
			p.curStyle = p.curStyle.Reverse(true)
			p.curStyle.Attribute |= vaxis.AttrReverse
		case 8:
			// Hidden. not supported by tcell
			// Hidden. not supported by vaxis
		case 9:
			p.curStyle = p.curStyle.StrikeThrough(true)
			p.curStyle.Attribute |= vaxis.AttrStrikethrough
		case 21:
			p.curStyle = p.curStyle.Bold(false)
			p.curStyle.Attribute &^= vaxis.AttrBold
		case 22:
			p.curStyle = p.curStyle.Dim(false)
			p.curStyle.Attribute &^= vaxis.AttrDim
		case 23:
			p.curStyle = p.curStyle.Italic(false)
			p.curStyle.Attribute &^= vaxis.AttrItalic
		case 24:
			p.curStyle = p.curStyle.Underline(false)
			p.curStyle.UnderlineStyle = vaxis.UnderlineOff
		case 25:
			p.curStyle = p.curStyle.Blink(false)
			p.curStyle.Attribute &^= vaxis.AttrBlink
		case 26:
			// rapid blink, not supported by tcell. fallback to slow
			// rapid blink, not supported by vaxis. fallback to slow
			// blink
			p.curStyle = p.curStyle.Blink(false)
			p.curStyle.Attribute &^= vaxis.AttrBlink
		case 27:
			p.curStyle = p.curStyle.Reverse(false)
			p.curStyle.Attribute &^= vaxis.AttrReverse
		case 28:
			// Hidden. unsupported by tcell
			// Hidden. unsupported by vaxis
		case 29:
			p.curStyle = p.curStyle.StrikeThrough(false)
			p.curStyle.Attribute &^= vaxis.AttrStrikethrough
		case 30, 31, 32, 33, 34, 35, 36, 37:
			p.curStyle = p.curStyle.Foreground(tcell.PaletteColor(param - 30))
			p.curStyle.Foreground = vaxis.IndexColor(uint8(param - 30))
		case 38:
			if i+2 < len(params) && params[i+1] == 5 {
				p.curStyle = p.curStyle.Foreground(tcell.PaletteColor(params[i+2]))
				p.curStyle.Foreground = vaxis.IndexColor(uint8(params[i+2]))
				i += 2
			}
			if i+4 < len(params) && params[i+1] == 2 {
				switch len(params) {
				case 6:
					r := int32(params[i+3])
					g := int32(params[i+4])
					b := int32(params[i+5])
					p.curStyle = p.curStyle.Foreground(tcell.NewRGBColor(r, g, b))
					r := uint8(params[i+3])
					g := uint8(params[i+4])
					b := uint8(params[i+5])
					p.curStyle.Foreground = vaxis.RGBColor(r, g, b)
					i += 5
				default:
					r := int32(params[i+2])
					g := int32(params[i+3])
					b := int32(params[i+4])
					p.curStyle = p.curStyle.Foreground(tcell.NewRGBColor(r, g, b))
					r := uint8(params[i+2])
					g := uint8(params[i+3])
					b := uint8(params[i+4])
					p.curStyle.Foreground = vaxis.RGBColor(r, g, b)
					i += 4
				}
			}
		case 40, 41, 42, 43, 44, 45, 46, 47:
			p.curStyle = p.curStyle.Background(tcell.PaletteColor(param - 40))
			p.curStyle.Background = vaxis.IndexColor(uint8(param - 40))
		case 48:
			if i+2 < len(params) && params[i+1] == 5 {
				p.curStyle = p.curStyle.Background(tcell.PaletteColor(params[i+2]))
				p.curStyle.Background = vaxis.IndexColor(uint8(params[i+2]))
				i += 2
			}
			if i+4 < len(params) && params[i+1] == 2 {
				switch len(params) {
				case 6:
					r := int32(params[i+3])
					g := int32(params[i+4])
					b := int32(params[i+5])
					p.curStyle = p.curStyle.Background(tcell.NewRGBColor(r, g, b))
					r := uint8(params[i+3])
					g := uint8(params[i+4])
					b := uint8(params[i+5])
					p.curStyle.Background = vaxis.RGBColor(r, g, b)
					i += 5
				default:
					r := int32(params[i+2])
					g := int32(params[i+3])
					b := int32(params[i+4])
					p.curStyle = p.curStyle.Background(tcell.NewRGBColor(r, g, b))
					r := uint8(params[i+2])
					g := uint8(params[i+3])
					b := uint8(params[i+4])
					p.curStyle.Background = vaxis.RGBColor(r, g, b)
					i += 4
				}
			}
		case 90, 91, 92, 93, 94, 95, 96, 97:
			p.curStyle = p.curStyle.Foreground(tcell.PaletteColor(param - 90 + 8))
			p.curStyle.Foreground = vaxis.IndexColor(uint8(param - 90 + 8))
		case 100, 101, 102, 103, 104, 105, 106, 107:
			p.curStyle = p.curStyle.Background(tcell.PaletteColor(param - 100 + 8))
			p.curStyle.Background = vaxis.IndexColor(uint8(param - 100 + 8))
		}
	}
}
diff --git a/lib/parse/ansi_test.go b/lib/parse/ansi_test.go
index 6aa95b185ded..5d8e8572fc2b 100644
--- a/lib/parse/ansi_test.go
+++ b/lib/parse/ansi_test.go
@@ -110,7 +110,7 @@ func TestParser(t *testing.T) {
		{
			name:           "8 bit color: foreground and background",
			input:          "\x1b[31;41mhello, world",
			expectedString: "\x1b[m\x1b[38;5;1;48;5;1mhello, world\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;1m\x1b[48;5;1mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
@@ -128,7 +128,7 @@ func TestParser(t *testing.T) {
		{
			name:           "16 bit color: foreground and background",
			input:          "\x1b[91;101mhello, world",
			expectedString: "\x1b[m\x1b[38;5;9;48;5;9mhello, world\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;9m\x1b[48;5;9mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
@@ -152,7 +152,7 @@ func TestParser(t *testing.T) {
		{
			name:           "256 color: foreground and background",
			input:          "\x1b[38;5;20;48;5;20mhello, world",
			expectedString: "\x1b[m\x1b[38;5;20;48;5;20mhello, world\x1b[m",
			expectedString: "\x1b[m\x1b[38;5;20m\x1b[48;5;20mhello, world\x1b[m",
			expectedLen:    12,
		},
		{
@@ -194,7 +194,7 @@ func TestParser(t *testing.T) {
		{
			name:           "true color: foreground and background",
			input:          "\x1b[38;2;200;200;200;48;2;0;0;0mhello, world",
			expectedString: "\x1b[m\x1b[38;2;200;200;200;48;2;0;0;0mhello, world\x1b[m",
			expectedString: "\x1b[m\x1b[38;2;200;200;200m\x1b[48;2;0;0;0mhello, world\x1b[m",
			expectedLen:    12,
		},
	}
diff --git a/lib/ui/context.go b/lib/ui/context.go
index 12621c8af97a..693611208a77 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -57,7 +57,7 @@ func (ctx *Context) SetCell(x, y int, ch rune, style vaxis.Style) {
	})
}

func (ctx *Context) Printf(x, y int, style tcell.Style,
func (ctx *Context) Printf(x, y int, style vaxis.Style,
	format string, a ...interface{},
) int {
	width, height := ctx.window.Size()
@@ -93,7 +93,7 @@ func (ctx *Context) Printf(x, y int, style tcell.Style,
					Grapheme: string(sr.Value),
					Width:    sr.Width,
				},
				Style: tcell.VaxisStyle(sr.Style),
				Style: sr.Style,
			})
			x += sr.Width
			if x == old_x+width {
@@ -107,14 +107,14 @@ func (ctx *Context) Printf(x, y int, style tcell.Style,
	return buf.Len()
}

func (ctx *Context) Fill(x, y, width, height int, rune rune, style tcell.Style) {
func (ctx *Context) Fill(x, y, width, height int, rune rune, style vaxis.Style) {
	win := ctx.window.New(x, y, width, height)
	win.Fill(vaxis.Cell{
		Character: vaxis.Character{
			Grapheme: string(rune),
			Width:    1,
		},
		Style: tcell.VaxisStyle(style),
		Style: style,
	})
}

diff --git a/lib/ui/table.go b/lib/ui/table.go
index 9cb96bd9ccea..ceb01c78c5cc 100644
--- a/lib/ui/table.go
+++ b/lib/ui/table.go
@@ -6,7 +6,7 @@ import (

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/parse"
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/mattn/go-runewidth"
)

@@ -18,7 +18,7 @@ type Table struct {
	// of table rows. If true is returned, the default routine is skipped.
	CustomDraw func(t *Table, row int, c *Context) bool
	// Optional callback that allows returning a custom style for the row.
	GetRowStyle func(t *Table, row int) tcell.Style
	GetRowStyle func(t *Table, row int) vaxis.Style

	// true if at least one column has WIDTH_FIT
	autoFitWidths bool
@@ -42,14 +42,14 @@ func NewTable(
	height int,
	columnDefs []*config.ColumnDef, separator string,
	customDraw func(*Table, int, *Context) bool,
	getRowStyle func(*Table, int) tcell.Style,
	getRowStyle func(*Table, int) vaxis.Style,
) Table {
	if customDraw == nil {
		customDraw = func(*Table, int, *Context) bool { return false }
	}
	if getRowStyle == nil {
		getRowStyle = func(*Table, int) tcell.Style {
			return tcell.StyleDefault
		getRowStyle = func(*Table, int) vaxis.Style {
			return vaxis.Style{}
		}
	}
	columns := make([]Column, len(columnDefs))
@@ -166,7 +166,7 @@ func (col *Column) alignCell(cell string) string {
	switch {
	case col.Def.Flags.Has(config.ALIGN_LEFT):
		if width < col.Width {
			buf.PadRight(col.Width, ' ', tcell.StyleDefault)
			buf.PadRight(col.Width, ' ', vaxis.Style{})
			cell = buf.String()
		} else if width > col.Width {
			cell = buf.Truncate(col.Width, '…')
@@ -174,15 +174,15 @@ func (col *Column) alignCell(cell string) string {
	case col.Def.Flags.Has(config.ALIGN_CENTER):
		if width < col.Width {
			pad := col.Width - width
			buf.PadLeft(col.Width-(pad/2), ' ', tcell.StyleDefault)
			buf.PadRight(col.Width, ' ', tcell.StyleDefault)
			buf.PadLeft(col.Width-(pad/2), ' ', vaxis.Style{})
			buf.PadRight(col.Width, ' ', vaxis.Style{})
			cell = buf.String()
		} else if width > col.Width {
			cell = buf.Truncate(col.Width, '…')
		}
	case col.Def.Flags.Has(config.ALIGN_RIGHT):
		if width < col.Width {
			buf.PadLeft(col.Width, ' ', tcell.StyleDefault)
			buf.PadLeft(col.Width, ' ', vaxis.Style{})
			cell = buf.String()
		} else if width > col.Width {
			cell = buf.TruncateHead(col.Width, '…')
diff --git a/lib/ui/text.go b/lib/ui/text.go
index def61a0a70e1..19a0b90028df 100644
--- a/lib/ui/text.go
+++ b/lib/ui/text.go
@@ -1,7 +1,7 @@
package ui

import (
	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/mattn/go-runewidth"
)

@@ -14,10 +14,10 @@ const (
type Text struct {
	text     string
	strategy uint
	style    tcell.Style
	style    vaxis.Style
}

func NewText(text string, style tcell.Style) *Text {
func NewText(text string, style vaxis.Style) *Text {
	return &Text{
		text:  text,
		style: style,
-- 
2.43.0

[PATCH aerc v3 11/17] terminal: replace tcell-term with vaxis terminal Export this patch

Replace tcell terminal with the vaxis terminal. The vaxis terminal is a
port of tcell term.

Changelog-changed: replaced tcell-term with vaxis terminal
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/terminal.go   | 54 ++++++++++++++++++-----------------------------
 go.mod            |  2 +-
 go.sum            |  5 -----
 lib/ui/context.go | 14 ------------
 lib/ui/ui.go      |  1 +
 5 files changed, 22 insertions(+), 54 deletions(-)

diff --git a/app/terminal.go b/app/terminal.go
index 97cd4410591b..d894368f2b92 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -7,8 +7,8 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rjarry/aerc/log"
	tcellterm "git.sr.ht/~rockorager/tcell-term"
	"git.sr.ht/~rockorager/vaxis"
	"git.sr.ht/~rockorager/vaxis/widgets/term"

	"github.com/gdamore/tcell/v2"
)
@@ -23,7 +23,7 @@ type Terminal struct {
	ctx     *ui.Context
	focus   bool
	visible bool
	vterm   *tcellterm.VT
	vterm   *term.Model
	running bool

	OnClose func(err error)
@@ -35,7 +35,7 @@ type Terminal struct {
func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
	term := &Terminal{
		cmd:     cmd,
		vterm:   tcellterm.New(),
		vterm:   term.New(),
		visible: true,
	}
	term.vterm.OSC8 = config.General.EnableOSC8
@@ -83,8 +83,6 @@ func (term *Terminal) Invalidate() {
}

func (term *Terminal) Draw(ctx *ui.Context) {
	term.vterm.SetSurface(ctx)

	w, h := ctx.Size()
	if !term.isClosed() && term.ctx != nil {
		ow, oh := term.ctx.Size()
@@ -105,15 +103,7 @@ func (term *Terminal) Draw(ctx *ui.Context) {
			term.OnStart()
		}
	}
	term.vterm.Draw()
	if term.focus {
		y, x, style, vis := term.vterm.Cursor()
		if vis && !term.isClosed() {
			ctx.SetCursor(x, y, vaxis.CursorStyle(style))
		} else {
			ctx.HideCursor()
		}
	}
	term.vterm.Draw(ctx.Window())
}

func (term *Terminal) Show(visible bool) {
@@ -136,7 +126,7 @@ func (term *Terminal) MouseEvent(localX int, localY int, event vaxis.Event) {
		return
	}
	e := tcell.NewEventMouse(localX, localY, ev.Buttons(), ev.Modifiers())
	term.vterm.HandleEvent(e)
	term.vterm.Update(e)
}

func (term *Terminal) Focus(focus bool) {
@@ -144,39 +134,34 @@ func (term *Terminal) Focus(focus bool) {
		return
	}
	term.focus = focus
	if term.ctx != nil {
		if !term.focus {
			term.ctx.HideCursor()
		} else {
			y, x, style, _ := term.vterm.Cursor()
			term.ctx.SetCursor(x, y, vaxis.CursorStyle(style))
			term.Invalidate()
		}
	if term.focus {
		term.vterm.Focus()
	} else {
		term.vterm.Blur()
	}
}

// HandleEvent is used to watch the underlying terminal events
func (term *Terminal) HandleEvent(ev tcell.Event) {
	if term.isClosed() {
func (t *Terminal) HandleEvent(ev vaxis.Event) {
	if t.isClosed() {
		return
	}
	switch ev := ev.(type) {
	case *tcellterm.EventRedraw:
		if term.visible {
	case vaxis.Redraw:
		if t.visible {
			ui.Invalidate()
		}
	case *tcellterm.EventTitle:
		if term.OnTitle != nil {
			term.OnTitle(ev.Title())
	case term.EventTitle:
		if t.OnTitle != nil {
			t.OnTitle(string(ev))
		}
	case *tcellterm.EventClosed:
		term.Close()
	case term.EventClosed:
		t.Close()
		ui.Invalidate()
	}
}

func (term *Terminal) Event(event vaxis.Event) bool {
	event = tcell.TcellEvent(event)
	if term.OnEvent != nil {
		if term.OnEvent(event) {
			return true
@@ -185,5 +170,6 @@ func (term *Terminal) Event(event vaxis.Event) bool {
	if term.isClosed() {
		return false
	}
	return term.vterm.HandleEvent(event)
	term.vterm.Update(event)
	return true
}
diff --git a/go.mod b/go.mod
index 63f3e7ba3646..1d8491e79610 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.18
require (
	git.sr.ht/~rjarry/go-opt v1.3.0
	git.sr.ht/~rockorager/go-jmap v0.3.0
	git.sr.ht/~rockorager/tcell-term v0.10.0
	git.sr.ht/~rockorager/vaxis v0.4.7
	github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5
	github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
@@ -59,6 +58,7 @@ require (
	golang.org/x/text v0.13.0 // indirect
	google.golang.org/appengine v1.6.7 // indirect
	google.golang.org/protobuf v1.30.0 // indirect
	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

diff --git a/go.sum b/go.sum
index 48122d9f3f22..25327ff4a36b 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@ git.sr.ht/~rjarry/go-opt v1.3.0 h1:9BLOcXi5OhDYVzH3Td48i2uM/byMGNqXY7YhBzvEZg8=
git.sr.ht/~rjarry/go-opt v1.3.0/go.mod h1:oEPZUTJKGn1FVye0znaLoeskE/QTuyoJw5q+fjusdM4=
git.sr.ht/~rockorager/go-jmap v0.3.0 h1:h2WuPcNyXRYFg9+W2HGf/mzIqC6ISy9EaS/BGa7Z5RY=
git.sr.ht/~rockorager/go-jmap v0.3.0/go.mod h1:aOTCtwpZSINpDDSOkLGpHU0Kbbm5lcSDMcobX3ZtOjY=
git.sr.ht/~rockorager/tcell-term v0.10.0 h1:BqxJjtCMmLIfS6fdIal8TSiH3qPiYTjATuIRIWNVPbo=
git.sr.ht/~rockorager/tcell-term v0.10.0/go.mod h1:Snxh5CrziiA2CjyLOZ6tGAg5vMPlE+REMWT3rtKuyyQ=
git.sr.ht/~rockorager/vaxis v0.4.7 h1:9VlkBBF9dxy62AMHnKAU8GqEs2/mUTlke/ZJ9o/6luk=
git.sr.ht/~rockorager/vaxis v0.4.7/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
git.sr.ht/~rockorager/vaxis-tcell v0.4.7 h1:ISMSnvbz1jnG9Ppi9y3NJKaLl7Nu67qMkpEXbXwHgmg=
@@ -24,7 +22,6 @@ github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSb
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
@@ -149,7 +146,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -200,7 +196,6 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
diff --git a/lib/ui/context.go b/lib/ui/context.go
index 693611208a77..2ca0d310b32b 100644
--- a/lib/ui/context.go
+++ b/lib/ui/context.go
@@ -5,7 +5,6 @@ import (

	"git.sr.ht/~rjarry/aerc/lib/parse"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

// A context allows you to draw in a sub-region of the terminal
@@ -136,19 +135,6 @@ func (ctx *Context) Popover(x, y, width, height int, d Drawable) {
	})
}

// SetContent is used to update the content of the Surface at the given
// location.
func (ctx *Context) SetContent(x int, y int, ch rune, comb []rune, style tcell.Style) {
	g := []rune{ch}
	g = append(g, comb...)
	ctx.window.SetCell(x, y, vaxis.Cell{
		Character: vaxis.Character{
			Grapheme: string(g),
		},
		Style: tcell.VaxisStyle(style),
	})
}

func (ctx *Context) Size() (int, int) {
	return ctx.window.Size()
}
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index 84c34459f038..a7233f49da4e 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -125,6 +125,7 @@ func Render() {
		state.vx.Window().Clear()
		// reset popover for the next Draw
		state.popover = nil
		state.vx.HideCursor()
		state.content.Draw(state.ctx)
		if state.popover != nil {
			// if the Draw resulted in a popover, draw it
-- 
2.43.0

[PATCH aerc v3 12/17] paste: use vaxis paste events Export this patch

Replace tcell paste events with vaxis paste events

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/aerc.go | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/app/aerc.go b/app/aerc.go
index 00f1cb5fc385..4c96ab70ea17 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -385,13 +385,15 @@ func (aerc *Aerc) Event(event vaxis.Event) bool {
		x, y := event.Position()
		aerc.grid.MouseEvent(x, y, event)
		return true
	case *tcell.EventPaste:
		if event.Start() {
			aerc.pasting = true
		}
		if event.End() {
			aerc.pasting = false
	case vaxis.PasteStartEvent:
		aerc.pasting = true
		interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
		if ok {
			return interactive.Event(event)
		}
		return false
	case vaxis.PasteEndEvent:
		aerc.pasting = false
		interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
		if ok {
			return interactive.Event(event)
-- 
2.43.0

[PATCH aerc v3 13/17] mouse: use vaxis mouse events Export this patch

Replace all tcell.EventMouse events with vaxis mouse events

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 app/aerc.go         |  6 ++----
 app/compose.go      |  9 ++++-----
 app/dirlist.go      | 12 +++++-------
 app/dirtree.go      | 11 +++++------
 app/msglist.go      | 11 +++++------
 app/partswitcher.go | 11 +++++------
 app/terminal.go     |  9 ++++-----
 lib/ui/grid.go      |  3 +--
 lib/ui/stack.go     |  5 ++---
 lib/ui/tab.go       | 15 +++++++--------
 lib/ui/textinput.go |  7 +++----
 11 files changed, 43 insertions(+), 56 deletions(-)

diff --git a/app/aerc.go b/app/aerc.go
index 4c96ab70ea17..1df89552adad 100644
--- a/app/aerc.go
+++ b/app/aerc.go
@@ -15,7 +15,6 @@ import (
	"git.sr.ht/~rockorager/vaxis"
	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
@@ -381,9 +380,8 @@ func (aerc *Aerc) Event(event vaxis.Event) bool {
			}
			return false
		}
	case *tcell.EventMouse:
		x, y := event.Position()
		aerc.grid.MouseEvent(x, y, event)
	case vaxis.Mouse:
		aerc.grid.MouseEvent(event.Col, event.Row, event)
		return true
	case vaxis.PasteStartEvent:
		aerc.pasting = true
diff --git a/app/compose.go b/app/compose.go
index a73127c9c3fd..2c5247d33323 100644
--- a/app/compose.go
+++ b/app/compose.go
@@ -17,7 +17,6 @@ import (
	"time"

	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
	"github.com/pkg/errors"

@@ -1224,8 +1223,8 @@ func (c *Composer) resetReview() {
}

func (c *Composer) termEvent(event vaxis.Event) bool {
	if event, ok := event.(*tcell.EventMouse); ok {
		if event.Buttons() == tcell.Button1 {
	if event, ok := event.(vaxis.Mouse); ok {
		if event.Button == vaxis.MouseLeftButton {
			c.FocusTerminal()
			return true
		}
@@ -1645,8 +1644,8 @@ func (he *headerEditor) Draw(ctx *ui.Context) {
}

func (he *headerEditor) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		if event.Buttons() == tcell.Button1 {
	if event, ok := event.(vaxis.Mouse); ok {
		if event.Button == vaxis.MouseLeftButton {
			he.focused = true
		}

diff --git a/app/dirlist.go b/app/dirlist.go
index e92283740ed9..71db2b6a0047 100644
--- a/app/dirlist.go
+++ b/app/dirlist.go
@@ -8,8 +8,6 @@ import (
	"sort"
	"time"

	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
	"git.sr.ht/~rjarry/aerc/lib/parse"
@@ -376,16 +374,16 @@ func (dirlist *DirectoryList) drawScrollbar(ctx *ui.Context) {
}

func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
	if event, ok := event.(vaxis.Mouse); ok {
		switch event.Button {
		case vaxis.MouseLeftButton:
			clickedDir, ok := dirlist.Clicked(localX, localY)
			if ok {
				dirlist.Select(clickedDir)
			}
		case tcell.WheelDown:
		case vaxis.MouseWheelDown:
			dirlist.Next()
		case tcell.WheelUp:
		case vaxis.MouseWheelUp:
			dirlist.Prev()
		}
	}
diff --git a/app/dirtree.go b/app/dirtree.go
index c3ad2ef6246b..5c1fcbcc4edd 100644
--- a/app/dirtree.go
+++ b/app/dirtree.go
@@ -15,7 +15,6 @@ import (
	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/types"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

type DirectoryTree struct {
@@ -168,16 +167,16 @@ func (dt *DirectoryTree) Draw(ctx *ui.Context) {
}

func (dt *DirectoryTree) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
	if event, ok := event.(vaxis.Mouse); ok {
		switch event.Button {
		case vaxis.MouseLeftButton:
			clickedDir, ok := dt.Clicked(localX, localY)
			if ok {
				dt.Select(clickedDir)
			}
		case tcell.WheelDown:
		case vaxis.MouseWheelDown:
			dt.NextPrev(1)
		case tcell.WheelUp:
		case vaxis.MouseWheelUp:
			dt.NextPrev(-1)
		}
	}
diff --git a/app/msglist.go b/app/msglist.go
index 8106c3cfc694..4b4e41294e49 100644
--- a/app/msglist.go
+++ b/app/msglist.go
@@ -7,7 +7,6 @@ import (

	sortthread "github.com/emersion/go-imap-sortthread"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -255,9 +254,9 @@ func (ml *MessageList) drawScrollbar(ctx *ui.Context) {
}

func (ml *MessageList) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
	if event, ok := event.(vaxis.Mouse); ok {
		switch event.Button {
		case vaxis.MouseLeftButton:
			selectedMsg, ok := ml.Clicked(localX, localY)
			if ok {
				ml.Select(selectedMsg)
@@ -281,12 +280,12 @@ func (ml *MessageList) MouseEvent(localX int, localY int, event vaxis.Event) {
						NewTab(viewer, msg.Envelope.Subject)
					})
			}
		case tcell.WheelDown:
		case vaxis.MouseWheelDown:
			if ml.store != nil {
				ml.store.Next()
			}
			ml.Invalidate()
		case tcell.WheelUp:
		case vaxis.MouseWheelUp:
			if ml.store != nil {
				ml.store.Prev()
			}
diff --git a/app/partswitcher.go b/app/partswitcher.go
index 5dc996f783d5..5a935a8c571f 100644
--- a/app/partswitcher.go
+++ b/app/partswitcher.go
@@ -6,7 +6,6 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"
	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
)

@@ -168,7 +167,7 @@ func (ps *PartSwitcher) MouseEvent(localX int, localY int, event vaxis.Event) {
		return
	}

	e, ok := event.(*tcell.EventMouse)
	e, ok := event.(vaxis.Mouse)
	if !ok {
		return
	}
@@ -177,8 +176,8 @@ func (ps *PartSwitcher) MouseEvent(localX int, localY int, event vaxis.Event) {
		ps.parts[ps.selected].term.Focus(false)
	}

	switch e.Buttons() {
	case tcell.Button1:
	switch e.Button {
	case vaxis.MouseLeftButton:
		i := localY - ps.offset + ps.Scroll()
		if i < 0 || i >= len(ps.parts) {
			break
@@ -188,10 +187,10 @@ func (ps *PartSwitcher) MouseEvent(localX int, localY int, event vaxis.Event) {
		}
		ps.selected = i
		ps.Invalidate()
	case tcell.WheelDown:
	case vaxis.MouseWheelDown:
		ps.NextPart()
		ps.Invalidate()
	case tcell.WheelUp:
	case vaxis.MouseWheelUp:
		ps.PreviousPart()
		ps.Invalidate()
	}
diff --git a/app/terminal.go b/app/terminal.go
index d894368f2b92..77da644ec6e6 100644
--- a/app/terminal.go
+++ b/app/terminal.go
@@ -9,8 +9,6 @@ import (
	"git.sr.ht/~rjarry/aerc/log"
	"git.sr.ht/~rockorager/vaxis"
	"git.sr.ht/~rockorager/vaxis/widgets/term"

	"github.com/gdamore/tcell/v2"
)

type HasTerminal interface {
@@ -115,7 +113,7 @@ func (term *Terminal) Terminal() *Terminal {
}

func (term *Terminal) MouseEvent(localX int, localY int, event vaxis.Event) {
	ev, ok := event.(*tcell.EventMouse)
	ev, ok := event.(vaxis.Mouse)
	if !ok {
		return
	}
@@ -125,8 +123,9 @@ func (term *Terminal) MouseEvent(localX int, localY int, event vaxis.Event) {
	if term.isClosed() {
		return
	}
	e := tcell.NewEventMouse(localX, localY, ev.Buttons(), ev.Modifiers())
	term.vterm.Update(e)
	ev.Row = localY
	ev.Col = localX
	term.vterm.Update(ev)
}

func (term *Terminal) Focus(focus bool) {
diff --git a/lib/ui/grid.go b/lib/ui/grid.go
index 00f759bf9cc3..ce3d37d6179e 100644
--- a/lib/ui/grid.go
+++ b/lib/ui/grid.go
@@ -5,7 +5,6 @@ import (
	"sync"

	"git.sr.ht/~rockorager/vaxis"
	"github.com/gdamore/tcell/v2"
)

type Grid struct {
@@ -130,7 +129,7 @@ func (grid *Grid) Draw(ctx *Context) {
}

func (grid *Grid) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
	if event, ok := event.(vaxis.Mouse); ok {

		grid.mutex.RLock()
		defer grid.mutex.RUnlock()
diff --git a/lib/ui/stack.go b/lib/ui/stack.go
index a4017007bd6e..890ab272f856 100644
--- a/lib/ui/stack.go
+++ b/lib/ui/stack.go
@@ -4,8 +4,7 @@ import (
	"fmt"

	"git.sr.ht/~rjarry/aerc/config"

	"github.com/gdamore/tcell/v2"
	"git.sr.ht/~rockorager/vaxis"
)

type Stack struct {
@@ -34,7 +33,7 @@ func (stack *Stack) Draw(ctx *Context) {
	}
}

func (stack *Stack) MouseEvent(localX int, localY int, event tcell.Event) {
func (stack *Stack) MouseEvent(localX int, localY int, event vaxis.Event) {
	if len(stack.children) > 0 {
		if element, ok := stack.Peek().(Mouseable); ok {
			element.MouseEvent(localX, localY, event)
diff --git a/lib/ui/tab.go b/lib/ui/tab.go
index 07b304ee500e..6149c4fda886 100644
--- a/lib/ui/tab.go
+++ b/lib/ui/tab.go
@@ -3,7 +3,6 @@ package ui
import (
	"sync"

	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -395,7 +394,7 @@ func (strip *TabStrip) Invalidate() {
	Invalidate()
}

func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
func (strip *TabStrip) MouseEvent(localX int, localY int, event vaxis.Event) {
	strip.parent.m.Lock()
	defer strip.parent.m.Unlock()
	changeFocus := func(focus bool) {
@@ -406,9 +405,9 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
	}
	unfocus := func() { changeFocus(false) }
	refocus := func() { changeFocus(true) }
	if event, ok := event.(*tcell.EventMouse); ok {
		switch event.Buttons() {
		case tcell.Button1:
	if event, ok := event.(vaxis.Mouse); ok {
		switch event.Button {
		case vaxis.MouseLeftButton:
			selectedTab, ok := strip.clicked(localX, localY)
			if !ok || selectedTab == strip.parent.curIndex {
				return
@@ -416,7 +415,7 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
			unfocus()
			strip.parent.selectPriv(selectedTab)
			refocus()
		case tcell.WheelDown:
		case vaxis.MouseWheelDown:
			unfocus()
			index := strip.parent.curIndex + 1
			if index >= len(strip.parent.tabs) {
@@ -424,7 +423,7 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
			}
			strip.parent.selectPriv(index)
			refocus()
		case tcell.WheelUp:
		case vaxis.MouseWheelUp:
			unfocus()
			index := strip.parent.curIndex - 1
			if index < 0 {
@@ -432,7 +431,7 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
			}
			strip.parent.selectPriv(index)
			refocus()
		case tcell.Button3:
		case vaxis.MouseMiddleButton:
			selectedTab, ok := strip.clicked(localX, localY)
			if !ok {
				return
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index d52ee07a687c..a01184f2ac36 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -6,7 +6,6 @@ import (
	"sync"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"

	"git.sr.ht/~rjarry/aerc/config"
@@ -144,9 +143,9 @@ func (ti *TextInput) drawPopover(ctx *Context) {
	ctx.Popover(pos, 0, width, height, cmp)
}

func (ti *TextInput) MouseEvent(localX int, localY int, event tcell.Event) {
	if event, ok := event.(*tcell.EventMouse); ok {
		if event.Buttons() == tcell.Button1 {
func (ti *TextInput) MouseEvent(localX int, localY int, event vaxis.Event) {
	if event, ok := event.(vaxis.Mouse); ok {
		if event.Button == vaxis.MouseLeftButton {
			if localX >= len(ti.prompt)+1 && localX <= len(ti.text[ti.scroll:])+len(ti.prompt)+1 {
				ti.index = localX - len(ti.prompt) - 1
				ti.ensureScroll()
-- 
2.43.0

[PATCH aerc v3 14/17] aerc: remove tcell import Export this patch

All references to tcell have been replaced with vaxis

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 go.mod |  3 ---
 go.sum | 19 +------------------
 2 files changed, 1 insertion(+), 21 deletions(-)

diff --git a/go.mod b/go.mod
index 1d8491e79610..26eaf474cb22 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,6 @@ require (
	github.com/fsnotify/fsevents v0.1.1
	github.com/fsnotify/fsnotify v1.6.0
	github.com/gatherstars-com/jwz v1.4.0
	github.com/gdamore/tcell/v2 v2.6.0
	github.com/go-ini/ini v1.67.0
	github.com/lithammer/fuzzysearch v1.1.5
	github.com/mattn/go-isatty v0.0.18
@@ -63,5 +62,3 @@ require (
)

replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3

replace github.com/gdamore/tcell/v2 => git.sr.ht/~rockorager/vaxis-tcell v0.4.7
diff --git a/go.sum b/go.sum
index 25327ff4a36b..0ec530333815 100644
--- a/go.sum
+++ b/go.sum
@@ -4,8 +4,6 @@ git.sr.ht/~rockorager/go-jmap v0.3.0 h1:h2WuPcNyXRYFg9+W2HGf/mzIqC6ISy9EaS/BGa7Z
git.sr.ht/~rockorager/go-jmap v0.3.0/go.mod h1:aOTCtwpZSINpDDSOkLGpHU0Kbbm5lcSDMcobX3ZtOjY=
git.sr.ht/~rockorager/vaxis v0.4.7 h1:9VlkBBF9dxy62AMHnKAU8GqEs2/mUTlke/ZJ9o/6luk=
git.sr.ht/~rockorager/vaxis v0.4.7/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
git.sr.ht/~rockorager/vaxis-tcell v0.4.7 h1:ISMSnvbz1jnG9Ppi9y3NJKaLl7Nu67qMkpEXbXwHgmg=
git.sr.ht/~rockorager/vaxis-tcell v0.4.7/go.mod h1:mpNiMGQDJ3fiwVO8pvz0GENWCdCXEE50beqCbgGoXLc=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 h1:JW27/kGLQzeM1Fxg5YQhdkTEAU7HIAHMgSag35zVTnY=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@@ -65,6 +63,7 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
github.com/gatherstars-com/jwz v1.4.0 h1:HrCJmTss6/PTzBxxQUGbJ5f0aFYjnfrfqpGlyWH+R5A=
github.com/gatherstars-com/jwz v1.4.0/go.mod h1:twtXjMamfC5/NRCTJ9vDiHGeDivORkTAvOMUX/qo0Ik=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
@@ -81,7 +80,6 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
@@ -140,13 +138,9 @@ github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsIm
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@@ -155,12 +149,9 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -168,7 +159,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
@@ -189,32 +179,25 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-- 
2.43.0

[PATCH aerc v3 15/17] aerc: set title using vaxis Export this patch

Set the window title using Vaxis at UI initialization.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 go.mod       |  1 -
 go.sum       |  3 ---
 lib/ui/ui.go |  1 +
 main.go      | 28 ----------------------------
 4 files changed, 1 insertion(+), 32 deletions(-)

diff --git a/go.mod b/go.mod
index 26eaf474cb22..fb81a2dc6a90 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,6 @@ require (
	github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab
	github.com/stretchr/testify v1.8.4
	github.com/syndtr/goleveldb v1.0.0
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
	golang.org/x/image v0.13.0
	golang.org/x/oauth2 v0.7.0
	golang.org/x/sys v0.13.0
diff --git a/go.sum b/go.sum
index 0ec530333815..2705424e07d5 100644
--- a/go.sum
+++ b/go.sum
@@ -145,10 +145,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
diff --git a/lib/ui/ui.go b/lib/ui/ui.go
index a7233f49da4e..ce0a7e930106 100644
--- a/lib/ui/ui.go
+++ b/lib/ui/ui.go
@@ -63,6 +63,7 @@ func Initialize(content DrawableInteractive) error {
	state.content = content
	state.vx = vx
	state.ctx = NewContext(state.vx, onPopover)
	vx.SetTitle("aerc")

	Invalidate()
	if beeper, ok := content.(DrawableInteractiveBeeper); ok {
diff --git a/main.go b/main.go
index f592189adce2..77227e5b37c6 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,6 @@
package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
@@ -13,8 +12,6 @@ import (
	"time"

	"git.sr.ht/~rjarry/go-opt"
	"github.com/mattn/go-isatty"
	"github.com/xo/terminfo"

	"git.sr.ht/~rjarry/aerc/app"
	"git.sr.ht/~rjarry/aerc/commands"
@@ -184,27 +181,6 @@ func buildInfo() string {
	return info
}

func setWindowTitle() {
	log.Tracef("Parsing terminfo")
	ti, err := terminfo.LoadFromEnv()
	if err != nil {
		log.Warnf("Cannot get terminfo: %v", err)
		return
	}

	if !ti.Has(terminfo.HasStatusLine) {
		log.Infof("Terminal does not have status line support")
		return
	}

	log.Debugf("Setting terminal title")
	buf := new(bytes.Buffer)
	ti.Fprintf(buf, terminfo.ToStatusLine)
	fmt.Fprint(buf, "aerc")
	ti.Fprintf(buf, terminfo.FromStatusLine)
	os.Stderr.Write(buf.Bytes())
}

type Opts struct {
	Help     bool     `opt:"-h" action:"ShowHelp"`
	Version  bool     `opt:"-v" action:"ShowVersion"`
@@ -324,10 +300,6 @@ func main() {
		}
	}

	if isatty.IsTerminal(os.Stderr.Fd()) {
		setWindowTitle()
	}

	go func() {
		defer log.PanicHandler()
		err := hooks.RunHook(&hooks.AercStartup{Version: Version})
-- 
2.43.0

[PATCH aerc v3 16/17] docs: update docs to remove tcell reference Export this patch

Update color value reference in documentation

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 doc/aerc-stylesets.7.scd | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/doc/aerc-stylesets.7.scd b/doc/aerc-stylesets.7.scd
index 8083888b14b4..a5871756fd76 100644
--- a/doc/aerc-stylesets.7.scd
+++ b/doc/aerc-stylesets.7.scd
@@ -327,8 +327,7 @@ since *msglist_\** also applies to *msglist\_marked*.

# COLORS

The color values are set using the values accepted by the tcell library.
The values can be one of the following.
The color values are set using any of the following methods:

_default_
	The color is set as per the system or terminal default.
-- 
2.43.0

[PATCH aerc v3 17/17] vaxis: update to v0.5.1 Export this patch

v0.5.1 brings many perf improvements to the terminal widget as well as a
few fixes to it. There are also some keybind related fixes for matching
keys

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
---
 go.mod | 2 +-
 go.sum | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/go.mod b/go.mod
index fb81a2dc6a90..61ea0dca9db6 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
require (
	git.sr.ht/~rjarry/go-opt v1.3.0
	git.sr.ht/~rockorager/go-jmap v0.3.0
	git.sr.ht/~rockorager/vaxis v0.4.7
	git.sr.ht/~rockorager/vaxis v0.5.1
	github.com/ProtonMail/go-crypto v0.0.0-20230417170513-8ee5748c52b5
	github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
	github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
diff --git a/go.sum b/go.sum
index 2705424e07d5..5ed2fee01b1c 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,8 @@ git.sr.ht/~rjarry/go-opt v1.3.0 h1:9BLOcXi5OhDYVzH3Td48i2uM/byMGNqXY7YhBzvEZg8=
git.sr.ht/~rjarry/go-opt v1.3.0/go.mod h1:oEPZUTJKGn1FVye0znaLoeskE/QTuyoJw5q+fjusdM4=
git.sr.ht/~rockorager/go-jmap v0.3.0 h1:h2WuPcNyXRYFg9+W2HGf/mzIqC6ISy9EaS/BGa7Z5RY=
git.sr.ht/~rockorager/go-jmap v0.3.0/go.mod h1:aOTCtwpZSINpDDSOkLGpHU0Kbbm5lcSDMcobX3ZtOjY=
git.sr.ht/~rockorager/vaxis v0.4.7 h1:9VlkBBF9dxy62AMHnKAU8GqEs2/mUTlke/ZJ9o/6luk=
git.sr.ht/~rockorager/vaxis v0.4.7/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
git.sr.ht/~rockorager/vaxis v0.5.1 h1:5b2v3YXPKlLFl2Xu+6ZHTfT6cw2ZziVfQDpvGBcpORs=
git.sr.ht/~rockorager/vaxis v0.5.1/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 h1:JW27/kGLQzeM1Fxg5YQhdkTEAU7HIAHMgSag35zVTnY=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-- 
2.43.0
aerc/patches: SUCCESS in 5m0s

[Replace tcell][0] v3 from [Tim Culverhouse][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/48376
[1]: mailto:tim@timculverhouse.com

✓ #1129000 SUCCESS aerc/patches/alpine-edge.yml https://builds.sr.ht/~rjarry/job/1129000
✓ #1129001 SUCCESS aerc/patches/openbsd.yml     https://builds.sr.ht/~rjarry/job/1129001