diff options
author | Tim Culverhouse <tim@timculverhouse.com> | 2022-09-14 14:09:41 -0500 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-09-14 22:18:35 +0200 |
commit | 518f3e962ce39fb9712bb693857789ab22adfe9c (patch) | |
tree | 87dbed5b918e507e10bc35e53ef471bfc2a6f96b | |
parent | 17c4781911a16c4f0d13dfbad4fab89db283dd54 (diff) | |
download | aerc-518f3e962ce39fb9712bb693857789ab22adfe9c.tar.gz |
term: replace go-libvterm with tcell-term
Replace go-libvterm package with tcell-term. go-libvterm provides the
embedded terminal for aerc. It uses a statically linked C library,
requiring CGO.
tcell-term is written in pure go and is written to be portable with
tcell applications by implementing the tcell Widget interface. This
allows the terminal to take a view (which aerc already supplies) and
draw directly to it, as well as issue tcell Events to a Watcher.
Enable setting cursor shapes in embedded terminals.
Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Signed-off-by: Robin Jarry <robin@jarry.cc>
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | go.mod | 8 | ||||
-rw-r--r-- | go.sum | 9 | ||||
-rw-r--r-- | widgets/msgviewer.go | 4 | ||||
-rw-r--r-- | widgets/terminal.go | 444 |
5 files changed, 84 insertions, 383 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7454b2a2..d5395ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Zoxide support with `:z`. - Hide local timezone with `send-as-utc = true` in `accounts.conf`. - Persistent command history in `~/.cache/aerc/history`. +- Cursor shape support in embedded terminals. ### Changed - `:open-link` now supports link types other than HTTP(S) - Running the same command multiple times only adds one entry to the command history. +- Embedded terminal backend (libvterm was replaced by a pure go implementation). ### Fixed @@ -3,12 +3,12 @@ module git.sr.ht/~rjarry/aerc go 1.16 require ( + git.sr.ht/~rockorager/tcell-term v0.1.0 git.sr.ht/~sircmpwn/getopt v1.0.0 github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182 - github.com/creack/pty v1.1.18 + github.com/creack/pty v1.1.18 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 - github.com/ddevault/go-libvterm v0.0.0-20190526194226-b7d861da3810 github.com/emersion/go-imap v1.2.0 github.com/emersion/go-imap-sortthread v1.2.0 github.com/emersion/go-maildir v0.2.0 @@ -28,7 +28,6 @@ require ( github.com/kyoh86/xdg v1.2.0 github.com/lithammer/fuzzysearch v1.1.3 github.com/mattn/go-isatty v0.0.16 - github.com/mattn/go-pointer v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.13 github.com/miolini/datacounter v1.0.2 github.com/mitchellh/go-homedir v1.1.0 @@ -39,9 +38,6 @@ require ( github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 github.com/zenhack/go.notmuch v0.0.0-20211022191430-4d57e8ad2a8b golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 - golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect - golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 @@ -62,6 +62,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.sr.ht/~rockorager/tcell-term v0.1.0 h1:5tRUEW6y5O5xFr4SvVL+hBO56fllgbYBr7AYGFe6O6I= +git.sr.ht/~rockorager/tcell-term v0.1.0/go.mod h1:0CEvSJrV0ItwHlPHSmkxd5egiuUtpvGnnCsqzV02BN4= git.sr.ht/~sircmpwn/getopt v1.0.0 h1:/pRHjO6/OCbBF4puqD98n6xtPEgE//oq5U8NXjP7ROc= git.sr.ht/~sircmpwn/getopt v1.0.0/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= github.com/Antonboom/errname v0.1.7 h1:mBBDKvEYwPl4WFFNwec1CZO096G6vzK9vvDQzAwkako= @@ -186,6 +188,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/cristalhq/acmd v0.7.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= @@ -199,8 +202,6 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ddevault/go-libvterm v0.0.0-20190526194226-b7d861da3810 h1:VlHKuIrEvuGlED53TkovT4AVUjrqTyeCt3wiqw1OsFc= -github.com/ddevault/go-libvterm v0.0.0-20190526194226-b7d861da3810/go.mod h1:Ow1oE1Hr4xE7eWY2/Ih2kbcOyyXDH7G0XKv/I4yiCYs= github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -648,9 +649,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-pointer v0.0.0-20180825124634-49522c3f3791/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= -github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= -github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1037,7 +1035,6 @@ golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d h1:+W8Qf4iJtMGKkyAygcKohjxTk4JPsL9DpzApJ22m5Ic= golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go index cd0c9371..94ba98c8 100644 --- a/widgets/msgviewer.go +++ b/widgets/msgviewer.go @@ -758,6 +758,10 @@ func (pv *PartViewer) copyFilterOutToPager() { logging.Warnf("failed to wait for the filter process: %v", err) } pv.pagerin.Close() + // If the pager command doesn't keep the terminal running, we + // risk not drawing the screen until user input unless we + // invalidate after writing + pv.Invalidate() }() } diff --git a/widgets/terminal.go b/widgets/terminal.go index 55a849ab..3a496dea 100644 --- a/widgets/terminal.go +++ b/widgets/terminal.go @@ -1,113 +1,27 @@ package widgets import ( - "os" "os/exec" - "sync" "syscall" "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/logging" + tcellterm "git.sr.ht/~rockorager/tcell-term" - "github.com/creack/pty" - vterm "github.com/ddevault/go-libvterm" "github.com/gdamore/tcell/v2" + "github.com/gdamore/tcell/v2/views" ) -type vtermKey struct { - Key vterm.Key - Rune rune - Mod vterm.Modifier -} - -var keyMap map[tcell.Key]vtermKey - -func directKey(key vterm.Key) vtermKey { - return vtermKey{key, 0, vterm.ModNone} -} - -func runeMod(r rune, mod vterm.Modifier) vtermKey { - return vtermKey{vterm.KeyNone, r, mod} -} - -func keyMod(key vterm.Key, mod vterm.Modifier) vtermKey { - return vtermKey{key, 0, mod} -} - -func init() { - keyMap = make(map[tcell.Key]vtermKey) - keyMap[tcell.KeyCtrlSpace] = runeMod(' ', vterm.ModCtrl) - keyMap[tcell.KeyCtrlA] = runeMod('a', vterm.ModCtrl) - keyMap[tcell.KeyCtrlB] = runeMod('b', vterm.ModCtrl) - keyMap[tcell.KeyCtrlC] = runeMod('c', vterm.ModCtrl) - keyMap[tcell.KeyCtrlD] = runeMod('d', vterm.ModCtrl) - keyMap[tcell.KeyCtrlE] = runeMod('e', vterm.ModCtrl) - keyMap[tcell.KeyCtrlF] = runeMod('f', vterm.ModCtrl) - keyMap[tcell.KeyCtrlG] = runeMod('g', vterm.ModCtrl) - keyMap[tcell.KeyCtrlH] = runeMod('h', vterm.ModCtrl) - keyMap[tcell.KeyCtrlI] = runeMod('i', vterm.ModCtrl) - keyMap[tcell.KeyCtrlJ] = runeMod('j', vterm.ModCtrl) - keyMap[tcell.KeyCtrlK] = runeMod('k', vterm.ModCtrl) - keyMap[tcell.KeyCtrlL] = runeMod('l', vterm.ModCtrl) - keyMap[tcell.KeyCtrlM] = runeMod('m', vterm.ModCtrl) - keyMap[tcell.KeyCtrlN] = runeMod('n', vterm.ModCtrl) - keyMap[tcell.KeyCtrlO] = runeMod('o', vterm.ModCtrl) - keyMap[tcell.KeyCtrlP] = runeMod('p', vterm.ModCtrl) - keyMap[tcell.KeyCtrlQ] = runeMod('q', vterm.ModCtrl) - keyMap[tcell.KeyCtrlR] = runeMod('r', vterm.ModCtrl) - keyMap[tcell.KeyCtrlS] = runeMod('s', vterm.ModCtrl) - keyMap[tcell.KeyCtrlT] = runeMod('t', vterm.ModCtrl) - keyMap[tcell.KeyCtrlU] = runeMod('u', vterm.ModCtrl) - keyMap[tcell.KeyCtrlV] = runeMod('v', vterm.ModCtrl) - keyMap[tcell.KeyCtrlW] = runeMod('w', vterm.ModCtrl) - keyMap[tcell.KeyCtrlX] = runeMod('x', vterm.ModCtrl) - keyMap[tcell.KeyCtrlY] = runeMod('y', vterm.ModCtrl) - keyMap[tcell.KeyCtrlZ] = runeMod('z', vterm.ModCtrl) - keyMap[tcell.KeyCtrlBackslash] = runeMod('\\', vterm.ModCtrl) - keyMap[tcell.KeyCtrlCarat] = runeMod('^', vterm.ModCtrl) - keyMap[tcell.KeyCtrlUnderscore] = runeMod('_', vterm.ModCtrl) - keyMap[tcell.KeyEnter] = directKey(vterm.KeyEnter) - keyMap[tcell.KeyTab] = directKey(vterm.KeyTab) - keyMap[tcell.KeyBacktab] = keyMod(vterm.KeyTab, vterm.ModShift) - keyMap[tcell.KeyBackspace] = directKey(vterm.KeyBackspace) - keyMap[tcell.KeyEscape] = directKey(vterm.KeyEscape) - keyMap[tcell.KeyUp] = directKey(vterm.KeyUp) - keyMap[tcell.KeyDown] = directKey(vterm.KeyDown) - keyMap[tcell.KeyLeft] = directKey(vterm.KeyLeft) - keyMap[tcell.KeyRight] = directKey(vterm.KeyRight) - keyMap[tcell.KeyInsert] = directKey(vterm.KeyIns) - keyMap[tcell.KeyDelete] = directKey(vterm.KeyDel) - keyMap[tcell.KeyHome] = directKey(vterm.KeyHome) - keyMap[tcell.KeyEnd] = directKey(vterm.KeyEnd) - keyMap[tcell.KeyPgUp] = directKey(vterm.KeyPageUp) - keyMap[tcell.KeyPgDn] = directKey(vterm.KeyPageDown) - for i := 0; i < 64; i++ { - keyMap[tcell.Key(int(tcell.KeyF1)+i)] = directKey(vterm.Key(int(vterm.KeyFunction0) + i + 1)) - } - keyMap[tcell.KeyTAB] = directKey(vterm.KeyTab) - keyMap[tcell.KeyESC] = directKey(vterm.KeyEscape) - keyMap[tcell.KeyDEL] = directKey(vterm.KeyBackspace) -} - type Terminal struct { ui.Invalidatable closed bool cmd *exec.Cmd ctx *ui.Context - cursorPos vterm.Pos cursorShown bool destroyed bool - err error focus bool - pty *os.File - start chan interface{} - vterm *vterm.VTerm - - damage []vterm.Rect // protected by damageMutex - damageMutex sync.Mutex - writeMutex sync.Mutex - readMutex sync.Mutex - closeMutex sync.Mutex + vterm *tcellterm.Terminal + running bool OnClose func(err error) OnEvent func(event tcell.Event) bool @@ -120,115 +34,56 @@ func NewTerminal(cmd *exec.Cmd) (*Terminal, error) { cursorShown: true, } term.cmd = cmd - term.vterm = vterm.New(24, 80) - term.vterm.SetUTF8(true) - term.start = make(chan interface{}) - screen := term.vterm.ObtainScreen() - go func() { - defer logging.PanicHandler() - - <-term.start - buf := make([]byte, 4096) - for { - n, err := term.pty.Read(buf) - if err != nil || term.closed { - // These are generally benine errors when the process exits - term.Close(nil) - return - } - term.writeMutex.Lock() - _, err = term.vterm.Write(buf[:n]) - term.writeMutex.Unlock() - if err != nil { - term.Close(err) - return - } - screen.Flush() - term.flushTerminal() - term.invalidate() - } - }() - screen.OnDamage = term.onDamage - screen.OnMoveCursor = term.onMoveCursor - screen.OnSetTermProp = term.onSetTermProp - screen.EnableAltScreen(true) - screen.Reset(true) + term.vterm = tcellterm.New() return term, nil } -func (term *Terminal) flushTerminal() { - buf := make([]byte, 4096) - for { - term.readMutex.Lock() - n, err := term.vterm.Read(buf) - term.readMutex.Unlock() - if err != nil { - term.Close(err) - return - } - if n == 0 { - break - } - _, err = term.pty.Write(buf[:n]) - if err != nil { - term.Close(err) - return - } - } -} - func (term *Terminal) Close(err error) { - term.closeMutex.Lock() - defer term.closeMutex.Unlock() - if term.closed { return } - term.err = err - if term.pty != nil { - term.pty.Close() - term.pty = nil - } + // Stop receiving events + term.vterm.Unwatch(term) if term.cmd != nil && term.cmd.Process != nil { err := term.cmd.Process.Kill() if err != nil { logging.Warnf("failed to kill process: %v", err) } - err = term.cmd.Wait() + // Race condition here, check if cmd exists. If process exits + // fast, this could by nil and panic + if term.cmd != nil { + err = term.cmd.Wait() + } if err != nil { logging.Warnf("failed for wait for process to terminate: %v", err) } term.cmd = nil } + if term.vterm != nil { + term.vterm.Close() + } if !term.closed && term.OnClose != nil { term.OnClose(err) } - term.closed = true term.ctx.HideCursor() + term.closed = true } func (term *Terminal) Destroy() { if term.destroyed { return } - if term.vterm != nil { - term.vterm.Close() - term.vterm = nil - } if term.ctx != nil { term.ctx.HideCursor() } + // If we destroy, we don't want to call the OnClose callback + term.OnClose = nil + term.Close(nil) + term.vterm = nil term.destroyed = true } func (term *Terminal) Invalidate() { - if term.vterm != nil { - width, height := term.vterm.Size() - rect := vterm.NewRect(0, width, 0, height) - term.damageMutex.Lock() - term.damage = append(term.damage, *rect) - term.damageMutex.Unlock() - } term.invalidate() } @@ -240,104 +95,44 @@ func (term *Terminal) Draw(ctx *ui.Context) { if term.destroyed { return } - term.ctx = ctx // gross - - if !term.closed { - winsize := pty.Winsize{ - Cols: uint16(ctx.Width()), - Rows: uint16(ctx.Height()), - } - if winsize.Cols == 0 || winsize.Rows == 0 || term.cmd == nil { - return - } - - if term.pty == nil { - term.vterm.SetSize(ctx.Height(), ctx.Width()) - - term.closeMutex.Lock() - if term.cmd == nil { - term.closeMutex.Unlock() - return - } - tty, err := pty.StartWithAttrs(term.cmd, &winsize, &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 1}) - term.closeMutex.Unlock() - - term.pty = tty - if err != nil { + term.vterm.SetView(ctx.View()) + if !term.running && !term.closed && term.cmd != nil { + go func() { + defer logging.PanicHandler() + term.vterm.Watch(term) + attr := &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 1} + if err := term.vterm.RunWithAttrs(term.cmd, attr); err != nil { + logging.Errorf("error running terminal: %w", err) term.Close(err) + term.running = false return } - term.start <- nil - if term.OnStart != nil { - term.OnStart() - } - } - - ws, err := pty.GetsizeFull(term.pty) - if err != nil { - return - } - rows := int(ws.Rows) - cols := int(ws.Cols) - - if ctx.Width() != cols || ctx.Height() != rows { - term.writeMutex.Lock() - err := pty.Setsize(term.pty, &winsize) - if err != nil { - logging.Warnf("failed to set terminal size: %v", err) + term.running = false + term.Close(nil) + }() + for { + if term.cmd.Process != nil { + term.running = true + break } - term.vterm.SetSize(ctx.Height(), ctx.Width()) - term.writeMutex.Unlock() - rect := vterm.NewRect(0, ctx.Width(), 0, ctx.Height()) - term.damageMutex.Lock() - term.damage = append(term.damage, *rect) - term.damageMutex.Unlock() - return } - } - - screen := term.vterm.ObtainScreen() - - type coords struct { - x int - y int - } - - // naive optimization - visited := make(map[coords]interface{}) - - term.damageMutex.Lock() - for _, rect := range term.damage { - for x := rect.StartCol(); x < rect.EndCol() && x < ctx.Width(); x += 1 { - for y := rect.StartRow(); y < rect.EndRow() && y < ctx.Height(); y += 1 { - - coords := coords{x, y} - if _, ok := visited[coords]; ok { - continue - } - visited[coords] = nil - - cell, err := screen.GetCellAt(y, x) - if err != nil { - continue - } - style := term.styleFromCell(cell) - ctx.Printf(x, y, style, "%s", string(cell.Chars())) - } + if term.OnStart != nil { + term.OnStart() } } + term.draw() +} - term.damage = nil - term.damageMutex.Unlock() - +func (term *Terminal) draw() { + term.vterm.Draw() if term.focus && !term.closed { if !term.cursorShown { - ctx.HideCursor() + term.ctx.HideCursor() } else { - state := term.vterm.ObtainState() - row, col := state.GetCursorPos() - ctx.SetCursor(col, row) + _, x, y, style := term.vterm.GetCursor() + term.ctx.SetCursor(x, y) + term.ctx.SetCursorStyle(style) } } } @@ -364,29 +159,38 @@ func (term *Terminal) Focus(focus bool) { if !term.focus { term.ctx.HideCursor() } else { - state := term.vterm.ObtainState() - row, col := state.GetCursorPos() - term.ctx.SetCursor(col, row) - term.Invalidate() + _, x, y, style := term.vterm.GetCursor() + term.ctx.SetCursor(x, y) + term.ctx.SetCursorStyle(style) + term.invalidate() } } } -func convertMods(mods tcell.ModMask) vterm.Modifier { - var ( - ret uint = 0 - mask uint = uint(mods) - ) - if mask&uint(tcell.ModShift) > 0 { - ret |= uint(vterm.ModShift) - } - if mask&uint(tcell.ModCtrl) > 0 { - ret |= uint(vterm.ModCtrl) +// HandleEvent is used to watch the underlying terminal events +func (term *Terminal) HandleEvent(ev tcell.Event) bool { + if term.closed || term.destroyed { + return false } - if mask&uint(tcell.ModAlt) > 0 { - ret |= uint(vterm.ModAlt) + switch ev := ev.(type) { + case *views.EventWidgetContent: + // Draw here for performance improvement. We call draw again in + // the main Draw, but tcell-term only draws dirty cells, so it + // won't be too much extra CPU there. Drawing there is needed + // for certain msgviews, particularly if the pager command + // exits. + term.draw() + // Perform a tcell screen.Show() to show our updates + // immediately + term.ctx.Show() + term.invalidate() + return true + case *tcellterm.EventTitle: + if term.OnTitle != nil { + term.OnTitle(ev.Title()) + } } - return vterm.Modifier(ret) + return false } func (term *Terminal) Event(event tcell.Event) bool { @@ -398,107 +202,5 @@ func (term *Terminal) Event(event tcell.Event) bool { if term.closed { return false } - if event, ok := event.(*tcell.EventKey); ok { - if event.Key() == tcell.KeyRune { - term.vterm.KeyboardUnichar( - event.Rune(), convertMods(event.Modifiers())) - } else if key, ok := keyMap[event.Key()]; ok { - switch { - case key.Key == vterm.KeyNone: - term.vterm.KeyboardUnichar( - key.Rune, key.Mod) - case key.Mod == vterm.ModNone: - term.vterm.KeyboardKey(key.Key, - convertMods(event.Modifiers())) - default: - term.vterm.KeyboardKey(key.Key, key.Mod) - } - } - term.flushTerminal() - } - return false -} - -func (term *Terminal) styleFromCell(cell *vterm.ScreenCell) tcell.Style { - style := tcell.StyleDefault - - background := cell.Bg() - foreground := cell.Fg() - - var ( - bg tcell.Color - fg tcell.Color - ) - switch { - case background.IsDefaultBg(): - bg = tcell.ColorDefault - case background.IsIndexed(): - bg = tcell.Color(tcell.PaletteColor(int(background.GetIndex()))) - case background.IsRgb(): - r, g, b := background.GetRGB() - bg = tcell.NewRGBColor(int32(r), int32(g), int32(b)) - } - switch { - case foreground.IsDefaultFg(): - fg = tcell.ColorDefault - case foreground.IsIndexed(): - fg = tcell.Color(tcell.PaletteColor(int(foreground.GetIndex()))) - case foreground.IsRgb(): - r, g, b := foreground.GetRGB() - fg = tcell.NewRGBColor(int32(r), int32(g), int32(b)) - } - - style = style.Background(bg).Foreground(fg) - attrs := cell.Attrs() - - if attrs.Bold != 0 { - style = style.Bold(true) - } - if attrs.Italic != 0 { - style = style.Italic(true) - } - if attrs.Underline != 0 { - style = style.Underline(true) - } - if attrs.Blink != 0 { - style = style.Blink(true) - } - if attrs.Reverse != 0 { - style = style.Reverse(true) - } - return style -} - -func (term *Terminal) onDamage(rect *vterm.Rect) int { - term.damageMutex.Lock() - term.damage = append(term.damage, *rect) - term.damageMutex.Unlock() - term.invalidate() - return 1 -} - -func (term *Terminal) onMoveCursor(old *vterm.Pos, - pos *vterm.Pos, visible bool, -) int { - rows, cols, _ := pty.Getsize(term.pty) - if pos.Row() >= rows || pos.Col() >= cols { - return 1 - } - - term.cursorPos = *pos - term.invalidate() - return 1 -} - -func (term *Terminal) onSetTermProp(prop int, val *vterm.VTermValue) int { - switch prop { - case vterm.VTERM_PROP_TITLE: - if term.OnTitle != nil { - term.OnTitle(val.String) - } - case vterm.VTERM_PROP_CURSORVISIBLE: - term.cursorShown = val.Boolean - term.invalidate() - } - return 1 + return term.vterm.HandleEvent(event) } |