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:
parent
372d19448e
commit
b23d99cd8d
12
go.mod
Normal file
12
go.mod
Normal 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
22
go.sum
Normal 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=
|
21
terminal/tcell/cell_options.go
Normal file
21
terminal/tcell/cell_options.go
Normal 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
|
||||||
|
}
|
35
terminal/tcell/cell_options_test.go
Normal file
35
terminal/tcell/cell_options_test.go
Normal 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
166
terminal/tcell/event.go
Normal 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
136
terminal/tcell/tcell.go
Normal 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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user