diff options
Diffstat (limited to 'widgets/terminal.go')
-rw-r--r-- | widgets/terminal.go | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/widgets/terminal.go b/widgets/terminal.go new file mode 100644 index 00000000..4cf7d9aa --- /dev/null +++ b/widgets/terminal.go @@ -0,0 +1,179 @@ +package widgets + +import ( + "os" + "os/exec" + + "git.sr.ht/~sircmpwn/aerc2/lib/ui" + + "git.sr.ht/~sircmpwn/go-libvterm" + "github.com/gdamore/tcell" + "github.com/kr/pty" +) + +type Terminal struct { + closed bool + cmd *exec.Cmd + ctx *ui.Context + cursorPos vterm.Pos + cursorShown bool + damage []vterm.Rect + focus bool + onInvalidate func(d ui.Drawable) + pty *os.File + vterm *vterm.VTerm +} + +func NewTerminal(cmd *exec.Cmd) (*Terminal, error) { + term := &Terminal{} + term.cmd = cmd + tty, err := pty.Start(cmd) + if err != nil { + return nil, err + } + term.pty = tty + rows, cols, err := pty.Getsize(term.pty) + if err != nil { + return nil, err + } + term.vterm = vterm.New(rows, cols) + term.vterm.SetUTF8(true) + go func() { + buf := make([]byte, 2048) + for { + n, err := term.pty.Read(buf) + if err != nil { + term.Close() + } + n, err = term.vterm.Write(buf[:n]) + if err != nil { + term.Close() + } + term.Invalidate() + } + }() + screen := term.vterm.ObtainScreen() + screen.OnDamage = term.onDamage + screen.OnMoveCursor = term.onMoveCursor + screen.Reset(true) + return term, nil +} + +func (term *Terminal) Close() { + if term.closed { + return + } + term.closed = true + term.vterm.Close() + term.pty.Close() + term.cmd.Process.Kill() +} + +func (term *Terminal) OnInvalidate(cb func(d ui.Drawable)) { + term.onInvalidate = cb +} + +func (term *Terminal) Invalidate() { + if term.onInvalidate != nil { + term.onInvalidate(term) + } +} + +func (term *Terminal) Draw(ctx *ui.Context) { + term.ctx = ctx // gross + if term.closed { + return + } + + rows, cols, err := pty.Getsize(term.pty) + if err != nil { + return + } + if ctx.Width() != cols || ctx.Height() != rows { + winsize := pty.Winsize{ + Cols: uint16(ctx.Width()), + Rows: uint16(ctx.Height()), + } + pty.Setsize(term.pty, &winsize) + term.vterm.SetSize(ctx.Height(), ctx.Width()) + return + } + + screen := term.vterm.ObtainScreen() + screen.Flush() + + type coords struct { + x int + y int + } + + // naive optimization + visited := make(map[coords]interface{}) + + for _, rect := range term.damage { + for x := rect.StartCol(); x < rect.EndCol() && x < ctx.Width(); x += 1 { + + for y := rect.StartCol(); y < rect.EndCol() && 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 := styleFromCell(cell) + ctx.Printf(x, y, style, "%s", string(cell.Chars())) + } + } + } +} + +func (term *Terminal) Focus(focus bool) { + term.focus = focus + term.resetCursor() +} + +func (term *Terminal) Event(event tcell.Event) bool { + // TODO + return false +} + +func styleFromCell(cell *vterm.ScreenCell) tcell.Style { + background := cell.Bg() + br, bg, bb := background.GetRGB() + foreground := cell.Fg() + fr, fg, fb := foreground.GetRGB() + style := tcell.StyleDefault. + Background(tcell.NewRGBColor(int32(br), int32(bg), int32(bb))). + Foreground(tcell.NewRGBColor(int32(fr), int32(fg), int32(fb))) + return style +} + +func (term *Terminal) onDamage(rect *vterm.Rect) int { + term.damage = append(term.damage, *rect) + term.Invalidate() + return 1 +} + +func (term *Terminal) resetCursor() { + if term.ctx != nil && term.focus { + if !term.cursorShown { + term.ctx.HideCursor() + } else { + term.ctx.SetCursor(term.cursorPos.Col(), term.cursorPos.Row()) + } + } +} + +func (term *Terminal) onMoveCursor(old *vterm.Pos, + pos *vterm.Pos, visible bool) int { + + term.cursorShown = visible + term.cursorPos = *pos + term.resetCursor() + return 1 +} |