From 02a0509e34c3ed74da7933f98cf6863fc41f42dc Mon Sep 17 00:00:00 2001 From: Aleksandr Krivoshchekov Date: Sat, 7 Nov 2020 08:43:12 +0300 Subject: [PATCH] Simplify code and add tests for `mouse.go` (#30) --- mouse.go | 70 +++++---- mouse_test.go | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 431 insertions(+), 26 deletions(-) create mode 100644 mouse_test.go diff --git a/mouse.go b/mouse.go index 446c600..90d147c 100644 --- a/mouse.go +++ b/mouse.go @@ -58,6 +58,7 @@ var mouseEventTypes = map[MouseEventType]string{ // // ESC [M Cb Cx Cy // +// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking func parseX10MouseEvent(buf []byte) (m MouseEvent, err error) { if len(buf) != 6 || string(buf[:3]) != "\x1b[M" { return m, errors.New("not an X10 mouse event") @@ -65,38 +66,55 @@ func parseX10MouseEvent(buf []byte) (m MouseEvent, err error) { e := buf[3] - 32 - switch e { - case 35: - m.Type = MouseMotion - case 64: - m.Type = MouseWheelUp - case 65: - m.Type = MouseWheelDown - default: - switch e & 3 { - case 0: - if e&64 != 0 { - m.Type = MouseWheelUp - } else { - m.Type = MouseLeft - } - case 1: - if e&64 != 0 { - m.Type = MouseWheelDown - } else { - m.Type = MouseMiddle - } - case 2: + const ( + bitShift = 0b0000_0100 + bitAlt = 0b0000_1000 + bitCtrl = 0b0001_0000 + bitMotion = 0b0010_0000 + bitWheel = 0b0100_0000 + + bitsMask = 0b0000_0011 + + bitsLeft = 0b0000_0000 + bitsMiddle = 0b0000_0001 + bitsRight = 0b0000_0010 + bitsRelease = 0b0000_0011 + + bitsWheelUp = 0b0000_0000 + bitsWheelDown = 0b0000_0001 + ) + + if e&bitWheel != 0 { + // Check the low two bits. + switch e & bitsMask { + case bitsWheelUp: + m.Type = MouseWheelUp + case bitsWheelDown: + m.Type = MouseWheelDown + } + } else { + // Check the low two bits. + // We do not separate clicking and dragging. + switch e & bitsMask { + case bitsLeft: + m.Type = MouseLeft + case bitsMiddle: + m.Type = MouseMiddle + case bitsRight: m.Type = MouseRight - case 3: - m.Type = MouseRelease + case bitsRelease: + if e&bitMotion != 0 { + m.Type = MouseMotion + } else { + m.Type = MouseRelease + } } } - if e&8 != 0 { + if e&bitAlt != 0 { m.Alt = true } - if e&16 != 0 { + if e&bitCtrl != 0 { m.Ctrl = true } diff --git a/mouse_test.go b/mouse_test.go new file mode 100644 index 0000000..9a4df8a --- /dev/null +++ b/mouse_test.go @@ -0,0 +1,387 @@ +package tea + +import "testing" + +func TestMouseEvent_String(t *testing.T) { + tt := []struct { + name string + event MouseEvent + expected string + }{ + { + name: "unknown", + event: MouseEvent{Type: MouseUnknown}, + expected: "unknown", + }, + { + name: "left", + event: MouseEvent{Type: MouseLeft}, + expected: "left", + }, + { + name: "right", + event: MouseEvent{Type: MouseRight}, + expected: "right", + }, + { + name: "middle", + event: MouseEvent{Type: MouseMiddle}, + expected: "middle", + }, + { + name: "release", + event: MouseEvent{Type: MouseRelease}, + expected: "release", + }, + { + name: "wheel up", + event: MouseEvent{Type: MouseWheelUp}, + expected: "wheel up", + }, + { + name: "wheel down", + event: MouseEvent{Type: MouseWheelDown}, + expected: "wheel down", + }, + { + name: "motion", + event: MouseEvent{Type: MouseMotion}, + expected: "motion", + }, + { + name: "alt+left", + event: MouseEvent{ + Type: MouseLeft, + Alt: true, + }, + expected: "alt+left", + }, + { + name: "ctrl+left", + event: MouseEvent{ + Type: MouseLeft, + Ctrl: true, + }, + expected: "ctrl+left", + }, + { + name: "ctrl+alt+left", + event: MouseEvent{ + Type: MouseLeft, + Alt: true, + Ctrl: true, + }, + expected: "ctrl+alt+left", + }, + { + name: "ignore coordinates", + event: MouseEvent{ + X: 100, + Y: 200, + Type: MouseLeft, + }, + expected: "left", + }, + { + name: "broken type", + event: MouseEvent{ + Type: MouseEventType(-1000), + }, + expected: "", + }, + } + + for i := range tt { + tc := tt[i] + + t.Run(tc.name, func(t *testing.T) { + actual := tc.event.String() + + if tc.expected != actual { + t.Fatalf("expected %q but got %q", + tc.expected, + actual, + ) + } + }) + } +} + +func TestParseX10MouseEvent(t *testing.T) { + encode := func(b byte, x, y int) []byte { + return []byte{ + '\x1b', + '[', + 'M', + byte(32) + b, + byte(x + 32 + 1), + byte(y + 32 + 1), + } + } + + tt := []struct { + name string + buf []byte + expected MouseEvent + }{ + // Position. + { + name: "zero position", + buf: encode(0b0010_0000, 0, 0), + expected: MouseEvent{ + X: 0, + Y: 0, + Type: MouseLeft, + }, + }, + { + name: "max position", + buf: encode(0b0010_0000, 222, 222), // Because 255 (max int8) - 32 - 1. + expected: MouseEvent{ + X: 222, + Y: 222, + Type: MouseLeft, + }, + }, + // Simple. + { + name: "left", + buf: encode(0b0000_0000, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseLeft, + }, + }, + { + name: "left in motion", + buf: encode(0b0010_0000, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseLeft, + }, + }, + { + name: "middle", + buf: encode(0b0000_0001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMiddle, + }, + }, + { + name: "middle in motion", + buf: encode(0b0010_0001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMiddle, + }, + }, + { + name: "right", + buf: encode(0b0000_0010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + }, + }, + { + name: "right in motion", + buf: encode(0b0010_0010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + }, + }, + { + name: "motion", + buf: encode(0b0010_0011, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseMotion, + }, + }, + { + name: "wheel up", + buf: encode(0b0100_0000, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelUp, + }, + }, + { + name: "wheel down", + buf: encode(0b0100_0001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + }, + }, + { + name: "release", + buf: encode(0b0000_0011, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRelease, + }, + }, + // Combinations. + { + name: "alt+right", + buf: encode(0b0010_1010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Alt: true, + }, + }, + { + name: "ctrl+right", + buf: encode(0b0011_0010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Ctrl: true, + }, + }, + { + name: "ctrl+alt+right", + buf: encode(0b0011_1010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseRight, + Alt: true, + Ctrl: true, + }, + }, + { + name: "alt+wheel down", + buf: encode(0b0100_1001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Alt: true, + }, + }, + { + name: "ctrl+wheel down", + buf: encode(0b0101_0001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Ctrl: true, + }, + }, + { + name: "ctrl+alt+wheel down", + buf: encode(0b0101_1001, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseWheelDown, + Alt: true, + Ctrl: true, + }, + }, + // Unknown. + { + name: "wheel with unknown bit", + buf: encode(0b0100_0010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseUnknown, + }, + }, + { + name: "unknown with modifier", + buf: encode(0b0100_1010, 32, 16), + expected: MouseEvent{ + X: 32, + Y: 16, + Type: MouseUnknown, + Alt: true, + }, + }, + // Overflow position. + { + name: "overflow position", + buf: encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1. + expected: MouseEvent{ + X: -6, + Y: -33, + Type: MouseLeft, + }, + }, + } + + for i := range tt { + tc := tt[i] + + t.Run(tc.name, func(t *testing.T) { + actual, err := parseX10MouseEvent(tc.buf) + if err != nil { + t.Fatalf("unexpected error: %v", + err, + ) + } + + if tc.expected != actual { + t.Fatalf("expected %#v but got %#v", + tc.expected, + actual, + ) + } + }) + } +} + +func TestParseX10MouseEvent_error(t *testing.T) { + tt := []struct { + name string + buf []byte + }{ + { + name: "empty buf", + buf: nil, + }, + { + name: "wrong high bit", + buf: []byte("\x1a[M@A1"), + }, + { + name: "short buf", + buf: []byte("\x1b[M@A"), + }, + { + name: "long buf", + buf: []byte("\x1b[M@A11"), + }, + } + + for i := range tt { + tc := tt[i] + + t.Run(tc.name, func(t *testing.T) { + _, err := parseX10MouseEvent(tc.buf) + + if err == nil { + t.Fatalf("expected error but got nil") + } + }) + } +}