aerc/widgets/terminal.go

478 lines
11 KiB
Go
Raw Normal View History

2019-03-17 18:02:33 +00:00
package widgets
import (
"os"
"os/exec"
widgets/terminal: fix damage race condition Terminal.damage is accessed when drawing and when invalidating the widget. For this reason we need to protect it with a mutex. This seems to fix various damage issues I've been experiencing (where some regions of the terminal weren't correctly repainted). Race detector trace: Read at 0x00c0000c6670 by main goroutine: git.sr.ht/~sircmpwn/aerc/widgets.(*Terminal).Draw() /home/simon/src/aerc/widgets/terminal.go:292 +0x191 git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/widgets.(*MessageViewer).Draw() /home/simon/src/aerc/widgets/msgviewer.go:231 +0x253 git.sr.ht/~sircmpwn/aerc/lib/ui.(*TabContent).Draw() /home/simon/src/aerc/lib/ui/tab.go:124 +0x12e git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Draw() /home/simon/src/aerc/widgets/aerc.go:95 +0x5a git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick() /home/simon/src/aerc/lib/ui/ui.go:93 +0x1dd main.main() /home/simon/src/aerc/aerc.go:105 +0x539 Previous write at 0x00c0000c6670 by goroutine 37: git.sr.ht/~sircmpwn/aerc/widgets.(*Terminal).onDamage-fm() /home/simon/src/aerc/widgets/terminal.go:429 +0x131 git.sr.ht/~sircmpwn/go-libvterm._go_handle_damage() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:481 +0xf9 git.sr.ht/~sircmpwn/go-libvterm._cgoexpwrap_5e22200b58b7__go_handle_damage() _cgo_gotypes.go:731 +0x58 runtime.call32() /usr/lib/go/src/runtime/asm_amd64.s:519 +0x3a git.sr.ht/~sircmpwn/go-libvterm.(*VTerm).Write.func1() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:329 +0x9d git.sr.ht/~sircmpwn/go-libvterm.(*VTerm).Write() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:329 +0x7f git.sr.ht/~sircmpwn/aerc/widgets.NewTerminal.func1() /home/simon/src/aerc/widgets/terminal.go:131 +0x18c Goroutine 37 (running) created at: git.sr.ht/~sircmpwn/aerc/widgets.NewTerminal() /home/simon/src/aerc/widgets/terminal.go:121 +0x23f git.sr.ht/~sircmpwn/aerc/widgets.NewMessageViewer() /home/simon/src/aerc/widgets/msgviewer.go:147 +0xfbe git.sr.ht/~sircmpwn/aerc/commands/account.ViewMessage() /home/simon/src/aerc/commands/account/view-message.go:26 +0x4a4 git.sr.ht/~sircmpwn/aerc/commands.(*Commands).ExecuteCommand() /home/simon/src/aerc/commands/commands.go:47 +0x1f0 main.main.func1() /home/simon/src/aerc/aerc.go:76 +0x205 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).BeginExCommand.func1() /home/simon/src/aerc/widgets/aerc.go:262 +0x89 git.sr.ht/~sircmpwn/aerc/widgets.(*ExLine).Event() /home/simon/src/aerc/widgets/exline.go:47 +0x222 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event() /home/simon/src/aerc/widgets/aerc.go:133 +0x83c git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).simulate() /home/simon/src/aerc/widgets/aerc.go:126 +0x12a git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event() /home/simon/src/aerc/widgets/aerc.go:148 +0x766 git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick() /home/simon/src/aerc/lib/ui/ui.go:86 +0x11b main.main() /home/simon/src/aerc/aerc.go:105 +0x539
2019-05-19 11:40:05 +00:00
"sync"
"syscall"
2019-03-17 18:02:33 +00:00
"git.sr.ht/~rjarry/aerc/lib/ui"
2019-03-17 18:02:33 +00:00
"github.com/creack/pty"
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
vterm "github.com/ddevault/go-libvterm"
"github.com/gdamore/tcell/v2"
2019-03-17 18:02:33 +00:00
)
2019-03-21 23:50:54 +00:00
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)
2021-02-06 08:14:24 +00:00
keyMap[tcell.KeyBacktab] = keyMod(vterm.KeyTab, vterm.ModShift)
2019-03-21 23:50:54 +00:00
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))
2019-03-21 23:50:54 +00:00
}
keyMap[tcell.KeyTAB] = directKey(vterm.KeyTab)
keyMap[tcell.KeyESC] = directKey(vterm.KeyEscape)
keyMap[tcell.KeyDEL] = directKey(vterm.KeyBackspace)
}
2019-03-17 18:02:33 +00:00
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
2019-03-17 21:08:54 +00:00
2019-06-01 15:28:29 +00:00
damage []vterm.Rect // protected by damageMutex
damageMutex sync.Mutex
writeMutex sync.Mutex
widgets/terminal: fix damage race condition Terminal.damage is accessed when drawing and when invalidating the widget. For this reason we need to protect it with a mutex. This seems to fix various damage issues I've been experiencing (where some regions of the terminal weren't correctly repainted). Race detector trace: Read at 0x00c0000c6670 by main goroutine: git.sr.ht/~sircmpwn/aerc/widgets.(*Terminal).Draw() /home/simon/src/aerc/widgets/terminal.go:292 +0x191 git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/widgets.(*MessageViewer).Draw() /home/simon/src/aerc/widgets/msgviewer.go:231 +0x253 git.sr.ht/~sircmpwn/aerc/lib/ui.(*TabContent).Draw() /home/simon/src/aerc/lib/ui/tab.go:124 +0x12e git.sr.ht/~sircmpwn/aerc/lib/ui.(*Grid).Draw() /home/simon/src/aerc/lib/ui/grid.go:117 +0x575 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Draw() /home/simon/src/aerc/widgets/aerc.go:95 +0x5a git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick() /home/simon/src/aerc/lib/ui/ui.go:93 +0x1dd main.main() /home/simon/src/aerc/aerc.go:105 +0x539 Previous write at 0x00c0000c6670 by goroutine 37: git.sr.ht/~sircmpwn/aerc/widgets.(*Terminal).onDamage-fm() /home/simon/src/aerc/widgets/terminal.go:429 +0x131 git.sr.ht/~sircmpwn/go-libvterm._go_handle_damage() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:481 +0xf9 git.sr.ht/~sircmpwn/go-libvterm._cgoexpwrap_5e22200b58b7__go_handle_damage() _cgo_gotypes.go:731 +0x58 runtime.call32() /usr/lib/go/src/runtime/asm_amd64.s:519 +0x3a git.sr.ht/~sircmpwn/go-libvterm.(*VTerm).Write.func1() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:329 +0x9d git.sr.ht/~sircmpwn/go-libvterm.(*VTerm).Write() /home/simon/go/pkg/mod/git.sr.ht/~sircmpwn/go-libvterm@v0.0.0-20190421201021-3184f6f13687/vterm.go:329 +0x7f git.sr.ht/~sircmpwn/aerc/widgets.NewTerminal.func1() /home/simon/src/aerc/widgets/terminal.go:131 +0x18c Goroutine 37 (running) created at: git.sr.ht/~sircmpwn/aerc/widgets.NewTerminal() /home/simon/src/aerc/widgets/terminal.go:121 +0x23f git.sr.ht/~sircmpwn/aerc/widgets.NewMessageViewer() /home/simon/src/aerc/widgets/msgviewer.go:147 +0xfbe git.sr.ht/~sircmpwn/aerc/commands/account.ViewMessage() /home/simon/src/aerc/commands/account/view-message.go:26 +0x4a4 git.sr.ht/~sircmpwn/aerc/commands.(*Commands).ExecuteCommand() /home/simon/src/aerc/commands/commands.go:47 +0x1f0 main.main.func1() /home/simon/src/aerc/aerc.go:76 +0x205 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).BeginExCommand.func1() /home/simon/src/aerc/widgets/aerc.go:262 +0x89 git.sr.ht/~sircmpwn/aerc/widgets.(*ExLine).Event() /home/simon/src/aerc/widgets/exline.go:47 +0x222 git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event() /home/simon/src/aerc/widgets/aerc.go:133 +0x83c git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).simulate() /home/simon/src/aerc/widgets/aerc.go:126 +0x12a git.sr.ht/~sircmpwn/aerc/widgets.(*Aerc).Event() /home/simon/src/aerc/widgets/aerc.go:148 +0x766 git.sr.ht/~sircmpwn/aerc/lib/ui.(*UI).Tick() /home/simon/src/aerc/lib/ui/ui.go:86 +0x11b main.main() /home/simon/src/aerc/aerc.go:105 +0x539
2019-05-19 11:40:05 +00:00
2019-03-17 21:23:53 +00:00
OnClose func(err error)
OnEvent func(event tcell.Event) bool
2019-03-30 15:58:24 +00:00
OnStart func()
2019-03-17 21:08:54 +00:00
OnTitle func(title string)
2019-03-17 18:02:33 +00:00
}
func NewTerminal(cmd *exec.Cmd) (*Terminal, error) {
term := &Terminal{
cursorShown: true,
}
2019-03-17 18:02:33 +00:00
term.cmd = cmd
term.vterm = vterm.New(24, 80)
2019-03-17 18:02:33 +00:00
term.vterm.SetUTF8(true)
term.start = make(chan interface{})
screen := term.vterm.ObtainScreen()
2019-03-17 18:02:33 +00:00
go func() {
<-term.start
buf := make([]byte, 4096)
2019-03-17 18:02:33 +00:00
for {
n, err := term.pty.Read(buf)
2019-03-22 01:07:49 +00:00
if err != nil || term.closed {
2019-03-21 23:50:54 +00:00
// These are generally benine errors when the process exits
term.Close(nil)
return
2019-03-17 18:02:33 +00:00
}
term.writeMutex.Lock()
2019-03-17 18:02:33 +00:00
n, err = term.vterm.Write(buf[:n])
term.writeMutex.Unlock()
2019-03-17 18:02:33 +00:00
if err != nil {
2019-03-17 21:23:53 +00:00
term.Close(err)
2019-03-21 23:50:54 +00:00
return
2019-03-17 18:02:33 +00:00
}
screen.Flush()
term.flushTerminal()
2019-04-15 20:07:05 +00:00
term.invalidate()
2019-03-17 18:02:33 +00:00
}
}()
screen.OnDamage = term.onDamage
screen.OnMoveCursor = term.onMoveCursor
2019-03-17 21:08:54 +00:00
screen.OnSetTermProp = term.onSetTermProp
2019-03-22 01:24:23 +00:00
screen.EnableAltScreen(true)
2019-03-17 18:02:33 +00:00
screen.Reset(true)
return term, nil
}
2019-03-21 23:50:54 +00:00
func (term *Terminal) flushTerminal() {
buf := make([]byte, 4096)
2019-03-21 23:50:54 +00:00
for {
n, err := term.vterm.Read(buf)
if err != nil {
term.Close(err)
return
}
if n == 0 {
break
}
n, err = term.pty.Write(buf[:n])
if err != nil {
term.Close(err)
return
}
}
}
2019-03-17 21:23:53 +00:00
func (term *Terminal) Close(err error) {
2019-03-30 15:58:24 +00:00
if term.closed {
return
}
2019-03-17 21:23:53 +00:00
term.err = err
if term.pty != nil {
term.pty.Close()
term.pty = nil
}
if term.cmd != nil && term.cmd.Process != nil {
term.cmd.Process.Kill()
term.cmd.Wait()
2019-03-17 21:23:53 +00:00
term.cmd = nil
}
if !term.closed && term.OnClose != nil {
term.OnClose(err)
2019-03-17 18:02:33 +00:00
}
term.closed = true
term.ctx.HideCursor()
2019-03-17 18:02:33 +00:00
}
2019-03-30 20:29:52 +00:00
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()
}
term.destroyed = true
}
2019-03-17 18:02:33 +00:00
func (term *Terminal) Invalidate() {
2019-04-15 20:07:05 +00:00
if term.vterm != nil {
width, height := term.vterm.Size()
rect := vterm.NewRect(0, width, 0, height)
term.damageMutex.Lock()
2019-04-15 20:07:05 +00:00
term.damage = append(term.damage, *rect)
term.damageMutex.Unlock()
2019-04-15 20:07:05 +00:00
}
term.invalidate()
}
func (term *Terminal) invalidate() {
term.DoInvalidate(term)
2019-03-17 18:02:33 +00:00
}
func (term *Terminal) Draw(ctx *ui.Context) {
2019-03-30 20:29:52 +00:00
if term.destroyed {
2019-03-17 21:23:53 +00:00
return
}
2019-03-30 20:29:52 +00:00
term.ctx = ctx // gross
2019-03-22 01:23:30 +00:00
2019-03-30 20:29:52 +00:00
if !term.closed {
winsize := pty.Winsize{
Cols: uint16(ctx.Width()),
Rows: uint16(ctx.Height()),
}
2019-08-12 00:51:45 +00:00
if winsize.Cols == 0 || winsize.Rows == 0 || term.cmd == nil {
2019-07-13 15:13:48 +00:00
return
}
2019-03-30 20:29:52 +00:00
if term.pty == nil {
term.vterm.SetSize(ctx.Height(), ctx.Width())
tty, err := pty.StartWithAttrs(term.cmd, &winsize, &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 1})
2019-03-30 20:29:52 +00:00
term.pty = tty
if err != nil {
term.Close(err)
return
}
term.start <- nil
if term.OnStart != nil {
term.OnStart()
}
}
rows, cols, err := pty.Getsize(term.pty)
if err != nil {
return
}
2019-03-30 20:29:52 +00:00
if ctx.Width() != cols || ctx.Height() != rows {
term.writeMutex.Lock()
2019-03-30 20:29:52 +00:00
pty.Setsize(term.pty, &winsize)
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()
2019-03-30 20:29:52 +00:00
return
2019-03-30 15:58:24 +00:00
}
}
2019-03-17 18:02:33 +00:00
screen := term.vterm.ObtainScreen()
type coords struct {
x int
y int
}
// naive optimization
visited := make(map[coords]interface{})
term.damageMutex.Lock()
2019-03-17 18:02:33 +00:00
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 {
2019-03-17 18:02:33 +00:00
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)
2019-03-17 18:02:33 +00:00
ctx.Printf(x, y, style, "%s", string(cell.Chars()))
}
}
}
term.damage = nil
term.damageMutex.Unlock()
2019-03-30 20:29:52 +00:00
if term.focus && !term.closed {
if !term.cursorShown {
ctx.HideCursor()
} else {
state := term.vterm.ObtainState()
row, col := state.GetCursorPos()
ctx.SetCursor(col, row)
}
}
2019-03-17 18:02:33 +00:00
}
func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
switch event := event.(type) {
case *tcell.EventMouse:
if term.OnEvent != nil {
if term.OnEvent(event) {
return
}
}
if term.closed {
return
}
}
}
2019-03-17 18:02:33 +00:00
func (term *Terminal) Focus(focus bool) {
2019-03-30 15:58:24 +00:00
if term.closed {
return
}
2019-03-17 18:02:33 +00:00
term.focus = focus
2019-03-22 01:34:12 +00:00
if term.ctx != nil {
if !term.focus {
term.ctx.HideCursor()
} else {
state := term.vterm.ObtainState()
row, col := state.GetCursorPos()
term.ctx.SetCursor(col, row)
term.Invalidate()
2019-03-22 01:34:12 +00:00
}
}
2019-03-17 18:02:33 +00:00
}
2019-03-21 23:50:54 +00:00
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)
}
if mask&uint(tcell.ModAlt) > 0 {
ret |= uint(vterm.ModAlt)
}
return vterm.Modifier(ret)
}
2019-03-17 18:02:33 +00:00
func (term *Terminal) Event(event tcell.Event) bool {
if term.OnEvent != nil {
if term.OnEvent(event) {
return true
}
}
2019-03-30 15:58:24 +00:00
if term.closed {
return false
}
2019-03-21 23:50:54 +00:00
switch event := event.(type) {
case *tcell.EventKey:
if event.Key() == tcell.KeyRune {
term.vterm.KeyboardUnichar(
event.Rune(), convertMods(event.Modifiers()))
} else {
if key, ok := keyMap[event.Key()]; ok {
if key.Key == vterm.KeyNone {
term.vterm.KeyboardUnichar(
key.Rune, key.Mod)
} else if key.Mod == vterm.ModNone {
term.vterm.KeyboardKey(key.Key,
convertMods(event.Modifiers()))
} else {
term.vterm.KeyboardKey(key.Key, key.Mod)
}
}
}
term.flushTerminal()
}
2019-03-17 18:02:33 +00:00
return false
}
func (term *Terminal) styleFromCell(cell *vterm.ScreenCell) tcell.Style {
style := tcell.StyleDefault
2019-03-17 18:02:33 +00:00
background := cell.Bg()
foreground := cell.Fg()
var (
bg tcell.Color
fg tcell.Color
)
if background.IsDefaultBg() {
bg = tcell.ColorDefault
} else if background.IsIndexed() {
bg = tcell.Color(tcell.PaletteColor(int(background.GetIndex())))
} else if background.IsRgb() {
r, g, b := background.GetRGB()
bg = tcell.NewRGBColor(int32(r), int32(g), int32(b))
}
if foreground.IsDefaultFg() {
fg = tcell.ColorDefault
} else if foreground.IsIndexed() {
fg = tcell.Color(tcell.PaletteColor(int(foreground.GetIndex())))
} else if foreground.IsRgb() {
r, g, b := foreground.GetRGB()
fg = tcell.NewRGBColor(int32(r), int32(g), int32(b))
}
style = style.Background(bg).Foreground(fg)
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
attrs := cell.Attrs()
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
if attrs.Bold != 0 {
style = style.Bold(true)
}
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
if attrs.Italic != 0 {
style = style.Italic(true)
}
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
if attrs.Underline != 0 {
style = style.Underline(true)
}
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
if attrs.Blink != 0 {
style = style.Blink(true)
}
widgets/terminal: Only call vterm.ScreenCell.Attrs once in styleFromCell This fixes a substantial performance issue when scrolling emails with long/complicated contents, where scrolling down a single line can take something like hundreds of ms before the screen is updated to reflect the scroll. It's really bad if the email has lots of columns, e.g. like if it's an html email that was passed through a filter (w3m, etc) to render it. Using pprof, I found that the multiple calls to vterm.ScreenCell.Attrs() in styleFromCell were really really expensive. This patch replaces them with a single call. Here's a before and after with a simple, but very manual test of opening a large email with contents that went through a w3m filter and continuously scrolling up and down over and over for ~30 seconds: *** Before: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartSwitcher.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw ----------------------------------------------------------+------------- 28.25s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.PartViewer.Draw 0 0% 99.94% 28.25s 82.31% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 19.23s 68.07% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 6.04s 21.38% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.Screen.GetCellAt 1.38s 4.88% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2flib..z2fui.Context.Printf 0.62s 2.19% | runtime.mapassign 0.43s 1.52% | runtime.mapaccess2 0.20s 0.71% | runtime.newobject 0.19s 0.67% | runtime.callers (inline) 0.07s 0.25% | runtime.makeslice 0.07s 0.25% | runtime.mallocgc ----------------------------------------------------------+------------- 19.23s 100% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.Draw 0 0% 99.94% 19.23s 56.03% | git.x2esr.x2eht..z2f..z7esircmpwn..z2faerc..z2fwidgets.Terminal.styleFromCell 19.21s 99.90% | github.x2ecom..z2fddevault..z2fgo..z2dlibvterm.ScreenCell.Attrs *** After: ----------------------------------------------------------+------------- flat flat% sum% cum cum% calls calls% + context ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.NewPos 0.25s 80.65% | runtime.callers (inline) 0.04s 12.90% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.40s 100% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 0 0% 99.87% 8.40s 36.11% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 7.14s 85.00% | github.x2ecom/ddevault/go-libvterm._cgoCheckPointer 0.54s 6.43% | runtime.callers (inline) 0.35s 4.17% | runtime.exitsyscall 0.11s 1.31% | runtime.deferprocStack 0.07s 0.83% | doentersyscall 0.07s 0.83% | runtime.getg 0.05s 0.6% | runtime.casgstatus 0.03s 0.36% | _init 0.03s 0.36% | runtime.gomcache (inline) ----------------------------------------------------------+------------- 8.46s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.Draw 0 0% 99.87% 8.46s 36.37% | github.x2ecom/ddevault/go-libvterm.Screen.GetCellAt 8.40s 99.29% | github.x2ecom/ddevault/go-libvterm.Screen.GetCell 0.06s 0.71% | runtime.callers (inline) ----------------------------------------------------------+------------- 0.31s 100% | git.x2esr.x2eht/~sircmpwn/aerc/widgets.Terminal.styleFromCell 0 0% 99.87% 0.31s 1.33% | github.x2ecom/ddevault/go-libvterm.ScreenCell.Attrs )
2021-04-12 23:24:35 +00:00
if attrs.Reverse != 0 {
style = style.Reverse(true)
}
2019-03-17 18:02:33 +00:00
return style
}
func (term *Terminal) onDamage(rect *vterm.Rect) int {
term.damageMutex.Lock()
2019-03-17 18:02:33 +00:00
term.damage = append(term.damage, *rect)
term.damageMutex.Unlock()
2019-04-15 20:07:05 +00:00
term.invalidate()
2019-03-17 18:02:33 +00:00
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
2019-04-15 20:07:05 +00:00
term.invalidate()
2019-03-17 18:02:33 +00:00
return 1
}
2019-03-17 21:08:54 +00:00
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
2019-04-15 20:07:05 +00:00
term.invalidate()
2019-03-17 21:08:54 +00:00
}
return 1
}