1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-28 13:48:51 +08:00

Add tcell dependency as another terminal backend

This commit is contained in:
kvnxiao 2020-02-14 18:00:45 -05:00
parent 372d19448e
commit b23d99cd8d
6 changed files with 392 additions and 0 deletions

12
go.mod Normal file
View File

@ -0,0 +1,12 @@
module github.com/mum4k/termdash
go 1.13
require (
github.com/gdamore/tcell v1.3.1-0.20200206054723-bac2bbc5b394
github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-runewidth v0.0.8
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect
golang.org/x/text v0.3.2 // indirect
)

22
go.sum Normal file
View File

@ -0,0 +1,22 @@
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 v1.3.1-0.20200206054723-bac2bbc5b394 h1:jpZN87sd1rKHwYDWlCaRzmZklWa35Ft+O8rBBWFLQJ0=
github.com/gdamore/tcell v1.3.1-0.20200206054723-bac2bbc5b394/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be h1:yzmWtPyxEUIKdZg4RcPq64MfS8NA6A5fNOJgYhpR9EQ=
github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -0,0 +1,21 @@
package tcell
import (
"github.com/gdamore/tcell"
"github.com/mum4k/termdash/cell"
)
// cellColor converts termdash cell color to the tcell format.
func cellColor(c cell.Color) tcell.Color {
return tcell.Color(int(c)&0x1ff) - 1
}
// cellOptsToStyle converts termdash cell color to the tcell format.
func cellOptsToStyle(opts *cell.Options) tcell.Style {
fg := cellColor(opts.FgColor)
bg := cellColor(opts.BgColor)
st := tcell.StyleDefault
st = st.Foreground(fg).Background(bg)
return st
}

View File

@ -0,0 +1,35 @@
package tcell
import (
"testing"
"github.com/gdamore/tcell"
"github.com/mum4k/termdash/cell"
)
func TestCellColor(t *testing.T) {
tests := []struct {
color cell.Color
want tcell.Color
}{
{cell.ColorDefault, tcell.ColorDefault},
{cell.ColorBlack, tcell.ColorBlack},
{cell.ColorRed, tcell.ColorMaroon},
{cell.ColorGreen, tcell.ColorGreen},
{cell.ColorYellow, tcell.ColorOlive},
{cell.ColorBlue, tcell.ColorNavy},
{cell.ColorMagenta, tcell.ColorPurple},
{cell.ColorCyan, tcell.ColorTeal},
{cell.ColorWhite, tcell.ColorSilver},
{cell.ColorNumber(42), tcell.Color(42)},
}
for _, tc := range tests {
t.Run(tc.color.String(), func(t *testing.T) {
got := cellColor(tc.color)
if got != tc.want {
t.Errorf("cellColor(%v) => got %v, want %v", tc.color, got, tc.want)
}
})
}
}

166
terminal/tcell/event.go Normal file
View File

