From b23d99cd8d5733faa84ec0b56df58b6e246e937f Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Fri, 14 Feb 2020 18:00:45 -0500 Subject: [PATCH 01/25] Add tcell dependency as another terminal backend --- go.mod | 12 ++ go.sum | 22 ++++ terminal/tcell/cell_options.go | 21 ++++ terminal/tcell/cell_options_test.go | 35 ++++++ terminal/tcell/event.go | 166 ++++++++++++++++++++++++++++ terminal/tcell/tcell.go | 136 +++++++++++++++++++++++ 6 files changed, 392 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 terminal/tcell/cell_options.go create mode 100644 terminal/tcell/cell_options_test.go create mode 100644 terminal/tcell/event.go create mode 100644 terminal/tcell/tcell.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ce0c929 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b4baf5d --- /dev/null +++ b/go.sum @@ -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= diff --git a/terminal/tcell/cell_options.go b/terminal/tcell/cell_options.go new file mode 100644 index 0000000..5a5dd60 --- /dev/null +++ b/terminal/tcell/cell_options.go @@ -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 +} diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go new file mode 100644 index 0000000..acbacdb --- /dev/null +++ b/terminal/tcell/cell_options_test.go @@ -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) + } + }) + } +} diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go new file mode 100644 index 0000000..d8ad96d --- /dev/null +++ b/terminal/tcell/event.go @@ -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), + } + } +} diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go new file mode 100644 index 0000000..c0fc932 --- /dev/null +++ b/terminal/tcell/tcell.go @@ -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() +} From 4fcdf48ecbb4ac9fc82e80878d65310259e91dc5 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Fri, 14 Feb 2020 19:23:10 -0500 Subject: [PATCH 02/25] Fix mouse events --- terminal/tcell/event.go | 28 ++-- terminal/tcell/event_test.go | 253 +++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 terminal/tcell/event_test.go diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index d8ad96d..d215331 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -12,9 +12,6 @@ import ( // 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, @@ -41,7 +38,6 @@ var tcellToTd = map[tcell.Key]keyboard.Key{ 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, @@ -72,6 +68,7 @@ var tcellToTd = map[tcell.Key]keyboard.Key{ tcell.KeyCtrlRightSq: keyboard.KeyCtrlRsqBracket, tcell.KeyCtrlUnderscore: keyboard.KeyCtrlUnderscore, tcell.KeyBackspace2: keyboard.KeyBackspace2, + tcell.KeyCtrlSpace: keyboard.KeyCtrlSpace, } // convKey converts a tcell keyboard event to the termdash format. @@ -97,19 +94,33 @@ func convKey(event *tcell.EventKey) terminalapi.Event { // 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() + + // tcell uses signed int16 for button masks, and negative values are invalid + if tcellBtn < 0 { + return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn) + } + + // Get wheel events if tcellBtn&tcell.WheelUp != 0 { button = mouse.ButtonWheelUp } else if tcellBtn&tcell.WheelDown != 0 { button = mouse.ButtonWheelDown } - // Get button events + // Return wheel event if found + if button > 0 { + return &terminalapi.Mouse{ + Position: image.Point{X: x, Y: y}, + Button: button, + } + } + + // Get only button events, not wheel events + tcellBtn &= tcell.ButtonMask(0xff) switch tcellBtn = event.Buttons(); tcellBtn { case tcell.ButtonNone: button = mouse.ButtonRelease @@ -120,7 +131,8 @@ func convMouse(event *tcell.EventMouse) terminalapi.Event { case tcell.Button3: button = mouse.ButtonMiddle default: - return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn) + // Do nothing, since tcell allows multiple buttons to be pressed at the same time + // Maybe refactor terminalapi to handle multiple mouse buttons being pressed at the same time (e.g. M1 + M2) } return &terminalapi.Mouse{ diff --git a/terminal/tcell/event_test.go b/terminal/tcell/event_test.go new file mode 100644 index 0000000..d10cf77 --- /dev/null +++ b/terminal/tcell/event_test.go @@ -0,0 +1,253 @@ +package tcell + +import ( + "errors" + "fmt" + "image" + "testing" + "time" + + "github.com/gdamore/tcell" + "github.com/kylelemons/godebug/pretty" + "github.com/mum4k/termdash/keyboard" + "github.com/mum4k/termdash/mouse" + "github.com/mum4k/termdash/terminal/terminalapi" +) + +type mockUnknownEvent struct { +} + +func (m *mockUnknownEvent) When() time.Time { + return time.Now() +} + +func TestToTermdashEvents(t *testing.T) { + tests := []struct { + desc string + event tcell.Event + want []terminalapi.Event + }{ + { + desc: "unknown event type", + event: &mockUnknownEvent{}, + want: []terminalapi.Event{ + terminalapi.NewError("unknown tcell event type: &{}"), + }, + }, + { + desc: "interrupts aren't supported", + event: tcell.NewEventInterrupt(nil), + want: []terminalapi.Event{ + terminalapi.NewError("event type EventInterrupt isn't supported"), + }, + }, + { + desc: "error event", + event: tcell.NewEventError(errors.New("error event")), + want: []terminalapi.Event{ + terminalapi.NewError("encountered tcell error event: error event"), + }, + }, + { + desc: "resize event", + event: tcell.NewEventResize(640, 480), + want: []terminalapi.Event{ + &terminalapi.Resize{ + Size: image.Point{X: 640, Y: 480}, + }, + }, + }, + { + desc: "resize event to a negative size", + event: tcell.NewEventResize(-1, -1), + want: []terminalapi.Event{ + terminalapi.NewError("terminal resized to negative size: (-1,-1)"), + }, + }, + { + desc: "mouse event", + event: tcell.NewEventMouse(100, 200, tcell.Button1, tcell.ModNone), + want: []terminalapi.Event{ + &terminalapi.Mouse{ + Position: image.Point{X: 100, Y: 200}, + Button: mouse.ButtonLeft, + }, + }, + }, + { + desc: "keyboard event", + event: tcell.NewEventKey(tcell.KeyF1, 0, tcell.ModNone), + want: []terminalapi.Event{ + &terminalapi.Keyboard{ + Key: keyboard.KeyF1, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got := toTermdashEvents(tc.event) + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("toTermdashEvents => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestMouseButtons(t *testing.T) { + tests := []struct { + btnMask tcell.ButtonMask + want mouse.Button + wantErr bool + }{ + {btnMask: -1, wantErr: true}, + {btnMask: tcell.Button1, want: mouse.ButtonLeft}, + {btnMask: tcell.Button3, want: mouse.ButtonMiddle}, + {btnMask: tcell.Button2, want: mouse.ButtonRight}, + {btnMask: tcell.ButtonNone, want: mouse.ButtonRelease}, + {btnMask: tcell.WheelUp, want: mouse.ButtonWheelUp}, + {btnMask: tcell.WheelDown, want: mouse.ButtonWheelDown}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("key:%v want:%v", tc.btnMask, tc.want), func(t *testing.T) { + + evs := toTermdashEvents(tcell.NewEventMouse(0, 0, tc.btnMask, tcell.ModNone)) + if got, want := len(evs), 1; got != want { + t.Fatalf("toTermdashEvents => got %d events, want %d", got, want) + } + + ev := evs[0] + if err, ok := ev.(*terminalapi.Error); ok != tc.wantErr { + t.Fatalf("toTermdashEvents => unexpected error:%v, wantErr: %v", err, tc.wantErr) + } + if _, ok := ev.(*terminalapi.Error); ok { + return + } + + switch e := ev.(type) { + case *terminalapi.Mouse: + if got := e.Button; got != tc.want { + t.Errorf("toTermdashEvents => got %v, want %v", got, tc.want) + } + + default: + t.Fatalf("toTermdashEvents => unexpected event type %T", e) + } + }) + } +} + +func TestKeyboardKeys(t *testing.T) { + tests := []struct { + key tcell.Key + ch rune + want keyboard.Key + wantErr bool + }{ + {key: 2000, wantErr: true}, + {key: tcell.KeyRune, ch: 'a', want: 'a'}, + {key: tcell.KeyRune, ch: 'A', want: 'A'}, + {key: tcell.KeyRune, ch: 'z', want: 'z'}, + {key: tcell.KeyRune, ch: 'Z', want: 'Z'}, + {key: tcell.KeyRune, ch: '0', want: '0'}, + {key: tcell.KeyRune, ch: '9', want: '9'}, + {key: tcell.KeyRune, ch: '!', want: '!'}, + {key: tcell.KeyRune, ch: ')', want: ')'}, + {key: tcellSpaceKey, want: keyboard.KeySpace}, + {key: tcell.KeyF1, want: keyboard.KeyF1}, + {key: tcell.KeyF2, want: keyboard.KeyF2}, + {key: tcell.KeyF3, want: keyboard.KeyF3}, + {key: tcell.KeyF4, want: keyboard.KeyF4}, + {key: tcell.KeyF5, want: keyboard.KeyF5}, + {key: tcell.KeyF6, want: keyboard.KeyF6}, + {key: tcell.KeyF7, want: keyboard.KeyF7}, + {key: tcell.KeyF8, want: keyboard.KeyF8}, + {key: tcell.KeyF9, want: keyboard.KeyF9}, + {key: tcell.KeyF10, want: keyboard.KeyF10}, + {key: tcell.KeyF11, want: keyboard.KeyF11}, + {key: tcell.KeyF12, want: keyboard.KeyF12}, + {key: tcell.KeyInsert, want: keyboard.KeyInsert}, + {key: tcell.KeyDelete, want: keyboard.KeyDelete}, + {key: tcell.KeyHome, want: keyboard.KeyHome}, + {key: tcell.KeyEnd, want: keyboard.KeyEnd}, + {key: tcell.KeyPgUp, want: keyboard.KeyPgUp}, + {key: tcell.KeyPgDn, want: keyboard.KeyPgDn}, + {key: tcell.KeyUp, want: keyboard.KeyArrowUp}, + {key: tcell.KeyDown, want: keyboard.KeyArrowDown}, + {key: tcell.KeyLeft, want: keyboard.KeyArrowLeft}, + {key: tcell.KeyRight, want: keyboard.KeyArrowRight}, + {key: tcell.KeyCtrlSpace, want: keyboard.KeyCtrlTilde}, + {key: tcell.KeyCtrlA, want: keyboard.KeyCtrlA}, + {key: tcell.KeyCtrlB, want: keyboard.KeyCtrlB}, + {key: tcell.KeyCtrlC, want: keyboard.KeyCtrlC}, + {key: tcell.KeyCtrlD, want: keyboard.KeyCtrlD}, + {key: tcell.KeyCtrlE, want: keyboard.KeyCtrlE}, + {key: tcell.KeyCtrlF, want: keyboard.KeyCtrlF}, + {key: tcell.KeyCtrlG, want: keyboard.KeyCtrlG}, + {key: tcell.KeyBackspace, want: keyboard.KeyBackspace}, + {key: tcell.KeyBackspace, want: keyboard.KeyCtrlH}, + {key: tcell.KeyCtrlH, want: keyboard.KeyBackspace}, + {key: tcell.KeyTab, want: keyboard.KeyTab}, + {key: tcell.KeyTab, want: keyboard.KeyCtrlI}, + {key: tcell.KeyCtrlI, want: keyboard.KeyTab}, + {key: tcell.KeyCtrlJ, want: keyboard.KeyCtrlJ}, + {key: tcell.KeyCtrlK, want: keyboard.KeyCtrlK}, + {key: tcell.KeyCtrlL, want: keyboard.KeyCtrlL}, + {key: tcell.KeyEnter, want: keyboard.KeyEnter}, + {key: tcell.KeyEnter, want: keyboard.KeyCtrlM}, + {key: tcell.KeyCtrlM, want: keyboard.KeyEnter}, + {key: tcell.KeyCtrlN, want: keyboard.KeyCtrlN}, + {key: tcell.KeyCtrlO, want: keyboard.KeyCtrlO}, + {key: tcell.KeyCtrlP, want: keyboard.KeyCtrlP}, + {key: tcell.KeyCtrlQ, want: keyboard.KeyCtrlQ}, + {key: tcell.KeyCtrlR, want: keyboard.KeyCtrlR}, + {key: tcell.KeyCtrlS, want: keyboard.KeyCtrlS}, + {key: tcell.KeyCtrlT, want: keyboard.KeyCtrlT}, + {key: tcell.KeyCtrlU, want: keyboard.KeyCtrlU}, + {key: tcell.KeyCtrlV, want: keyboard.KeyCtrlV}, + {key: tcell.KeyCtrlW, want: keyboard.KeyCtrlW}, + {key: tcell.KeyCtrlX, want: keyboard.KeyCtrlX}, + {key: tcell.KeyCtrlY, want: keyboard.KeyCtrlY}, + {key: tcell.KeyCtrlZ, want: keyboard.KeyCtrlZ}, + {key: tcell.KeyEsc, want: keyboard.KeyEsc}, + {key: tcell.KeyEsc, want: keyboard.KeyCtrlLsqBracket}, + {key: tcell.KeyEsc, want: keyboard.KeyCtrl3}, + {key: tcell.KeyCtrlLeftSq, want: keyboard.KeyEsc}, + {key: tcell.KeyCtrlBackslash, want: keyboard.KeyCtrl4}, + {key: tcell.KeyCtrlRightSq, want: keyboard.KeyCtrl5}, + {key: tcell.KeyCtrlUnderscore, want: keyboard.KeyCtrlUnderscore}, + {key: tcell.KeyBackspace2, want: keyboard.KeyBackspace2}, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("key:%v and ch:%v want:%v", tc.key, tc.ch, tc.want), func(t *testing.T) { + evs := toTermdashEvents(tcell.NewEventKey(tc.key, tc.ch, tcell.ModNone)) + + gotCount := len(evs) + wantCount := 1 + if gotCount != wantCount { + t.Fatalf("toTermdashEvents => got %d events, want %d, events were:\n%v", gotCount, wantCount, pretty.Sprint(evs)) + } + ev := evs[0] + + if err, ok := ev.(*terminalapi.Error); ok != tc.wantErr { + t.Fatalf("toTermdashEvents => unexpected error:%v, wantErr: %v", err, tc.wantErr) + } + if _, ok := ev.(*terminalapi.Error); ok { + return + } + + switch e := ev.(type) { + case *terminalapi.Keyboard: + if got, want := e.Key, tc.want; got != want { + t.Errorf("toTermdashEvents => got key %v, want %v", got, want) + } + + default: + t.Fatalf("toTermdashEvents => unexpected event type %T", e) + } + }) + } +} From facb0e6743da43e4e1ec5b3e49ec2573ecf45b3d Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Fri, 14 Feb 2020 19:51:03 -0500 Subject: [PATCH 03/25] Add missing comment --- terminal/tcell/tcell.go | 1 + 1 file changed, 1 insertion(+) diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index c0fc932..7f19621 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -21,6 +21,7 @@ type Terminal struct { // done gets closed when Close() is called. done chan struct{} + // the tcell terminal window screen tcell.Screen } From f01148c8fd28da1b87b31dd9cc0ce51218a4de90 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 02:01:13 -0500 Subject: [PATCH 04/25] Add color mode and clear style colors --- terminal/tcell/cell_options.go | 33 +++++++++++-- terminal/tcell/tcell.go | 86 +++++++++++++++++++++++++++------- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/terminal/tcell/cell_options.go b/terminal/tcell/cell_options.go index 5a5dd60..90f547e 100644 --- a/terminal/tcell/cell_options.go +++ b/terminal/tcell/cell_options.go @@ -3,19 +3,46 @@ package tcell import ( "github.com/gdamore/tcell" "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/terminal/terminalapi" ) // cellColor converts termdash cell color to the tcell format. func cellColor(c cell.Color) tcell.Color { - return tcell.Color(int(c)&0x1ff) - 1 + return tcell.Color(c&0x1ff) - 1 +} + +// fixColor converts the target color for the current color mode +func fixColor(c tcell.Color, colorMode terminalapi.ColorMode) tcell.Color { + if colorMode == DefaultColorMode { + return c + } + switch colorMode { + case terminalapi.ColorModeNormal: + c %= tcell.Color(16) + case terminalapi.ColorMode256: + c %= tcell.Color(256) + case terminalapi.ColorMode216: + c %= tcell.Color(216) + c += tcell.Color(16) + case terminalapi.ColorModeGrayscale: + c %= tcell.Color(24) + c += tcell.Color(232) + default: + c = tcell.ColorDefault + } + return c } // cellOptsToStyle converts termdash cell color to the tcell format. -func cellOptsToStyle(opts *cell.Options) tcell.Style { +func cellOptsToStyle(opts *cell.Options, colorMode terminalapi.ColorMode) tcell.Style { + st := tcell.StyleDefault + fg := cellColor(opts.FgColor) bg := cellColor(opts.BgColor) - st := tcell.StyleDefault + fg = fixColor(fg, colorMode) + bg = fixColor(bg, colorMode) + st = st.Foreground(fg).Background(bg) return st } diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index 7f19621..143aed2 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -11,6 +11,42 @@ import ( "github.com/mum4k/termdash/terminal/terminalapi" ) +// Option is used to provide options. +type Option interface { + // set sets the provided option. + set(*Terminal) +} + +// option implements Option. +type option func(*Terminal) + +// set implements Option.set. +func (o option) set(t *Terminal) { + o(t) +} + +// DefaultColorMode is the default value for the ColorMode option. +const DefaultColorMode = terminalapi.ColorMode256 + +// ColorMode sets the terminal color mode. +// Defaults to DefaultColorMode. +func ColorMode(cm terminalapi.ColorMode) Option { + return option(func(t *Terminal) { + t.colorMode = cm + }) +} + +// ClearStyle sets the style to use for tcell when clearing the screen. +// Defaults to white foreground and black background. +func ClearStyle(fg, bg cell.Color) Option { + return option(func(t *Terminal) { + t.clearStyle = &cell.Options{ + FgColor: fg, + BgColor: bg, + } + }) +} + // Terminal provides input and output to a real terminal. Wraps the // gdamore/tcell terminal implementation. This object is not thread-safe. // Implements terminalapi.Terminal. @@ -23,37 +59,55 @@ type Terminal struct { // the tcell terminal window screen tcell.Screen + + // Options. + colorMode terminalapi.ColorMode + clearStyle *cell.Options } -// 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() - +// newTerminal creates the terminal and applies the options. +func newTerminal(opts ...Option) (*Terminal, error) { screen, err := tcell.NewScreen() if err != nil { return nil, err } t := &Terminal{ - events: eventqueue.New(), - done: make(chan struct{}), + events: eventqueue.New(), + done: make(chan struct{}), + colorMode: DefaultColorMode, + clearStyle: &cell.Options{ + FgColor: cell.ColorWhite, + BgColor: cell.ColorBlack, + }, screen: screen, } + for _, opt := range opts { + opt.set(t) + } + return t, nil +} + +// New returns a new tcell based Terminal. +// Call Close() when the terminal isn't required anymore. +func New(opts ...Option) (*Terminal, error) { + // Enable full character set support for tcell + encoding.Register() + + t, err := newTerminal(opts...) + if err != nil { + return nil, err + } if err = t.screen.Init(); err != nil { return nil, err } - defaultStyle := tcell.StyleDefault. - Foreground(tcell.ColorWhite). - Background(tcell.ColorBlack) - + clearStyle := cellOptsToStyle(t.clearStyle, t.colorMode) t.screen.EnableMouse() - t.screen.SetStyle(defaultStyle) + t.screen.SetStyle(clearStyle) - go t.pollEvents() + go t.pollEvents() // Stops when Close() is called. return t, nil } @@ -69,7 +123,7 @@ func (t *Terminal) Size() image.Point { // Clear implements terminalapi.Terminal.Clear. func (t *Terminal) Clear(opts ...cell.Option) error { o := cell.NewOptions(opts...) - st := cellOptsToStyle(o) + st := cellOptsToStyle(o, t.colorMode) w, h := t.screen.Size() for row := 0; row < h; row++ { for col := 0; col < w; col++ { @@ -98,7 +152,7 @@ func (t *Terminal) 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) + st := cellOptsToStyle(o, t.colorMode) t.screen.SetContent(p.X, p.Y, r, nil, st) return nil } From 2f95ff99d1ff6131e4d513f17147871ceb360488 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 02:10:10 -0500 Subject: [PATCH 05/25] Remove local go.mod stuff --- go.mod | 12 ------------ go.sum | 22 ---------------------- 2 files changed, 34 deletions(-) delete mode 100644 go.mod delete mode 100644 go.sum diff --git a/go.mod b/go.mod deleted file mode 100644 index ce0c929..0000000 --- a/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -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 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index b4baf5d..0000000 --- a/go.sum +++ /dev/null @@ -1,22 +0,0 @@ -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= From 67c22d9015269136db2271d7f4d25b9c9c459a02 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 02:15:23 -0500 Subject: [PATCH 06/25] Add license headers --- terminal/tcell/cell_options.go | 14 ++++++++++++++ terminal/tcell/cell_options_test.go | 14 ++++++++++++++ terminal/tcell/event.go | 14 ++++++++++++++ terminal/tcell/event_test.go | 14 ++++++++++++++ terminal/tcell/tcell.go | 14 ++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/terminal/tcell/cell_options.go b/terminal/tcell/cell_options.go index 90f547e..7d22c25 100644 --- a/terminal/tcell/cell_options.go +++ b/terminal/tcell/cell_options.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go index acbacdb..d8b1887 100644 --- a/terminal/tcell/cell_options_test.go +++ b/terminal/tcell/cell_options_test.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index d215331..ff1b122 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( diff --git a/terminal/tcell/event_test.go b/terminal/tcell/event_test.go index d10cf77..6be5ca1 100644 --- a/terminal/tcell/event_test.go +++ b/terminal/tcell/event_test.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index 143aed2..acdb311 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( From 3fdaf659f215959c3fbdf2658a726889046596f3 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 17:21:55 -0500 Subject: [PATCH 07/25] Use tcell screen.Fill() to clear screen --- terminal/tcell/tcell.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index acdb311..07553ad 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -138,12 +138,7 @@ func (t *Terminal) Size() image.Point { func (t *Terminal) Clear(opts ...cell.Option) error { o := cell.NewOptions(opts...) st := cellOptsToStyle(o, t.colorMode) - w, h := t.screen.Size() - for row := 0; row < h; row++ { - for col := 0; col < w; col++ { - t.screen.SetContent(col, row, ' ', nil, st) - } - } + t.screen.Fill(' ', st) return nil } From 5cd12d9fce4b234c47a9f3a599b9db302519559c Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 17:32:35 -0500 Subject: [PATCH 08/25] Add test for tcell options --- terminal/tcell/tcell_test.go | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 terminal/tcell/tcell_test.go diff --git a/terminal/tcell/tcell_test.go b/terminal/tcell/tcell_test.go new file mode 100644 index 0000000..a4e43b0 --- /dev/null +++ b/terminal/tcell/tcell_test.go @@ -0,0 +1,102 @@ +package tcell + +import ( + "testing" + + "github.com/kylelemons/godebug/pretty" + "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/terminal/terminalapi" +) + +func TestNewTerminalColorMode(t *testing.T) { + tests := []struct { + desc string + opts []Option + want *Terminal + }{ + { + desc: "default options", + want: &Terminal{ + colorMode: terminalapi.ColorMode256, + }, + }, + { + desc: "sets color mode", + opts: []Option{ + ColorMode(terminalapi.ColorModeNormal), + }, + want: &Terminal{ + colorMode: terminalapi.ColorModeNormal, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got, err := newTerminal(tc.opts...) + if err != nil { + t.Errorf("newTerminal => unexpected error:\n%v", err) + } + + // Ignore these fields. + got.screen = nil + got.events = nil + got.done = nil + got.clearStyle = nil + + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("newTerminal => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} + +func TestNewTerminalClearStyle(t *testing.T) { + tests := []struct { + desc string + opts []Option + want *Terminal + }{ + { + desc: "default options", + want: &Terminal{ + colorMode: terminalapi.ColorMode256, + clearStyle: &cell.Options{ + FgColor: cell.ColorWhite, + BgColor: cell.ColorBlack, + }, + }, + }, + { + desc: "sets clear style", + opts: []Option{ + ClearStyle(cell.ColorRed, cell.ColorBlue), + }, + want: &Terminal{ + colorMode: terminalapi.ColorMode256, + clearStyle: &cell.Options{ + FgColor: cell.ColorRed, + BgColor: cell.ColorBlue, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + got, err := newTerminal(tc.opts...) + if err != nil { + t.Errorf("newTerminal => unexpected error:\n%v", err) + } + + // Ignore these fields. + got.screen = nil + got.events = nil + got.done = nil + + if diff := pretty.Compare(tc.want, got); diff != "" { + t.Errorf("newTerminal => unexpected diff (-want, +got):\n%s", diff) + } + }) + } +} From c431b8c3bd7794e46174b98287873c31f00fd965 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 17:52:32 -0500 Subject: [PATCH 09/25] Add fixColor test --- terminal/tcell/cell_options_test.go | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go index d8b1887..f8c8fc4 100644 --- a/terminal/tcell/cell_options_test.go +++ b/terminal/tcell/cell_options_test.go @@ -19,6 +19,7 @@ import ( "github.com/gdamore/tcell" "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/terminal/terminalapi" ) func TestCellColor(t *testing.T) { @@ -47,3 +48,67 @@ func TestCellColor(t *testing.T) { }) } } + +func TestFixColor(t *testing.T) { + tests := []struct { + colorMode terminalapi.ColorMode + color cell.Color + want tcell.Color + }{ + // See https://jonasjacek.github.io/colors/ for a good reference of all 256 xterm colors + // All 256 colors + {terminalapi.ColorMode256, cell.ColorDefault, tcell.ColorDefault}, + {terminalapi.ColorMode256, cell.ColorBlack, tcell.ColorBlack}, + {terminalapi.ColorMode256, cell.ColorRed, tcell.ColorMaroon}, + {terminalapi.ColorMode256, cell.ColorGreen, tcell.ColorGreen}, + {terminalapi.ColorMode256, cell.ColorYellow, tcell.ColorOlive}, + {terminalapi.ColorMode256, cell.ColorBlue, tcell.ColorNavy}, + {terminalapi.ColorMode256, cell.ColorMagenta, tcell.ColorPurple}, + {terminalapi.ColorMode256, cell.ColorCyan, tcell.ColorTeal}, + {terminalapi.ColorMode256, cell.ColorWhite, tcell.ColorSilver}, + {terminalapi.ColorMode256, cell.ColorNumber(42), tcell.Color(42)}, + // 8 system colors + {terminalapi.ColorModeNormal, cell.ColorDefault, tcell.ColorDefault}, + {terminalapi.ColorModeNormal, cell.ColorBlack, tcell.ColorBlack}, + {terminalapi.ColorModeNormal, cell.ColorRed, tcell.ColorMaroon}, + {terminalapi.ColorModeNormal, cell.ColorGreen, tcell.ColorGreen}, + {terminalapi.ColorModeNormal, cell.ColorYellow, tcell.ColorOlive}, + {terminalapi.ColorModeNormal, cell.ColorBlue, tcell.ColorNavy}, + {terminalapi.ColorModeNormal, cell.ColorMagenta, tcell.ColorPurple}, + {terminalapi.ColorModeNormal, cell.ColorCyan, tcell.ColorTeal}, + {terminalapi.ColorModeNormal, cell.ColorWhite, tcell.ColorSilver}, + {terminalapi.ColorModeNormal, cell.ColorNumber(42), tcell.Color(10)}, + // Grayscale colors (all the grey colours from 231 to 255) + {terminalapi.ColorModeGrayscale, cell.ColorDefault, tcell.Color231}, + {terminalapi.ColorModeGrayscale, cell.ColorBlack, tcell.Color232}, + {terminalapi.ColorModeGrayscale, cell.ColorRed, tcell.Color233}, + {terminalapi.ColorModeGrayscale, cell.ColorGreen, tcell.Color234}, + {terminalapi.ColorModeGrayscale, cell.ColorYellow, tcell.Color235}, + {terminalapi.ColorModeGrayscale, cell.ColorBlue, tcell.Color236}, + {terminalapi.ColorModeGrayscale, cell.ColorMagenta, tcell.Color237}, + {terminalapi.ColorModeGrayscale, cell.ColorCyan, tcell.Color238}, + {terminalapi.ColorModeGrayscale, cell.ColorWhite, tcell.Color239}, + {terminalapi.ColorModeGrayscale, cell.ColorNumber(42), tcell.Color(250)}, + // 216 colors (16 to 231) + {terminalapi.ColorMode216, cell.ColorDefault, tcell.ColorWhite}, + {terminalapi.ColorMode216, cell.ColorBlack, tcell.Color16}, + {terminalapi.ColorMode216, cell.ColorRed, tcell.Color17}, + {terminalapi.ColorMode216, cell.ColorGreen, tcell.Color18}, + {terminalapi.ColorMode216, cell.ColorYellow, tcell.Color19}, + {terminalapi.ColorMode216, cell.ColorBlue, tcell.Color20}, + {terminalapi.ColorMode216, cell.ColorMagenta, tcell.Color21}, + {terminalapi.ColorMode216, cell.ColorCyan, tcell.Color22}, + {terminalapi.ColorMode216, cell.ColorWhite, tcell.Color23}, + {terminalapi.ColorMode216, cell.ColorNumber(42), tcell.Color(66)}, + } + + for _, tc := range tests { + t.Run(tc.colorMode.String()+"_"+tc.color.String(), func(t *testing.T) { + color := cellColor(tc.color) + got := fixColor(color, tc.colorMode) + if got != tc.want { + t.Errorf("fixColor(%v_%v), => got %v, want %v", tc.colorMode, tc.color, got, tc.want) + } + }) + } +} From 5f5aff3a945d7f711ffcb7118ac0a9c2bcdb43ae Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:03:36 -0500 Subject: [PATCH 10/25] Add cellOptsToStyle test --- terminal/tcell/cell_options_test.go | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go index f8c8fc4..d82bb36 100644 --- a/terminal/tcell/cell_options_test.go +++ b/terminal/tcell/cell_options_test.go @@ -112,3 +112,44 @@ func TestFixColor(t *testing.T) { }) } } + +func TestCellOptsToStyle(t *testing.T) { + tests := []struct { + colorMode terminalapi.ColorMode + opts cell.Options + want tcell.Style + }{ + { + colorMode: terminalapi.ColorMode256, + opts: cell.Options{FgColor: cell.ColorWhite, BgColor: cell.ColorBlack}, + want: tcell.StyleDefault.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack), + }, + { + colorMode: terminalapi.ColorModeNormal, + opts: cell.Options{FgColor: cell.ColorWhite, BgColor: cell.ColorBlack}, + want: tcell.StyleDefault.Foreground(tcell.ColorSilver).Background(tcell.ColorBlack), + }, + { + colorMode: terminalapi.ColorModeGrayscale, + opts: cell.Options{FgColor: cell.ColorWhite, BgColor: cell.ColorBlack}, + want: tcell.StyleDefault.Foreground(tcell.Color239).Background(tcell.Color232), + }, + { + colorMode: terminalapi.ColorMode216, + opts: cell.Options{FgColor: cell.ColorWhite, BgColor: cell.ColorBlack}, + want: tcell.StyleDefault.Foreground(tcell.Color23).Background(tcell.Color16), + }, + } + + for _, tc := range tests { + t.Run(tc.opts.FgColor.String()+"+"+tc.opts.BgColor.String(), func(t *testing.T) { + got := cellOptsToStyle(&tc.opts, tc.colorMode) + if got != tc.want { + fg, bg, _ := got.Decompose() + wantFg, wantBg, _ := tc.want.Decompose() + t.Errorf("cellOptsToStyle(%v, fg=%v, bg=%v) => got (fg=%X, bg=%X), want (fg=%X, bg=%X)", + tc.colorMode, tc.opts.FgColor, tc.opts.BgColor, fg, bg, wantFg, wantBg) + } + }) + } +} From 2f2b13d3194b9596ff7af1d8309654074204b779 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:06:55 -0500 Subject: [PATCH 11/25] Add event.Name() to keyboard key error message --- terminal/tcell/event.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index ff1b122..4b315ce 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -98,7 +98,7 @@ func convKey(event *tcell.EventKey) terminalapi.Event { k, ok := tcellToTd[tcellKey] if !ok { - return terminalapi.NewErrorf("unknown keyboard key '%v' in a keyboard event", tcellKey) + return terminalapi.NewErrorf("unknown keyboard key '%v' in a keyboard event %v", tcellKey, event.Name()) } return &terminalapi.Keyboard{ From 299590e784f3ea16fb8f5943132224722eccd893 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:09:20 -0500 Subject: [PATCH 12/25] Fix a test case for fixColors on color mode 216 --- terminal/tcell/cell_options_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go index d82bb36..32c808d 100644 --- a/terminal/tcell/cell_options_test.go +++ b/terminal/tcell/cell_options_test.go @@ -99,7 +99,7 @@ func TestFixColor(t *testing.T) { {terminalapi.ColorMode216, cell.ColorMagenta, tcell.Color21}, {terminalapi.ColorMode216, cell.ColorCyan, tcell.Color22}, {terminalapi.ColorMode216, cell.ColorWhite, tcell.Color23}, - {terminalapi.ColorMode216, cell.ColorNumber(42), tcell.Color(66)}, + {terminalapi.ColorMode216, cell.ColorNumber(42), tcell.Color(58)}, } for _, tc := range tests { From d270c6de176a504ee9346ccc0a8619fead70574b Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:26:10 -0500 Subject: [PATCH 13/25] Add missing license header --- terminal/tcell/tcell_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/terminal/tcell/tcell_test.go b/terminal/tcell/tcell_test.go index a4e43b0..e77fcf7 100644 --- a/terminal/tcell/tcell_test.go +++ b/terminal/tcell/tcell_test.go @@ -1,3 +1,17 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tcell import ( From a0d3da0527403217f84e48d27d37b1ad536fb5dd Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:36:19 -0500 Subject: [PATCH 14/25] Fix a check in fixColor --- terminal/tcell/cell_options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal/tcell/cell_options.go b/terminal/tcell/cell_options.go index 7d22c25..e0357a9 100644 --- a/terminal/tcell/cell_options.go +++ b/terminal/tcell/cell_options.go @@ -27,7 +27,7 @@ func cellColor(c cell.Color) tcell.Color { // fixColor converts the target color for the current color mode func fixColor(c tcell.Color, colorMode terminalapi.ColorMode) tcell.Color { - if colorMode == DefaultColorMode { + if c == tcell.ColorDefault { return c } switch colorMode { From f7578e54ccf891182a267d07a499847ad1284d00 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sat, 29 Feb 2020 18:46:20 -0500 Subject: [PATCH 15/25] Fix tests for ColorDefault --- terminal/tcell/cell_options_test.go | 6 ++++-- terminal/tcell/event.go | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/terminal/tcell/cell_options_test.go b/terminal/tcell/cell_options_test.go index 32c808d..b3df570 100644 --- a/terminal/tcell/cell_options_test.go +++ b/terminal/tcell/cell_options_test.go @@ -79,7 +79,7 @@ func TestFixColor(t *testing.T) { {terminalapi.ColorModeNormal, cell.ColorWhite, tcell.ColorSilver}, {terminalapi.ColorModeNormal, cell.ColorNumber(42), tcell.Color(10)}, // Grayscale colors (all the grey colours from 231 to 255) - {terminalapi.ColorModeGrayscale, cell.ColorDefault, tcell.Color231}, + {terminalapi.ColorModeGrayscale, cell.ColorDefault, tcell.ColorDefault}, {terminalapi.ColorModeGrayscale, cell.ColorBlack, tcell.Color232}, {terminalapi.ColorModeGrayscale, cell.ColorRed, tcell.Color233}, {terminalapi.ColorModeGrayscale, cell.ColorGreen, tcell.Color234}, @@ -90,7 +90,7 @@ func TestFixColor(t *testing.T) { {terminalapi.ColorModeGrayscale, cell.ColorWhite, tcell.Color239}, {terminalapi.ColorModeGrayscale, cell.ColorNumber(42), tcell.Color(250)}, // 216 colors (16 to 231) - {terminalapi.ColorMode216, cell.ColorDefault, tcell.ColorWhite}, + {terminalapi.ColorMode216, cell.ColorDefault, tcell.ColorDefault}, {terminalapi.ColorMode216, cell.ColorBlack, tcell.Color16}, {terminalapi.ColorMode216, cell.ColorRed, tcell.Color17}, {terminalapi.ColorMode216, cell.ColorGreen, tcell.Color18}, @@ -100,6 +100,8 @@ func TestFixColor(t *testing.T) { {terminalapi.ColorMode216, cell.ColorCyan, tcell.Color22}, {terminalapi.ColorMode216, cell.ColorWhite, tcell.Color23}, {terminalapi.ColorMode216, cell.ColorNumber(42), tcell.Color(58)}, + // Unknown color mode + {-1, cell.ColorRed, tcell.ColorDefault}, } for _, tc := range tests { diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index 4b315ce..a196660 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -107,7 +107,9 @@ func convKey(event *tcell.EventKey) terminalapi.Event { } // convMouse converts a tcell mouse event to the termdash format. -func convMouse(event *tcell.EventMouse) terminalapi.Event { +// Since tcell supports many combinations of mouse events, such as multiple mouse buttons pressed at the same time, +// this function returns a secondary bool that denotes whether the event is valid for termdash. +func convMouse(event *tcell.EventMouse) (terminalapi.Event, bool) { var button mouse.Button x, y := event.Position() @@ -115,7 +117,7 @@ func convMouse(event *tcell.EventMouse) terminalapi.Event { // tcell uses signed int16 for button masks, and negative values are invalid if tcellBtn < 0 { - return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn) + return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn), true } // Get wheel events @@ -130,7 +132,7 @@ func convMouse(event *tcell.EventMouse) terminalapi.Event { return &terminalapi.Mouse{ Position: image.Point{X: x, Y: y}, Button: button, - } + }, true } // Get only button events, not wheel events @@ -145,14 +147,17 @@ func convMouse(event *tcell.EventMouse) terminalapi.Event { case tcell.Button3: button = mouse.ButtonMiddle default: - // Do nothing, since tcell allows multiple buttons to be pressed at the same time - // Maybe refactor terminalapi to handle multiple mouse buttons being pressed at the same time (e.g. M1 + M2) + // Unknown event to termdash + return &terminalapi.Mouse{ + Position: image.Point{X: x, Y: y}, + Button: button, + }, false } return &terminalapi.Mouse{ Position: image.Point{X: x, Y: y}, Button: button, - } + }, true } // convResize converts a tcell resize event to the termdash format. @@ -177,7 +182,14 @@ func toTermdashEvents(event tcell.Event) []terminalapi.Event { case *tcell.EventKey: return []terminalapi.Event{convKey(event)} case *tcell.EventMouse: - return []terminalapi.Event{convMouse(event)} + mouseEvent, termdashOk := convMouse(event) + if termdashOk { + return []terminalapi.Event{mouseEvent} + } else { + return []terminalapi.Event{ + terminalapi.NewErrorf("unknown tcell event type: %v", event), + } + } case *tcell.EventResize: return []terminalapi.Event{convResize(event)} case *tcell.EventError: From 1e970dcacd49b6e317bb55ebf33970facbb562d8 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 01:14:37 -0500 Subject: [PATCH 16/25] Return nil for non-termdash events --- terminal/tcell/event.go | 4 +--- terminal/tcell/tcell.go | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index a196660..7dd09a5 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -186,9 +186,7 @@ func toTermdashEvents(event tcell.Event) []terminalapi.Event { if termdashOk { return []terminalapi.Event{mouseEvent} } else { - return []terminalapi.Event{ - terminalapi.NewErrorf("unknown tcell event type: %v", event), - } + return nil } case *tcell.EventResize: return []terminalapi.Event{convResize(event)} diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index 07553ad..93bb18f 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -177,7 +177,9 @@ func (t *Terminal) pollEvents() { events := toTermdashEvents(t.screen.PollEvent()) for _, ev := range events { - t.events.Push(ev) + if ev != nil { + t.events.Push(ev) + } } } } From c8ffedc2b8c2e25258a30f3c5dbfcd8d10b996e3 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 01:19:49 -0500 Subject: [PATCH 17/25] Return nil mouseEvent for non-supported events --- terminal/tcell/event.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index 7dd09a5..606819d 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -109,7 +109,7 @@ func convKey(event *tcell.EventKey) terminalapi.Event { // convMouse converts a tcell mouse event to the termdash format. // Since tcell supports many combinations of mouse events, such as multiple mouse buttons pressed at the same time, // this function returns a secondary bool that denotes whether the event is valid for termdash. -func convMouse(event *tcell.EventMouse) (terminalapi.Event, bool) { +func convMouse(event *tcell.EventMouse) terminalapi.Event { var button mouse.Button x, y := event.Position() @@ -117,7 +117,7 @@ func convMouse(event *tcell.EventMouse) (terminalapi.Event, bool) { // tcell uses signed int16 for button masks, and negative values are invalid if tcellBtn < 0 { - return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn), true + return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", tcellBtn) } // Get wheel events @@ -132,7 +132,7 @@ func convMouse(event *tcell.EventMouse) (terminalapi.Event, bool) { return &terminalapi.Mouse{ Position: image.Point{X: x, Y: y}, Button: button, - }, true + } } // Get only button events, not wheel events @@ -148,16 +148,13 @@ func convMouse(event *tcell.EventMouse) (terminalapi.Event, bool) { button = mouse.ButtonMiddle default: // Unknown event to termdash - return &terminalapi.Mouse{ - Position: image.Point{X: x, Y: y}, - Button: button, - }, false + return nil } return &terminalapi.Mouse{ Position: image.Point{X: x, Y: y}, Button: button, - }, true + } } // convResize converts a tcell resize event to the termdash format. @@ -182,8 +179,8 @@ func toTermdashEvents(event tcell.Event) []terminalapi.Event { case *tcell.EventKey: return []terminalapi.Event{convKey(event)} case *tcell.EventMouse: - mouseEvent, termdashOk := convMouse(event) - if termdashOk { + mouseEvent := convMouse(event) + if mouseEvent != nil { return []terminalapi.Event{mouseEvent} } else { return nil From 1af531975d56758ef629f04408728f772f5107b8 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 01:25:50 -0500 Subject: [PATCH 18/25] Fix linting --- terminal/tcell/event.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index 606819d..9ee4fa1 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -182,9 +182,8 @@ func toTermdashEvents(event tcell.Event) []terminalapi.Event { mouseEvent := convMouse(event) if mouseEvent != nil { return []terminalapi.Event{mouseEvent} - } else { - return nil } + return nil case *tcell.EventResize: return []terminalapi.Event{convResize(event)} case *tcell.EventError: From be7995369c9ea5c895967a2ea320c7a17ac83efd Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 01:48:32 -0500 Subject: [PATCH 19/25] Fix mouse button logic and tests --- terminal/tcell/event.go | 3 ++- terminal/tcell/event_test.go | 26 ++++++++++++++++---------- terminal/tcell/tcell.go | 4 +--- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index 9ee4fa1..99fe5ff 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -108,7 +108,7 @@ func convKey(event *tcell.EventKey) terminalapi.Event { // convMouse converts a tcell mouse event to the termdash format. // Since tcell supports many combinations of mouse events, such as multiple mouse buttons pressed at the same time, -// this function returns a secondary bool that denotes whether the event is valid for termdash. +// this function returns nil if the event is invalid for termdash. func convMouse(event *tcell.EventMouse) terminalapi.Event { var button mouse.Button x, y := event.Position() @@ -170,6 +170,7 @@ func convResize(event *tcell.EventResize) terminalapi.Event { } // toTermdashEvents converts a tcell event to the termdash event format. +// This function returns nil if the event is invalid for termdash. func toTermdashEvents(event tcell.Event) []terminalapi.Event { switch event := event.(type) { case *tcell.EventInterrupt: diff --git a/terminal/tcell/event_test.go b/terminal/tcell/event_test.go index 6be5ca1..7859f40 100644 --- a/terminal/tcell/event_test.go +++ b/terminal/tcell/event_test.go @@ -112,26 +112,32 @@ func TestToTermdashEvents(t *testing.T) { func TestMouseButtons(t *testing.T) { tests := []struct { btnMask tcell.ButtonMask - want mouse.Button + want []mouse.Button wantErr bool }{ - {btnMask: -1, wantErr: true}, - {btnMask: tcell.Button1, want: mouse.ButtonLeft}, - {btnMask: tcell.Button3, want: mouse.ButtonMiddle}, - {btnMask: tcell.Button2, want: mouse.ButtonRight}, - {btnMask: tcell.ButtonNone, want: mouse.ButtonRelease}, - {btnMask: tcell.WheelUp, want: mouse.ButtonWheelUp}, - {btnMask: tcell.WheelDown, want: mouse.ButtonWheelDown}, + {btnMask: -1, want: []mouse.Button{mouse.Button(-1)}, wantErr: true}, + {btnMask: tcell.Button1, want: []mouse.Button{mouse.ButtonLeft}}, + {btnMask: tcell.Button3, want: []mouse.Button{mouse.ButtonMiddle}}, + {btnMask: tcell.Button2, want: []mouse.Button{mouse.ButtonRight}}, + {btnMask: tcell.ButtonNone, want: []mouse.Button{mouse.ButtonRelease}}, + {btnMask: tcell.WheelUp, want: []mouse.Button{mouse.ButtonWheelUp}}, + {btnMask: tcell.WheelDown, want: []mouse.Button{mouse.ButtonWheelDown}}, + {btnMask: tcell.Button1 | tcell.Button2, want: nil}, } for _, tc := range tests { t.Run(fmt.Sprintf("key:%v want:%v", tc.btnMask, tc.want), func(t *testing.T) { evs := toTermdashEvents(tcell.NewEventMouse(0, 0, tc.btnMask, tcell.ModNone)) - if got, want := len(evs), 1; got != want { + if got, want := len(evs), len(tc.want); got != want { t.Fatalf("toTermdashEvents => got %d events, want %d", got, want) } + // Events that may exist for the terminal implementation but are not valid for termdash will be nil + if len(tc.want) == 0 { + return + } + ev := evs[0] if err, ok := ev.(*terminalapi.Error); ok != tc.wantErr { t.Fatalf("toTermdashEvents => unexpected error:%v, wantErr: %v", err, tc.wantErr) @@ -142,7 +148,7 @@ func TestMouseButtons(t *testing.T) { switch e := ev.(type) { case *terminalapi.Mouse: - if got := e.Button; got != tc.want { + if got := e.Button; got != tc.want[0] { t.Errorf("toTermdashEvents => got %v, want %v", got, tc.want) } diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index 93bb18f..07553ad 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -177,9 +177,7 @@ func (t *Terminal) pollEvents() { events := toTermdashEvents(t.screen.PollEvent()) for _, ev := range events { - if ev != nil { - t.events.Push(ev) - } + t.events.Push(ev) } } } From 95f5fe8fc91ee9df976c43634bf99a2be4578480 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 02:13:43 -0500 Subject: [PATCH 20/25] Fix default clear style to use ColorDefault --- terminal/tcell/tcell.go | 6 +++--- terminal/tcell/tcell_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/terminal/tcell/tcell.go b/terminal/tcell/tcell.go index 07553ad..6177202 100644 --- a/terminal/tcell/tcell.go +++ b/terminal/tcell/tcell.go @@ -51,7 +51,7 @@ func ColorMode(cm terminalapi.ColorMode) Option { } // ClearStyle sets the style to use for tcell when clearing the screen. -// Defaults to white foreground and black background. +// Defaults to ColorDefault for foreground and background. func ClearStyle(fg, bg cell.Color) Option { return option(func(t *Terminal) { t.clearStyle = &cell.Options{ @@ -91,8 +91,8 @@ func newTerminal(opts ...Option) (*Terminal, error) { done: make(chan struct{}), colorMode: DefaultColorMode, clearStyle: &cell.Options{ - FgColor: cell.ColorWhite, - BgColor: cell.ColorBlack, + FgColor: cell.ColorDefault, + BgColor: cell.ColorDefault, }, screen: screen, } diff --git a/terminal/tcell/tcell_test.go b/terminal/tcell/tcell_test.go index e77fcf7..6b8d79d 100644 --- a/terminal/tcell/tcell_test.go +++ b/terminal/tcell/tcell_test.go @@ -76,8 +76,8 @@ func TestNewTerminalClearStyle(t *testing.T) { want: &Terminal{ colorMode: terminalapi.ColorMode256, clearStyle: &cell.Options{ - FgColor: cell.ColorWhite, - BgColor: cell.ColorBlack, + FgColor: cell.ColorDefault, + BgColor: cell.ColorDefault, }, }, }, From b420a29b30e282ce4e7a6aa28f2e409ac0be02e8 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 02:15:15 -0500 Subject: [PATCH 21/25] Update CHANGELOG for new tcell terminal implementation --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d23fc..6e61de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- New [`tcell`](https://github.com/gdamore/tcell) based terminal implementation + which implements the `terminalapi.Terminal` interface. +- tcell implementation supports two initialization `Option`s: + - `ColorMode` the terminal color output mode (defaults to 256 color mode) + - `ClearStyle` the foreground and background color style to use when clearing + the screen (defaults to the global ColorDefault for both foreground and background) + ## [0.10.0] - 5-Jun-2019 ### Added From 95719be9f690493d9ee92bd3f3ad4dcbd32d2cbf Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 14:23:53 -0500 Subject: [PATCH 22/25] Make mouse event test case clearer --- terminal/tcell/event_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/terminal/tcell/event_test.go b/terminal/tcell/event_test.go index 7859f40..4d7e6c5 100644 --- a/terminal/tcell/event_test.go +++ b/terminal/tcell/event_test.go @@ -133,7 +133,15 @@ func TestMouseButtons(t *testing.T) { t.Fatalf("toTermdashEvents => got %d events, want %d", got, want) } - // Events that may exist for the terminal implementation but are not valid for termdash will be nil + switch count := len(tc.want); count { + case 0: + // Events that may exist for the terminal implementation but are not supported by termdash will be nil + return + case 1: + // Proceed with test + default: + t.Fatalf("toTermdashEvents test case specified %d expected events, only one is supported", count) + } if len(tc.want) == 0 { return } From 6246aba1f7a842753d8daed7a4b58aa81e6ee7d8 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 14:24:10 -0500 Subject: [PATCH 23/25] Change "invalid" to "unsupported" in comments --- terminal/tcell/event.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal/tcell/event.go b/terminal/tcell/event.go index 99fe5ff..a778dfe 100644 --- a/terminal/tcell/event.go +++ b/terminal/tcell/event.go @@ -108,7 +108,7 @@ func convKey(event *tcell.EventKey) terminalapi.Event { // convMouse converts a tcell mouse event to the termdash format. // Since tcell supports many combinations of mouse events, such as multiple mouse buttons pressed at the same time, -// this function returns nil if the event is invalid for termdash. +// this function returns nil if the event is unsupported by termdash. func convMouse(event *tcell.EventMouse) terminalapi.Event { var button mouse.Button x, y := event.Position() @@ -170,7 +170,7 @@ func convResize(event *tcell.EventResize) terminalapi.Event { } // toTermdashEvents converts a tcell event to the termdash event format. -// This function returns nil if the event is invalid for termdash. +// This function returns nil if the event is unsupported by termdash. func toTermdashEvents(event tcell.Event) []terminalapi.Event { switch event := event.(type) { case *tcell.EventInterrupt: From 37ee2177aca5999ba422f6be6b01bdac24874f1d Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 14:43:46 -0500 Subject: [PATCH 24/25] Add missing Close() function to terminalapi.Terminal interface --- terminal/terminalapi/terminalapi.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminal/terminalapi/terminalapi.go b/terminal/terminalapi/terminalapi.go index d45935a..831abc1 100644 --- a/terminal/terminalapi/terminalapi.go +++ b/terminal/terminalapi/terminalapi.go @@ -49,4 +49,8 @@ type Terminal interface { // This call blocks until the next event or cancellation of the context. // Returns nil when the context gets canceled. Event(ctx context.Context) Event + + // Close closes the underlying terminal implementation and should be called when + // the terminal isn't required anymore to return the screen to a sane state. + Close() } From 26f6d38c1f76710cce7d1641f70009f3d946564f Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Sun, 1 Mar 2020 14:44:33 -0500 Subject: [PATCH 25/25] Add -terminal flag to switch between implementations for termdashdemo --- termdashdemo/termdashdemo.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/termdashdemo/termdashdemo.go b/termdashdemo/termdashdemo.go index f2b6ab2..647ab05 100644 --- a/termdashdemo/termdashdemo.go +++ b/termdashdemo/termdashdemo.go @@ -18,7 +18,9 @@ package main import ( "context" + "flag" "fmt" + "log" "math" "math/rand" "sync" @@ -31,6 +33,7 @@ import ( "github.com/mum4k/termdash/container/grid" "github.com/mum4k/termdash/keyboard" "github.com/mum4k/termdash/linestyle" + "github.com/mum4k/termdash/terminal/tcell" "github.com/mum4k/termdash/terminal/termbox" "github.com/mum4k/termdash/terminal/terminalapi" "github.com/mum4k/termdash/widgets/barchart" @@ -466,8 +469,30 @@ func contLayout(w *widgets) ([]container.Option, error) { // rootID is the ID assigned to the root container. const rootID = "root" +// Terminal implementations +const ( + termboxTerminal = "termbox" + tcellTerminal = "tcell" +) + func main() { - t, err := termbox.New(termbox.ColorMode(terminalapi.ColorMode256)) + terminalPtr := flag.String("terminal", + "termbox", + "The terminal implementation to use. Available implementations are 'termbox' and 'tcell' (default = termbox).") + flag.Parse() + + var t terminalapi.Terminal + var err error + switch terminal := *terminalPtr; terminal { + case termboxTerminal: + t, err = termbox.New(termbox.ColorMode(terminalapi.ColorMode256)) + case tcellTerminal: + t, err = tcell.New(tcell.ColorMode(terminalapi.ColorMode256)) + default: + log.Fatalf("Unknown terminal implementation '%s' specified. Please choose between 'termbox' and 'tcell'.", terminal) + return + } + if err != nil { panic(err) }