mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-25 13:48:50 +08:00
Merge pull request #224 from kvnxiao/devel
Add Tcell implementation for terminal backend
This commit is contained in:
commit
d279ac7476
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
62
terminal/tcell/cell_options.go
Normal file
62
terminal/tcell/cell_options.go
Normal file
@ -0,0 +1,62 @@
|
||||
// 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 (
|
||||
"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(c&0x1ff) - 1
|
||||
}
|
||||
|
||||
// fixColor converts the target color for the current color mode
|
||||
func fixColor(c tcell.Color, colorMode terminalapi.ColorMode) tcell.Color {
|
||||
if c == tcell.ColorDefault {
|
||||
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, colorMode terminalapi.ColorMode) tcell.Style {
|
||||
st := tcell.StyleDefault
|
||||
|
||||
fg := cellColor(opts.FgColor)
|
||||
bg := cellColor(opts.BgColor)
|
||||
|
||||
fg = fixColor(fg, colorMode)
|
||||
bg = fixColor(bg, colorMode)
|
||||
|
||||
st = st.Foreground(fg).Background(bg)
|
||||
return st
|
||||
}
|
157
terminal/tcell/cell_options_test.go
Normal file
157
terminal/tcell/cell_options_test.go
Normal file
@ -0,0 +1,157 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/mum4k/termdash/cell"
|
||||
"github.com/mum4k/termdash/terminal/terminalapi"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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.ColorDefault},
|
||||
{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.ColorDefault},
|
||||
{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(58)},
|
||||
// Unknown color mode
|
||||
{-1, cell.ColorRed, tcell.ColorDefault},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
199
terminal/tcell/event.go
Normal file
199
terminal/tcell/event.go
Normal file
@ -0,0 +1,199 @@
|
||||
// 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 (
|
||||
"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(' ')
|
||||
|
||||
// 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,
|
||||
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,
|
||||
tcell.KeyCtrlSpace: keyboard.KeyCtrlSpace,
|
||||
}
|
||||
|
||||
// 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 %v", tcellKey, event.Name())
|
||||
}
|
||||
|
||||
return &terminalapi.Keyboard{
|
||||
Key: k,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 unsupported by termdash.
|
||||
func convMouse(event *tcell.EventMouse) terminalapi.Event {
|
||||
var button mouse.Button
|
||||
x, y := event.Position()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
case tcell.Button1:
|
||||
button = mouse.ButtonLeft
|
||||
case tcell.Button2:
|
||||
button = mouse.ButtonRight
|
||||
case tcell.Button3:
|
||||
button = mouse.ButtonMiddle
|
||||
default:
|
||||
// Unknown event to termdash
|
||||
return nil
|
||||
}
|
||||
|
||||
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.
|
||||
// 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:
|
||||
return []terminalapi.Event{
|
||||
terminalapi.NewError("event type EventInterrupt isn't supported"),
|
||||
}
|
||||
case *tcell.EventKey:
|
||||
return []terminalapi.Event{convKey(event)}
|
||||
case *tcell.EventMouse:
|
||||
mouseEvent := convMouse(event)
|
||||
if mouseEvent != nil {
|
||||
return []terminalapi.Event{mouseEvent}
|
||||
}
|
||||
return nil
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
281
terminal/tcell/event_test.go
Normal file
281
terminal/tcell/event_test.go
Normal file
@ -0,0 +1,281 @@
|
||||
// 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 (
|
||||
"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, 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), len(tc.want); got != want {
|
||||
t.Fatalf("toTermdashEvents => got %d events, want %d", got, want)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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[0] {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
200
terminal/tcell/tcell.go
Normal file
200
terminal/tcell/tcell.go
Normal file
@ -0,0 +1,200 @@
|
||||
// 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 (
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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 ColorDefault for foreground and 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.
|
||||
type Terminal struct {
|
||||
// events is a queue of input events.
|
||||
events *eventqueue.Unbound
|
||||
|
||||
// done gets closed when Close() is called.
|
||||
done chan struct{}
|
||||
|
||||
// the tcell terminal window
|
||||
screen tcell.Screen
|
||||
|
||||
// Options.
|
||||
colorMode terminalapi.ColorMode
|
||||
clearStyle *cell.Options
|
||||
}
|
||||
|
||||
// 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{}),
|
||||
colorMode: DefaultColorMode,
|
||||
clearStyle: &cell.Options{
|
||||
FgColor: cell.ColorDefault,
|
||||
BgColor: cell.ColorDefault,
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
clearStyle := cellOptsToStyle(t.clearStyle, t.colorMode)
|
||||
t.screen.EnableMouse()
|
||||
t.screen.SetStyle(clearStyle)
|
||||
|
||||
go t.pollEvents() // Stops when Close() is called.
|
||||
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, t.colorMode)
|
||||
t.screen.Fill(' ', 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.colorMode)
|
||||
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()
|
||||
}
|
116
terminal/tcell/tcell_test.go
Normal file
116
terminal/tcell/tcell_test.go
Normal file
@ -0,0 +1,116 @@
|
||||
// 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 (
|
||||
"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.ColorDefault,
|
||||
BgColor: cell.ColorDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user