@ -0,0 +1,166 @@
package tcell
import (
"image"
"github.com/gdamore/tcell"
"github.com/mum4k/termdash/keyboard"
"github.com/mum4k/termdash/mouse"
"github.com/mum4k/termdash/terminal/terminalapi"
)
// tcell representation of the space key
var tcellSpaceKey = tcell.Key(' ')
// tcell representation of the tilde key
var tcellTildeKey = tcell.Key('~')
// tcellToTd maps tcell key values to the termdash format.
var tcellToTd = map[tcell.Key]keyboard.Key{
tcellSpaceKey: keyboard.KeySpace,
tcell.KeyF1: keyboard.KeyF1,
tcell.KeyF2: keyboard.KeyF2,
tcell.KeyF3: keyboard.KeyF3,
tcell.KeyF4: keyboard.KeyF4,
tcell.KeyF5: keyboard.KeyF5,
tcell.KeyF6: keyboard.KeyF6,
tcell.KeyF7: keyboard.KeyF7,
tcell.KeyF8: keyboard.KeyF8,
tcell.KeyF9: keyboard.KeyF9,
tcell.KeyF10: keyboard.KeyF10,
tcell.KeyF11: keyboard.KeyF11,
tcell.KeyF12: keyboard.KeyF12,
tcell.KeyInsert: keyboard.KeyInsert,
tcell.KeyDelete: keyboard.KeyDelete,
tcell.KeyHome: keyboard.KeyHome,
tcell.KeyEnd: keyboard.KeyEnd,
tcell.KeyPgUp: keyboard.KeyPgUp,
tcell.KeyPgDn: keyboard.KeyPgDn,
tcell.KeyUp: keyboard.KeyArrowUp,
tcell.KeyDown: keyboard.KeyArrowDown,
tcell.KeyLeft: keyboard.KeyArrowLeft,
tcell.KeyRight: keyboard.KeyArrowRight,
tcell.KeyEnter: keyboard.KeyEnter,
tcellTildeKey: keyboard.KeyCtrlTilde,
tcell.KeyCtrlA: keyboard.KeyCtrlA,
tcell.KeyCtrlB: keyboard.KeyCtrlB,
tcell.KeyCtrlC: keyboard.KeyCtrlC,
tcell.KeyCtrlD: keyboard.KeyCtrlD,
tcell.KeyCtrlE: keyboard.KeyCtrlE,
tcell.KeyCtrlF: keyboard.KeyCtrlF,
tcell.KeyCtrlG: keyboard.KeyCtrlG,
tcell.KeyCtrlJ: keyboard.KeyCtrlJ,
tcell.KeyCtrlK: keyboard.KeyCtrlK,
tcell.KeyCtrlL: keyboard.KeyCtrlL,
tcell.KeyCtrlN: keyboard.KeyCtrlN,
tcell.KeyCtrlO: keyboard.KeyCtrlO,
tcell.KeyCtrlP: keyboard.KeyCtrlP,
tcell.KeyCtrlQ: keyboard.KeyCtrlQ,
tcell.KeyCtrlR: keyboard.KeyCtrlR,
tcell.KeyCtrlS: keyboard.KeyCtrlS,
tcell.KeyCtrlT: keyboard.KeyCtrlT,
tcell.KeyCtrlU: keyboard.KeyCtrlU,
tcell.KeyCtrlV: keyboard.KeyCtrlV,
tcell.KeyCtrlW: keyboard.KeyCtrlW,
tcell.KeyCtrlX: keyboard.KeyCtrlX,
tcell.KeyCtrlY: keyboard.KeyCtrlY,
tcell.KeyCtrlZ: keyboard.KeyCtrlZ,
tcell.KeyBackspace: keyboard.KeyBackspace,
tcell.KeyTab: keyboard.KeyTab,
tcell.KeyEscape: keyboard.KeyEsc,
tcell.KeyCtrlBackslash: keyboard.KeyCtrlBackslash,
tcell.KeyCtrlRightSq: keyboard.KeyCtrlRsqBracket,
tcell.KeyCtrlUnderscore: keyboard.KeyCtrlUnderscore,
tcell.KeyBackspace2: keyboard.KeyBackspace2,
}
// convKey converts a tcell keyboard event to the termdash format.
func convKey(event *tcell.EventKey) terminalapi.Event {
tcellKey := event.Key()
if tcellKey == tcell.KeyRune {
ch := event.Rune()
return &terminalapi.Keyboard{
Key: keyboard.Key(ch),
}
}
k, ok := tcellToTd[tcellKey]
if !ok {
return terminalapi.NewErrorf("unknown keyboard key '%v' in a keyboard event", tcellKey)
}
return &terminalapi.Keyboard{
Key: k,
}
}
// convMouse converts a tcell mouse event to the termdash format.
func convMouse(event *tcell.EventMouse) terminalapi.Event {
//var button mouse.Button
var button mouse.Button
x, y := event.Position()
// Get wheel events
tcellBtn := event.Buttons()
if tcellBtn&tcell.WheelUp != 0 {
button = mouse.ButtonWheelUp
} else if tcellBtn&tcell.WheelDown != 0 {
button = mouse.ButtonWheelDown
}
// Get button events
switch tcellBtn = event.Buttons(); tcellBtn {
case tcell.ButtonNone:
button = mouse.ButtonRelease
case tcell.Button1:
button = mouse.ButtonLeft
case tcell.Button2:
button = mouse.ButtonRight
case tcell.Button3:
button = mouse.ButtonMiddle
default:
return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn)
}
return &terminalapi.Mouse{
Position: image.Point{X: x, Y: y},
Button: button,
}
}
// convResize converts a tcell resize event to the termdash format.
func convResize(event *tcell.EventResize) terminalapi.Event {
w, h := event.Size()
size := image.Point{X: w, Y: h}
if size.X < 0 || size.Y < 0 {
return terminalapi.NewErrorf("terminal resized to negative size: %v", size)
}
return &terminalapi.Resize{
Size: size,
}
}
// toTermdashEvents converts a tcell event to the termdash event format.
func toTermdashEvents(event tcell.Event) []terminalapi.Event {
switch event := event.(type) {
case *tcell.EventInterrupt:
return []terminalapi.Event{
terminalapi.NewError("event type EventInterrupt isn't supported"),
}
case *tcell.EventKey:
return []terminalapi.Event{convKey(event)}
case *tcell.EventMouse:
return []terminalapi.Event{convMouse(event)}
case *tcell.EventResize:
return []terminalapi.Event{convResize(event)}
case *tcell.EventError:
return []terminalapi.Event{
terminalapi.NewErrorf("encountered tcell error event: %v", event),
}
default:
return []terminalapi.Event{
terminalapi.NewErrorf("unknown tcell event type: %v", event),
}
}
}

