From 4fcdf48ecbb4ac9fc82e80878d65310259e91dc5 Mon Sep 17 00:00:00 2001 From: kvnxiao Date: Fri, 14 Feb 2020 19:23:10 -0500 Subject: [PATCH] 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) + } + }) + } +}