136
terminal/tcell/tcell.go Normal file
View File

@ -0,0 +1,136 @@
package tcell
import (
"context"
"image"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/internal/event/eventqueue"
"github.com/mum4k/termdash/terminal/terminalapi"
)
// Terminal provides input and output to a real terminal. Wraps the
// gdamore/tcell terminal implementation. This object is not thread-safe.
// Implements terminalapi.Terminal.
type Terminal struct {
// events is a queue of input events.
events *eventqueue.Unbound
// done gets closed when Close() is called.
done chan struct{}
screen tcell.Screen
}
// New returns a new tcell based Terminal.
// Call Close() when the terminal isn't required anymore.
func New() (*Terminal, error) {
// Enable full character set support for tcell
encoding.Register()
screen, err := tcell.NewScreen()
if err != nil {
return nil, err
}
t := &Terminal{
events: eventqueue.New(),
done: make(chan struct{}),
screen: screen,
}
if err = t.screen.Init(); err != nil {
return nil, err
}
defaultStyle := tcell.StyleDefault.
Foreground(tcell.ColorWhite).
Background(tcell.ColorBlack)
t.screen.EnableMouse()
t.screen.SetStyle(defaultStyle)
go t.pollEvents()
return t, nil
}
// Size implements terminalapi.Terminal.Size.
func (t *Terminal) Size() image.Point {
w, h := t.screen.Size()
return image.Point{
X: w,
Y: h,
}
}
// Clear implements terminalapi.Terminal.Clear.
func (t *Terminal) Clear(opts ...cell.Option) error {
o := cell.NewOptions(opts...)
st := cellOptsToStyle(o)
w, h := t.screen.Size()
for row := 0; row < h; row++ {
for col := 0; col < w; col++ {
t.screen.SetContent(col, row, ' ', nil, st)
}
}
return nil
}
// Flush implements terminalapi.Terminal.Flush.
func (t *Terminal) Flush() error {
t.screen.Show()
return nil
}
// SetCursor implements terminalapi.Terminal.SetCursor.
func (t *Terminal) SetCursor(p image.Point) {
t.screen.ShowCursor(p.X, p.Y)
}
// HideCursor implements terminalapi.Terminal.HideCursor.
func (t *Terminal) HideCursor() {
t.screen.HideCursor()
}
// SetCell implements terminalapi.Terminal.SetCell.
func (t *Terminal) SetCell(p image.Point, r rune, opts ...cell.Option) error {
o := cell.NewOptions(opts...)
st := cellOptsToStyle(o)
t.screen.SetContent(p.X, p.Y, r, nil, st)
return nil
}
// pollEvents polls and enqueues the input events.
func (t *Terminal) pollEvents() {
for {
select {
case <-t.done:
return
default:
}
events := toTermdashEvents(t.screen.PollEvent())
for _, ev := range events {
t.events.Push(ev)
}
}
}
// Event implements terminalapi.Terminal.Event.
func (t *Terminal) Event(ctx context.Context) terminalapi.Event {
ev := t.events.Pull(ctx)
if ev == nil {
return nil
}
return ev
}
// Close closes the terminal, should be called when the terminal isn't required
// anymore to return the screen to a sane state.
// Implements terminalapi.Terminal.Close.
func (t *Terminal) Close() {
close(t.done)
t.screen.Fini()
}