okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Drew Smirnofffix: draw graphene clusters wider than 4 columns (#150)006e29f

upstream
63.0 KiB2746 linesraw
1package uv
2
3import (
4	"bytes"
5	"context"
6	"flag"
7	"fmt"
8	"image/color"
9	"io"
10	"math/rand"
11	"reflect"
12	"regexp"
13	"sort"
14	"strings"
15	"testing"
16	"time"
17
18	"github.com/charmbracelet/x/ansi"
19	"github.com/charmbracelet/x/ansi/kitty"
20	"github.com/charmbracelet/x/ansi/parser"
21	"github.com/lucasb-eyer/go-colorful"
22)
23
24var sequences = buildKeysTable(LegacyKeyEncoding(0), "dumb", true)
25
26func TestKeyString(t *testing.T) {
27	t.Run("alt+space", func(t *testing.T) {
28		k := KeyPressEvent{Code: KeySpace, Mod: ModAlt}
29		if got := k.String(); got != "alt+space" {
30			t.Fatalf(`expected a "alt+space", got %q`, got)
31		}
32	})
33
34	t.Run("runes", func(t *testing.T) {
35		k := KeyPressEvent{Code: 'a', Text: "a"}
36		if got := k.String(); got != "a" {
37			t.Fatalf(`expected an "a", got %q`, got)
38		}
39	})
40
41	t.Run("invalid", func(t *testing.T) {
42		k := KeyPressEvent{Code: 99999}
43		if got := k.String(); got != "𘚟" {
44			t.Fatalf(`expected a "unknown", got %q`, got)
45		}
46	})
47}
48
49type seqTest struct {
50	seq    []byte
51	Events []Event
52}
53
54var f3CurPosRegexp = regexp.MustCompile(`\x1b\[1;(\d+)R`)
55
56// buildBaseSeqTests returns sequence tests that are valid for the
57// detectSequence() function.
58func buildBaseSeqTests() []seqTest {
59	td := []seqTest{}
60	for seq, key := range sequences {
61		k := KeyPressEvent(key)
62		st := seqTest{seq: []byte(seq), Events: []Event{k}}
63
64		// XXX: This is a special case to handle F3 key sequence and cursor
65		// position report having the same sequence. See [parseCsi] for more
66		// information.
67		if f3CurPosRegexp.MatchString(seq) {
68			st.Events = []Event{k, CursorPositionEvent{Y: 0, X: int(key.Mod)}}
69		}
70		td = append(td, st)
71	}
72
73	// Additional special cases.
74	td = append(td,
75		// Unrecognized CSI sequence.
76		seqTest{
77			[]byte{'\x1b', '[', '-', '-', '-', '-', 'X'},
78			[]Event{
79				UnknownCsiEvent([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'}),
80			},
81		},
82		// A lone space character.
83		seqTest{
84			[]byte{' '},
85			[]Event{
86				KeyPressEvent{Code: KeySpace, Text: " "},
87			},
88		},
89		// An escape character with the alt modifier.
90		seqTest{
91			[]byte{'\x1b', ' '},
92			[]Event{
93				KeyPressEvent{Code: KeySpace, Mod: ModAlt},
94			},
95		},
96	)
97	return td
98}
99
100func TestFocus(t *testing.T) {
101	var p EventDecoder
102	_, e := p.Decode([]byte("\x1b[I"))
103	switch e.(type) {
104	case FocusEvent:
105		// ok
106	default:
107		t.Error("invalid sequence")
108	}
109}
110
111func TestBlur(t *testing.T) {
112	var p EventDecoder
113	_, e := p.Decode([]byte("\x1b[O"))
114	switch e.(type) {
115	case BlurEvent:
116		// ok
117	default:
118		t.Error("invalid sequence")
119	}
120}
121
122func TestParseSequence(t *testing.T) {
123	td := buildBaseSeqTests()
124	td = append(td,
125		// OSC 11 response
126		seqTest{
127			[]byte("\x1b]11;rgb:ffff/0000/ffff\x07"),
128			[]Event{
129				BackgroundColorEvent{ansi.XParseColor("rgb:ff/00/ff")},
130			},
131		},
132
133		// Teritary Device Attributes (DA3)
134		seqTest{
135			[]byte("\x1bP!|4368726d\x1b\\"),
136			[]Event{
137				TertiaryDeviceAttributesEvent("Chrm"),
138			},
139		},
140
141		// XTGETTCAP response
142		seqTest{
143			[]byte("\x1bP1+r524742\x1b\\"),
144			[]Event{
145				CapabilityEvent{"RGB"},
146			},
147		},
148
149		// Unknown sequences.
150		seqTest{
151			[]byte("\x1b[z\x1bOz\x1bO2 \x1bP?1;2:3+zABC\x1b\\"),
152			[]Event{
153				UnknownCsiEvent("\x1b[z"),
154				UnknownSs3Event("\x1bOz"),
155				UnknownEvent("\x1bO2"),
156				KeyPressEvent{Code: KeySpace, Text: " "},
157				UnknownDcsEvent("\x1bP?1;2:3+zABC\x1b\\"),
158			},
159		},
160
161		// OSC 52 read clipboard
162		seqTest{
163			[]byte("\x1b]52\x1b\\\x1b]52;c;!\x1b\\\x1b]52;c;aGk=\x1b\\"),
164			[]Event{
165				ClipboardEvent{},
166				ClipboardEvent{Content: "!"},
167				ClipboardEvent{Content: "hi", Selection: 'c'},
168			},
169		},
170
171		// Invalid Xterm modifyOtherKeys key sequence
172		seqTest{
173			[]byte("\x1b[27;3~"),
174			[]Event{UnknownCsiEvent("\x1b[27;3~")},
175		},
176
177		// Empty @ ^ ~
178		seqTest{
179			[]byte("\x1b[@\x1b[^\x1b[~"),
180			[]Event{
181				UnknownCsiEvent("\x1b[@"),
182				UnknownCsiEvent("\x1b[^"),
183				UnknownCsiEvent("\x1b[~"),
184			},
185		},
186
187		// Win32 input mode key sequences
188		seqTest{
189			[]byte("\x1b[65;0;97;1;0;1_\x1b[0;0;0_"),
190			[]Event{
191				KeyPressEvent{Code: 'a', BaseCode: 'a', Text: "a"},
192				UnknownCsiEvent("\x1b[0;0;0_"),
193			},
194		},
195
196		// Report mode responses
197		seqTest{
198			[]byte("\x1b[2;1$y\x1b[$y\x1b[2$y\x1b[2;$y"),
199			[]Event{
200				ModeReportEvent{Mode: ansi.ModeKeyboardAction, Value: ansi.ModeSet},
201				UnknownCsiEvent("\x1b[$y"),
202				UnknownCsiEvent("\x1b[2$y"),
203				ModeReportEvent{Mode: ansi.ModeKeyboardAction, Value: ansi.ModeNotRecognized},
204			},
205		},
206
207		// Short X10 mouse input
208		seqTest{
209			[]byte("\x1b[M !"),
210			[]Event{
211				UnknownCsiEvent("\x1b[M"),
212				KeyPressEvent{Code: ' ', Text: " "},
213				KeyPressEvent{Code: '!', Text: "!"},
214			},
215		},
216
217		// Invalid report mode responses
218		seqTest{
219			[]byte("\x1b[?$y\x1b[?1049$y\x1b[?1049;$y"),
220			[]Event{
221				UnknownCsiEvent("\x1b[?$y"),
222				UnknownCsiEvent("\x1b[?1049$y"),
223				ModeReportEvent{Mode: ansi.ModeAltScreenSaveCursor, Value: ansi.ModeNotRecognized},
224			},
225		},
226
227		// Xterm modifyOtherKeys response
228		seqTest{
229			[]byte("\x1b[>4;1m\x1b[>4m\x1b[>3m"),
230			[]Event{
231				ModifyOtherKeysEvent{1},
232				UnknownCsiEvent("\x1b[>4m"),
233				UnknownCsiEvent("\x1b[>3m"),
234			},
235		},
236
237		// F3 with modifier key press or cursor position report
238		seqTest{
239			[]byte("\x1b[1;5R\x1b[1;5;7R"),
240			[]Event{
241				KeyPressEvent{Code: KeyF3, Mod: ModCtrl},
242				CursorPositionEvent{Y: 0, X: 4},
243				UnknownCsiEvent("\x1b[1;5;7R"),
244			},
245		},
246
247		// Cursor position report
248		seqTest{
249			[]byte("\x1b[?12;34R\x1b[?14R"),
250			[]Event{
251				CursorPositionEvent{Y: 11, X: 33},
252				UnknownCsiEvent("\x1b[?14R"),
253			},
254		},
255
256		// Unknown CSI sequence
257		seqTest{
258			[]byte("\x1b[10;2;3c"),
259			[]Event{UnknownCsiEvent([]byte("\x1b[10;2;3c"))},
260		},
261
262		// Kitty Keyboard response
263		seqTest{
264			[]byte("\x1b[?16u\x1b[?u"),
265			[]Event{
266				KeyboardEnhancementsEvent{16},
267				KeyboardEnhancementsEvent{0},
268			},
269		},
270
271		// Secondary Device Attributes (DA2)
272		seqTest{
273			[]byte("\x1b[>1;2;3c"),
274			[]Event{SecondaryDeviceAttributesEvent{1, 2, 3}},
275		},
276
277		// Primary Device Attributes (DA1)
278		seqTest{
279			[]byte("\x1b[?1;2;3c"),
280			[]Event{PrimaryDeviceAttributesEvent{1, 2, 3}},
281		},
282
283		// esc followed by non-key event sequence
284		seqTest{
285			[]byte("\x1b\x1b[?2004;1$y"),
286			[]Event{
287				KeyPressEvent{Code: KeyEscape},
288				ModeReportEvent{Mode: ansi.ModeBracketedPaste, Value: ansi.ModeSet},
289			},
290		},
291
292		// 8-bit sequences
293		seqTest{
294			[]byte(
295				"\x9bA" + // CSI A
296					"\x8fA" + // SS3 A
297					"\x90>|Ultraviolet\x1b\\" + // DCS >|Ultraviolet ST
298					"\x9d11;#123456\x9c" + // OSC 11 ; #123456 ST
299					"\x98hi\x9c" + // SOS hi ST
300					"\x9fhello\x9c" + // APC hello ST
301					"\x9ebye\x9c", // PM bye ST
302			),
303			[]Event{
304				KeyPressEvent{Code: KeyUp},
305				KeyPressEvent{Code: KeyUp},
306				TerminalVersionEvent{"Ultraviolet"},
307				BackgroundColorEvent{ansi.XParseColor("#123456")},
308				UnknownSosEvent("\x98hi\x9c"),
309				UnknownApcEvent("\x9fhello\x9c"),
310				UnknownPmEvent("\x9ebye\x9c"),
311			},
312		},
313
314		// Empty input
315		seqTest{
316			[]byte(""),
317			[]Event(nil),
318		},
319
320		// Broken escape sequence introducers.
321		seqTest{
322			[]byte("\x1b["), // CSI
323			[]Event{KeyPressEvent{Code: '[', Mod: ModAlt}},
324		},
325		seqTest{
326			[]byte("\x1b]"), // OSC
327			[]Event{KeyPressEvent{Code: ']', Mod: ModAlt}},
328		},
329		seqTest{
330			[]byte("\x1b^"), // PM
331			[]Event{KeyPressEvent{Code: '^', Mod: ModAlt}},
332		},
333		seqTest{
334			[]byte("\x1b_"), // APC
335			[]Event{KeyPressEvent{Code: '_', Mod: ModAlt}},
336		},
337		seqTest{
338			[]byte("\x1bP"), // DCS
339			[]Event{KeyPressEvent{Code: 'p', Mod: ModShift | ModAlt}},
340		},
341		seqTest{
342			[]byte("\x1bX"), // SOS
343			[]Event{KeyPressEvent{Code: 'x', Mod: ModShift | ModAlt}},
344		},
345		seqTest{
346			[]byte("\x1bO"), // SS3
347			[]Event{KeyPressEvent{Code: 'o', Mod: ModShift | ModAlt}},
348		},
349		seqTest{
350			[]byte("\x1b"), // ESC
351			[]Event{KeyPressEvent{Code: KeyEscape}},
352		},
353
354		// Kitty invalid key sequence
355		seqTest{
356			[]byte("\x1b[u"),
357			[]Event{UnknownCsiEvent("\x1b[u")},
358		},
359
360		// Kitty printable keys with lock modifiers.
361		seqTest{
362			[]byte("\x1b[97;65u" + // caps lock on
363				"\x1b[97;2u" + // shift pressed
364				"\x1b[97;65u" + // caps lock on
365				"\x1b[97;66u" + // caps lock on and shift pressed
366				"\x1b[97;129u" + // num lock on
367				"\x1b[97;130u" + // num lock on and shift pressed
368				"\x1b[97;194u"), // num lock on and caps lock on and shift pressed
369			[]Event{
370				KeyPressEvent{Code: 'a', Text: "A", Mod: ModCapsLock},
371				KeyPressEvent{Code: 'a', Text: "A", Mod: ModShift},
372				KeyPressEvent{Code: 'a', Text: "A", Mod: ModCapsLock},
373				KeyPressEvent{Code: 'a', Text: "A", Mod: ModCapsLock | ModShift},
374				KeyPressEvent{Code: 'a', Text: "a", Mod: ModNumLock},
375				KeyPressEvent{Code: 'a', Text: "A", Mod: ModNumLock | ModShift},
376				KeyPressEvent{Code: 'a', Text: "A", Mod: ModNumLock | ModCapsLock | ModShift},
377			},
378		},
379
380		// Kitty NumPad keys.
381		seqTest{
382			[]byte("\x1b[57399u" +
383				"\x1b[57400u" +
384				"\x1b[57401u" +
385				"\x1b[57402u" +
386				"\x1b[57403u" +
387				"\x1b[57404u" +
388				"\x1b[57405u" +
389				"\x1b[57406u" +
390				"\x1b[57407u" +
391				"\x1b[57408u" +
392				"\x1b[57409u" +
393				"\x1b[57410u" +
394				"\x1b[57411u" +
395				"\x1b[57412u" +
396				"\x1b[57413u" +
397				"\x1b[57414u" +
398				"\x1b[57415u" +
399				"\x1b[57416u" +
400				"\x1b[57417u" +
401				"\x1b[57418u" +
402				"\x1b[57419u" +
403				"\x1b[57420u" +
404				"\x1b[57421u" +
405				"\x1b[57422u" +
406				"\x1b[57423u" +
407				"\x1b[57424u" +
408				"\x1b[57425u"),
409			[]Event{
410				KeyPressEvent{Code: KeyKp0, Text: "0"},
411				KeyPressEvent{Code: KeyKp1, Text: "1"},
412				KeyPressEvent{Code: KeyKp2, Text: "2"},
413				KeyPressEvent{Code: KeyKp3, Text: "3"},
414				KeyPressEvent{Code: KeyKp4, Text: "4"},
415				KeyPressEvent{Code: KeyKp5, Text: "5"},
416				KeyPressEvent{Code: KeyKp6, Text: "6"},
417				KeyPressEvent{Code: KeyKp7, Text: "7"},
418				KeyPressEvent{Code: KeyKp8, Text: "8"},
419				KeyPressEvent{Code: KeyKp9, Text: "9"},
420				KeyPressEvent{Code: KeyKpDecimal, Text: "."},
421				KeyPressEvent{Code: KeyKpDivide, Text: "/"},
422				KeyPressEvent{Code: KeyKpMultiply, Text: "*"},
423				KeyPressEvent{Code: KeyKpMinus, Text: "-"},
424				KeyPressEvent{Code: KeyKpPlus, Text: "+"},
425				KeyPressEvent{Code: KeyKpEnter},
426				KeyPressEvent{Code: KeyKpEqual, Text: "="},
427				KeyPressEvent{Code: KeyKpSep, Text: ","},
428				KeyPressEvent{Code: KeyKpLeft},
429				KeyPressEvent{Code: KeyKpRight},
430				KeyPressEvent{Code: KeyKpUp},
431				KeyPressEvent{Code: KeyKpDown},
432				KeyPressEvent{Code: KeyKpPgUp},
433				KeyPressEvent{Code: KeyKpPgDown},
434				KeyPressEvent{Code: KeyKpHome},
435				KeyPressEvent{Code: KeyKpEnd},
436				KeyPressEvent{Code: KeyKpInsert},
437			},
438		},
439
440		// Invalid CSI sequence.
441		seqTest{
442			[]byte("\x1b[?2004;1$y"),
443			[]Event{ModeReportEvent{Mode: ansi.ModeBracketedPaste, Value: ansi.ModeSet}},
444		},
445
446		// Invalid CSI sequence.
447		seqTest{
448			[]byte("\x1b[?2004;1$"),
449			[]Event{UnknownEvent("\x1b[?2004;1$")},
450		},
451
452		// Light/dark color scheme reports.
453		seqTest{
454			[]byte("\x1b[?997;1n"),
455			[]Event{DarkColorSchemeEvent{}},
456		},
457		seqTest{
458			[]byte("\x1b[?997;2n"),
459			[]Event{LightColorSchemeEvent{}},
460		},
461
462		// ESC [ [ansi.CSI]
463		seqTest{
464			[]byte("\x1b["),
465			[]Event{KeyPressEvent{Code: '[', Mod: ModAlt}},
466		},
467		// ESC ] [ansi.OSC]
468		seqTest{
469			[]byte("\x1b]"),
470			[]Event{KeyPressEvent{Code: ']', Mod: ModAlt}},
471		},
472		// ESC ^ [ansi.PM]
473		seqTest{
474			[]byte("\x1b^"),
475			[]Event{KeyPressEvent{Code: '^', Mod: ModAlt}},
476		},
477		// ESC _ [ansi.APC]
478		seqTest{
479			[]byte("\x1b_"),
480			[]Event{KeyPressEvent{Code: '_', Mod: ModAlt}},
481		},
482		// ESC p
483		seqTest{
484			[]byte("\x1bp"),
485			[]Event{KeyPressEvent{Code: 'p', Mod: ModAlt}},
486		},
487		// ESC P [ansi.DCS]
488		seqTest{
489			[]byte("\x1bP"),
490			[]Event{KeyPressEvent{Code: 'p', Mod: ModShift | ModAlt}},
491		},
492		// ESC x
493		seqTest{
494			[]byte("\x1bx"),
495			[]Event{KeyPressEvent{Code: 'x', Mod: ModAlt}},
496		},
497		// ESC X [ansi.SOS]
498		seqTest{
499			[]byte("\x1bX"),
500			[]Event{KeyPressEvent{Code: 'x', Mod: ModShift | ModAlt}},
501		},
502
503		// OSC 11 with ST termination.
504		seqTest{
505			[]byte("\x1b]11;#123456\x1b\\"),
506			[]Event{BackgroundColorEvent{
507				Color: func() color.Color {
508					c, _ := colorful.Hex("#123456")
509					return c
510				}(),
511			}},
512		},
513
514		// Kitty Graphics response.
515		seqTest{
516			[]byte("\x1b_Ga=t;OK\x1b\\"),
517			[]Event{KittyGraphicsEvent{
518				Options: kitty.Options{Action: kitty.Transmit},
519				Payload: []byte("OK"),
520			}},
521		},
522		seqTest{
523			[]byte("\x1b_Gi=99,I=13;OK\x1b\\"),
524			[]Event{KittyGraphicsEvent{
525				Options: kitty.Options{ID: 99, Number: 13},
526				Payload: []byte("OK"),
527			}},
528		},
529		seqTest{
530			[]byte("\x1b_Gi=1337,q=1;EINVAL:your face\x1b\\"),
531			[]Event{KittyGraphicsEvent{
532				Options: kitty.Options{ID: 1337, Quiet: 1},
533				Payload: []byte("EINVAL:your face"),
534			}},
535		},
536
537		// Xterm modifyOtherKeys CSI 27 ; <modifier> ; <code> ~
538		seqTest{
539			[]byte("\x1b[27;3;20320~"),
540			[]Event{KeyPressEvent{Code: '你', Mod: ModAlt}},
541		},
542		seqTest{
543			[]byte("\x1b[27;3;65~"),
544			[]Event{KeyPressEvent{Code: 'A', Mod: ModAlt}},
545		},
546		seqTest{
547			[]byte("\x1b[27;3;8~"),
548			[]Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
549		},
550		seqTest{
551			[]byte("\x1b[27;3;27~"),
552			[]Event{KeyPressEvent{Code: KeyEscape, Mod: ModAlt}},
553		},
554		seqTest{
555			[]byte("\x1b[27;3;127~"),
556			[]Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
557		},
558
559		// Xterm report window text area size in pixels.
560		seqTest{
561			[]byte("\x1b[4;24;80t" + // window pixel size
562				"\x1b[6;13;7t" + // single cell size
563				"\x1b[8;24;80t" + // window cells area size
564				"\x1b[48;24;80;312;560t" + // in-band resize response
565				"\x1b[t" + // invalid
566				"\x1b[999t" + // invalid
567				"\x1b[999;1t" + // invalid
568				""),
569			[]Event{
570				PixelSizeEvent{Width: 80, Height: 24},
571				CellSizeEvent{Width: 7, Height: 13},
572				WindowSizeEvent{Width: 80, Height: 24},
573				WindowSizeEvent{Width: 80, Height: 24},
574				PixelSizeEvent{Width: 560, Height: 312},
575				UnknownCsiEvent("\x1b[t"),
576				WindowOpEvent{Op: 999},
577				WindowOpEvent{Op: 999, Args: []int{1}},
578			},
579		},
580
581		// Kitty keyboard / CSI u (fixterms)
582		seqTest{
583			[]byte("\x1b[1B"),
584			[]Event{KeyPressEvent{Code: KeyDown}},
585		},
586		seqTest{
587			[]byte("\x1b[1;B"),
588			[]Event{KeyPressEvent{Code: KeyDown}},
589		},
590		seqTest{
591			[]byte("\x1b[1;4B"),
592			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
593		},
594		seqTest{
595			[]byte("\x1b[1;4:1B"),
596			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
597		},
598		seqTest{
599			[]byte("\x1b[1;4:2B"),
600			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyDown, IsRepeat: true}},
601		},
602		seqTest{
603			[]byte("\x1b[1;4:3B"),
604			[]Event{KeyReleaseEvent{Mod: ModShift | ModAlt, Code: KeyDown}},
605		},
606		seqTest{
607			[]byte("\x1b[8~"),
608			[]Event{KeyPressEvent{Code: KeyEnd}},
609		},
610		seqTest{
611			[]byte("\x1b[8;~"),
612			[]Event{KeyPressEvent{Code: KeyEnd}},
613		},
614		seqTest{
615			[]byte("\x1b[8;10~"),
616			[]Event{KeyPressEvent{Mod: ModShift | ModMeta, Code: KeyEnd}},
617		},
618		seqTest{
619			[]byte("\x1b[6;6:2~"),
620			[]Event{KeyPressEvent{Mod: ModShift | ModCtrl, Code: KeyPgDown, IsRepeat: true}},
621		},
622		seqTest{
623			[]byte("\x1b[6;6:3~"),
624			[]Event{KeyReleaseEvent{Mod: ModShift | ModCtrl, Code: KeyPgDown}},
625		},
626		seqTest{
627			[]byte("\x1b[27;4u"),
628			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyEscape}},
629		},
630		seqTest{
631			[]byte("\x1b[127;4u"),
632			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyBackspace}},
633		},
634		seqTest{
635			[]byte("\x1b[57358;4u"),
636			[]Event{KeyPressEvent{Mod: ModShift | ModAlt, Code: KeyCapsLock}},
637		},
638		seqTest{
639			[]byte("\x1b[9;2u"),
640			[]Event{KeyPressEvent{Mod: ModShift, Code: KeyTab}},
641		},
642		seqTest{
643			[]byte("\x1b[195;u"),
644			[]Event{KeyPressEvent{Text: "Ã", Code: 'Ã'}},
645		},
646		seqTest{
647			[]byte("\x1b[20320;2u"),
648			[]Event{KeyPressEvent{Text: "你", Mod: ModShift, Code: '你'}},
649		},
650		seqTest{
651			[]byte("\x1b[195;:1u"),
652			[]Event{KeyPressEvent{Text: "Ã", Code: 'Ã'}},
653		},
654		seqTest{
655			[]byte("\x1b[195;2:3u"),
656			[]Event{KeyReleaseEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
657		},
658		seqTest{
659			[]byte("\x1b[195;2:2u"),
660			[]Event{KeyPressEvent{Code: 'Ã', Text: "Ã", IsRepeat: true, Mod: ModShift}},
661		},
662		seqTest{
663			[]byte("\x1b[195;2:1u"),
664			[]Event{KeyPressEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
665		},
666		seqTest{
667			[]byte("\x1b[195;2:3u"),
668			[]Event{KeyReleaseEvent{Code: 'Ã', Text: "Ã", Mod: ModShift}},
669		},
670		seqTest{
671			[]byte("\x1b[97;2;65u"),
672			[]Event{KeyPressEvent{Code: 'a', Text: "A", Mod: ModShift}},
673		},
674		seqTest{
675			[]byte("\x1b[97;;229u"),
676			[]Event{KeyPressEvent{Code: 'a', Text: "å"}},
677		},
678
679		// focus/blur
680		seqTest{
681			[]byte{'\x1b', '[', 'I'},
682			[]Event{
683				FocusEvent{},
684			},
685		},
686		seqTest{
687			[]byte{'\x1b', '[', 'O'},
688			[]Event{
689				BlurEvent{},
690			},
691		},
692		// Mouse event.
693		seqTest{
694			[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
695			[]Event{
696				MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
697			},
698		},
699		// SGR Mouse event.
700		seqTest{
701			[]byte("\x1b[<0;33;17M"),
702			[]Event{
703				MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
704			},
705		},
706		// Runes.
707		seqTest{
708			[]byte{'a'},
709			[]Event{
710				KeyPressEvent{Code: 'a', Text: "a"},
711			},
712		},
713		seqTest{
714			[]byte{'\x1b', 'a'},
715			[]Event{
716				KeyPressEvent{Code: 'a', Mod: ModAlt},
717			},
718		},
719		seqTest{
720			[]byte{'a', 'a', 'a'},
721			[]Event{
722				KeyPressEvent{Code: 'a', Text: "a"},
723				KeyPressEvent{Code: 'a', Text: "a"},
724				KeyPressEvent{Code: 'a', Text: "a"},
725			},
726		},
727		// Multi-byte rune.
728		seqTest{
729			[]byte("☃"),
730			[]Event{
731				KeyPressEvent{Code: '☃', Text: "☃"},
732			},
733		},
734		seqTest{
735			[]byte("\x1b☃"),
736			[]Event{
737				KeyPressEvent{Code: '☃', Mod: ModAlt},
738			},
739		},
740		// Standalone control characters.
741		seqTest{
742			[]byte{'\x1b'},
743			[]Event{
744				KeyPressEvent{Code: KeyEscape},
745			},
746		},
747		seqTest{
748			[]byte{ansi.SOH},
749			[]Event{
750				KeyPressEvent{Code: 'a', Mod: ModCtrl},
751			},
752		},
753		seqTest{
754			[]byte{'\x1b', ansi.SOH},
755			[]Event{
756				KeyPressEvent{Code: 'a', Mod: ModCtrl | ModAlt},
757			},
758		},
759		seqTest{
760			[]byte{ansi.NUL},
761			[]Event{
762				KeyPressEvent{Code: KeySpace, Mod: ModCtrl},
763			},
764		},
765		seqTest{
766			[]byte{'\x1b', ansi.NUL},
767			[]Event{
768				KeyPressEvent{Code: KeySpace, Mod: ModCtrl | ModAlt},
769			},
770		},
771		// C1 control characters.
772		seqTest{
773			[]byte{'\x80'},
774			[]Event{
775				KeyPressEvent{Code: rune(0x80 - '@'), Mod: ModCtrl | ModAlt},
776			},
777		},
778	)
779
780	if !isWindows {
781		// Sadly, utf8.DecodeRune([]byte(0xfe)) returns a valid rune on windows.
782		// This is incorrect, but it makes our test fail if we try it out.
783		td = append(td, seqTest{
784			[]byte{'\xfe'},
785			[]Event{
786				UnknownEvent(rune(0xfe)),
787			},
788		})
789	}
790
791	var p EventDecoder
792	for _, tc := range td {
793		t.Run(fmt.Sprintf("%q", string(tc.seq)), func(t *testing.T) {
794			var events []Event
795			buf := tc.seq
796			for len(buf) > 0 {
797				width, Event := p.Decode(buf)
798				switch Event := Event.(type) {
799				case MultiEvent:
800					events = append(events, Event...)
801				default:
802					events = append(events, Event)
803				}
804				buf = buf[width:]
805			}
806			if len(tc.Events) != len(events) {
807				t.Fatalf("\nexpected %d events for %q:\n    %#v\ngot %d:\n    %#v", len(tc.Events), tc.seq, tc.Events, len(events), events)
808			}
809			for i := range tc.Events {
810				if !reflect.DeepEqual(tc.Events[i], events[i]) {
811					t.Errorf("\nexpected event %d for %q:\n    %#v\ngot:\n    %#v", i, tc.seq, tc.Events[i], events[i])
812				}
813			}
814		})
815	}
816}
817
818func TestEmptyBufferDecode(t *testing.T) {
819	var p EventDecoder
820	if n, e := p.Decode([]byte{}); n != 0 || e != nil {
821		t.Errorf("expected (0, nil), got (%d, %v)", n, e)
822	}
823}
824
825func TestSplitReads(t *testing.T) {
826	expect := []Event{
827		KeyPressEvent{Code: 'a', Text: "a"},
828		KeyPressEvent{Code: 'b', Text: "b"},
829		KeyPressEvent{Code: 'c', Text: "c"},
830		KeyPressEvent{Code: KeyUp},
831		MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
832		FocusEvent{},
833		MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
834		BlurEvent{},
835		MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
836		KeyPressEvent{Code: KeyUp},
837		MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
838		MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
839		FocusEvent{},
840		UnknownEvent("\x1b[12;34;9"),
841	}
842	inputs := []string{
843		"abc",
844		"\x1b[A",
845		"\x1b[<0;33",
846		";17M",
847		"\x1b[I",
848		"\x1b",
849		"[",
850		"<",
851		"0",
852		";",
853		"3",
854		"3",
855		";",
856		"1",
857		"7",
858		"M",
859		"\x1b[O",
860		"\x1b",
861		"]",
862		"2",
863		";",
864		"a",
865		"b",
866		"c",
867		"\x1b",
868		"\x1b[",
869		"<0;3",
870		"3;17M",
871		"\x1b[A\x1b[",
872		"<0;33;17M\x1b[",
873		"<0;33;17M\x1b[I",
874		"\x1b[12;34;9",
875	}
876
877	r := LimitedReader(strings.NewReader(strings.Join(inputs, "")), 8)
878	drv := NewTerminalReader(r, "dumb")
879	drv.SetLogger(TLogger{t})
880
881	eventc := make(chan Event)
882	go func(t testing.TB) {
883		defer close(eventc)
884		if err := drv.StreamEvents(t.Context(), eventc); err != nil {
885			t.Errorf("error streaming events: %v", err)
886		}
887	}(t)
888
889	var events []Event
890	for ev := range eventc {
891		events = append(events, ev)
892	}
893
894	if !reflect.DeepEqual(expect, events) {
895		t.Errorf("unexpected messages, expected:\n    %+v\ngot:\n    %+v", expect, events)
896	}
897}
898
899func TestReadLongInput(t *testing.T) {
900	expect := make([]Event, 1000)
901	for i := 0; i < 1000; i++ {
902		expect[i] = KeyPressEvent{Code: 'a', Text: "a"}
903	}
904	input := strings.Repeat("a", 1000)
905	rdr := strings.NewReader(input)
906	drv := NewTerminalReader(rdr, "dumb")
907
908	eventc := make(chan Event)
909	go func(t testing.TB) {
910		defer close(eventc)
911		if err := drv.StreamEvents(context.TODO(), eventc); err != nil {
912			t.Errorf("error streaming events: %v", err)
913		}
914	}(t)
915
916	var events []Event
917	for ev := range eventc {
918		events = append(events, ev)
919	}
920
921	if !reflect.DeepEqual(expect, events) {
922		t.Errorf("unexpected messages, expected:\n    %+v\ngot:\n    %+v", expect, events)
923	}
924}
925
926func TestReadInput(t *testing.T) {
927	type test struct {
928		keyname string
929		in      []byte
930		out     []Event
931	}
932	testData := []test{
933		{
934			"non-serialized single win32 esc",
935			[]byte("\x1b"),
936			[]Event{
937				KeyPressEvent{Code: KeyEscape},
938			},
939		},
940
941		{
942			"serialized win32 esc",
943			[]byte("\x1b[27;0;27;1;0;1_abc\x1b[0;0;55357;1;0;1_\x1b[0;0;56835;1;0;1_ "),
944			[]Event{
945				KeyPressEvent{Code: KeyEscape, BaseCode: KeyEscape},
946				KeyPressEvent{Code: 'a', Text: "a"},
947				KeyPressEvent{Code: 'b', Text: "b"},
948				KeyPressEvent{Code: 'c', Text: "c"},
949				KeyPressEvent{Code: 128515, Text: "😃"},
950				KeyPressEvent{Code: KeySpace, Text: " "},
951			},
952		},
953
954		{
955			"ignored osc",
956			[]byte("\x1b]11;#123456\x18\x1b]11;#123456\x1a\x1b]11;#123456\x1b"),
957			[]Event(nil),
958		},
959		{
960			"ignored apc",
961			[]byte("\x9f\x9c\x1b_hello\x1b\x1b_hello\x18\x1b_abc\x1b\\\x1ba"),
962			[]Event{
963				UnknownApcEvent("\x9f\x9c"),
964				UnknownApcEvent("\x1b_abc\x1b\\"),
965				KeyPressEvent{Code: 'a', Mod: ModAlt},
966			},
967		},
968		{
969			"alt+] alt+'",
970			[]byte("\x1b]\x1b'"),
971			[]Event{
972				KeyPressEvent{Code: ']', Mod: ModAlt},
973				KeyPressEvent{Code: '\'', Mod: ModAlt},
974			},
975		},
976		{
977			"alt+^ alt+&",
978			[]byte("\x1b^\x1b&"),
979			[]Event{
980				KeyPressEvent{Code: '^', Mod: ModAlt},
981				KeyPressEvent{Code: '&', Mod: ModAlt},
982			},
983		},
984		{
985			"a",
986			[]byte{'a'},
987			[]Event{
988				KeyPressEvent{Code: 'a', Text: "a"},
989			},
990		},
991		{
992			"space",
993			[]byte{' '},
994			[]Event{
995				KeyPressEvent{Code: KeySpace, Text: " "},
996			},
997		},
998		{
999			"a alt+a",
1000			[]byte{'a', '\x1b', 'a'},
1001			[]Event{
1002				KeyPressEvent{Code: 'a', Text: "a"},
1003				KeyPressEvent{Code: 'a', Mod: ModAlt},
1004			},
1005		},
1006		{
1007			"a alt+a a",
1008			[]byte{'a', '\x1b', 'a', 'a'},
1009			[]Event{
1010				KeyPressEvent{Code: 'a', Text: "a"},
1011				KeyPressEvent{Code: 'a', Mod: ModAlt},
1012				KeyPressEvent{Code: 'a', Text: "a"},
1013			},
1014		},
1015		{
1016			"ctrl+a",
1017			[]byte{byte(ansi.SOH)},
1018			[]Event{
1019				KeyPressEvent{Code: 'a', Mod: ModCtrl},
1020			},
1021		},
1022		{
1023			"ctrl+a ctrl+b",
1024			[]byte{byte(ansi.SOH), byte(ansi.STX)},
1025			[]Event{
1026				KeyPressEvent{Code: 'a', Mod: ModCtrl},
1027				KeyPressEvent{Code: 'b', Mod: ModCtrl},
1028			},
1029		},
1030		{
1031			"alt+a",
1032			[]byte{byte(0x1b), 'a'},
1033			[]Event{
1034				KeyPressEvent{Code: 'a', Mod: ModAlt},
1035			},
1036		},
1037		{
1038			"a b c d",
1039			[]byte{'a', 'b', 'c', 'd'},
1040			[]Event{
1041				KeyPressEvent{Code: 'a', Text: "a"},
1042				KeyPressEvent{Code: 'b', Text: "b"},
1043				KeyPressEvent{Code: 'c', Text: "c"},
1044				KeyPressEvent{Code: 'd', Text: "d"},
1045			},
1046		},
1047		{
1048			"up",
1049			[]byte("\x1b[A"),
1050			[]Event{
1051				KeyPressEvent{Code: KeyUp},
1052			},
1053		},
1054		{
1055			"wheel up",
1056			[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
1057			[]Event{
1058				MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1059			},
1060		},
1061		{
1062			"left motion release",
1063			[]byte{
1064				'\x1b', '[', 'M', byte(32) + 0b0010_0000, byte(32 + 33), byte(16 + 33),
1065				'\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33),
1066			},
1067			[]Event{
1068				MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1069				MouseReleaseEvent{X: 64, Y: 32, Button: MouseNone},
1070			},
1071		},
1072		{
1073			"shift+tab",
1074			[]byte{'\x1b', '[', 'Z'},
1075			[]Event{
1076				KeyPressEvent{Code: KeyTab, Mod: ModShift},
1077			},
1078		},
1079		{
1080			"enter",
1081			[]byte{'\r'},
1082			[]Event{KeyPressEvent{Code: KeyEnter}},
1083		},
1084		{
1085			"alt+enter",
1086			[]byte{'\x1b', '\r'},
1087			[]Event{
1088				KeyPressEvent{Code: KeyEnter, Mod: ModAlt},
1089			},
1090		},
1091		{
1092			"insert",
1093			[]byte{'\x1b', '[', '2', '~'},
1094			[]Event{
1095				KeyPressEvent{Code: KeyInsert},
1096			},
1097		},
1098		{
1099			"ctrl+alt+a",
1100			[]byte{'\x1b', byte(ansi.SOH)},
1101			[]Event{
1102				KeyPressEvent{Code: 'a', Mod: ModCtrl | ModAlt},
1103			},
1104		},
1105		{
1106			"CSI?----X?",
1107			[]byte{'\x1b', '[', '-', '-', '-', '-', 'X'},
1108			[]Event{UnknownCsiEvent([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'})},
1109		},
1110		// Powershell sequences.
1111		{
1112			"up",
1113			[]byte{'\x1b', 'O', 'A'},
1114			[]Event{KeyPressEvent{Code: KeyUp}},
1115		},
1116		{
1117			"down",
1118			[]byte{'\x1b', 'O', 'B'},
1119			[]Event{KeyPressEvent{Code: KeyDown}},
1120		},
1121		{
1122			"right",
1123			[]byte{'\x1b', 'O', 'C'},
1124			[]Event{KeyPressEvent{Code: KeyRight}},
1125		},
1126		{
1127			"left",
1128			[]byte{'\x1b', 'O', 'D'},
1129			[]Event{KeyPressEvent{Code: KeyLeft}},
1130		},
1131		{
1132			"alt+enter",
1133			[]byte{'\x1b', '\x0d'},
1134			[]Event{KeyPressEvent{Code: KeyEnter, Mod: ModAlt}},
1135		},
1136		{
1137			"alt+backspace",
1138			[]byte{'\x1b', '\x7f'},
1139			[]Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
1140		},
1141		{
1142			"ctrl+space",
1143			[]byte{'\x00'},
1144			[]Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl}},
1145		},
1146		{
1147			"ctrl+alt+space",
1148			[]byte{'\x1b', '\x00'},
1149			[]Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl | ModAlt}},
1150		},
1151		{
1152			"esc",
1153			[]byte{'\x1b'},
1154			[]Event{KeyPressEvent{Code: KeyEscape}},
1155		},
1156		{
1157			"alt+esc",
1158			[]byte{'\x1b', '\x1b'},
1159			[]Event{KeyPressEvent{Code: KeyEscape, Mod: ModAlt}},
1160		},
1161		{
1162			"a b o",
1163			[]byte{
1164				'\x1b', '[', '2', '0', '0', '~',
1165				'a', ' ', 'b',
1166				'\x1b', '[', '2', '0', '1', '~',
1167				'o',
1168			},
1169			[]Event{
1170				PasteStartEvent{},
1171				PasteEvent{"a b"},
1172				PasteEndEvent{},
1173				KeyPressEvent{Code: 'o', Text: "o"},
1174			},
1175		},
1176		{
1177			"a\x03\nb",
1178			[]byte{
1179				'\x1b', '[', '2', '0', '0', '~',
1180				'a', '\x03', '\n', 'b',
1181				'\x1b', '[', '2', '0', '1', '~',
1182			},
1183			[]Event{
1184				PasteStartEvent{},
1185				PasteEvent{"a\x03\nb"},
1186				PasteEndEvent{},
1187			},
1188		},
1189		{
1190			"?0xfe?",
1191			[]byte{'\xfe'},
1192			[]Event{
1193				UnknownEvent(rune(0xfe)),
1194			},
1195		},
1196		{
1197			"a ?0xfe?   b",
1198			[]byte{'a', '\xfe', ' ', 'b'},
1199			[]Event{
1200				KeyPressEvent{Code: 'a', Text: "a"},
1201				UnknownEvent(rune(0xfe)),
1202				KeyPressEvent{Code: KeySpace, Text: " "},
1203				KeyPressEvent{Code: 'b', Text: "b"},
1204			},
1205		},
1206	}
1207
1208	for i, td := range testData {
1209		t.Run(fmt.Sprintf("%d: %s", i, td.keyname), func(t *testing.T) {
1210			events := testReadInputs(t, bytes.NewReader(td.in))
1211			var buf strings.Builder
1212			for i, event := range events {
1213				if i > 0 {
1214					buf.WriteByte(' ')
1215				}
1216				if s, ok := event.(fmt.Stringer); ok {
1217					buf.WriteString(s.String())
1218				} else {
1219					fmt.Fprintf(&buf, "%#v:%T", event, event)
1220				}
1221			}
1222
1223			if len(events) != len(td.out) {
1224				t.Fatalf("unexpected message list length: got %d, expected %d\n  got: %#v\n  expected: %#v\n", len(events), len(td.out), events, td.out)
1225			}
1226
1227			if len(td.out) != len(events) {
1228				t.Fatalf("expected %d events, got %d: %s", len(td.out), len(events), buf.String())
1229			}
1230			for i, e := range events {
1231				if !reflect.DeepEqual(td.out[i], e) {
1232					t.Errorf("expected event %d to be %T %v, got %T %v", i, td.out[i], td.out[i], e, e)
1233				}
1234			}
1235			if !reflect.DeepEqual(td.out, events) {
1236				t.Fatalf("expected:\n%#v\ngot:\n%#v", td.out, events)
1237			}
1238		})
1239	}
1240}
1241
1242func testReadInputs(t *testing.T, input io.Reader) []Event {
1243	drv := NewTerminalReader(input, "dumb")
1244	drv.SetLogger(TLogger{t})
1245
1246	eventc := make(chan Event)
1247	go func(t testing.TB) {
1248		defer close(eventc)
1249		if err := drv.StreamEvents(context.TODO(), eventc); err != nil {
1250			t.Errorf("error streaming events: %v", err)
1251		}
1252	}(t)
1253
1254	var events []Event
1255	for ev := range eventc {
1256		events = append(events, ev)
1257	}
1258
1259	return events
1260}
1261
1262// randTest defines the test input and expected output for a sequence
1263// of interleaved control sequences and control characters.
1264type randTest struct {
1265	data    []byte
1266	lengths []int
1267	names   []string
1268}
1269
1270// seed is the random seed to randomize the input. This helps check
1271// that all the sequences get ultimately exercised.
1272var seed = flag.Int64("seed", 0, "random seed (0 to autoselect)")
1273
1274// genRandomData generates a randomized test, with a random seed unless
1275// the seed flag was set.
1276func genRandomData(logfn func(int64), length int) randTest {
1277	// We'll use a random source. However, we give the user the option
1278	// to override it to a specific value for reproduceability.
1279	s := *seed
1280	if s == 0 {
1281		s = time.Now().UnixNano()
1282	}
1283	// Inform the user so they know what to reuse to get the same data.
1284	logfn(s)
1285	return genRandomDataWithSeed(s, length)
1286}
1287
1288// genRandomDataWithSeed generates a randomized test with a fixed seed.
1289func genRandomDataWithSeed(s int64, length int) randTest {
1290	src := rand.NewSource(s)
1291	r := rand.New(src)
1292
1293	// allseqs contains all the sequences, in sorted order. We sort
1294	// to make the test deterministic (when the seed is also fixed).
1295	type seqpair struct {
1296		seq  string
1297		name string
1298	}
1299	var allseqs []seqpair
1300	for seq, key := range sequences {
1301		allseqs = append(allseqs, seqpair{seq, key.String()})
1302	}
1303	sort.Slice(allseqs, func(i, j int) bool { return allseqs[i].seq < allseqs[j].seq })
1304
1305	// res contains the computed test.
1306	var res randTest
1307
1308	for len(res.data) < length {
1309		alt := r.Intn(2)
1310		prefix := ""
1311		esclen := 0
1312		if alt == 1 {
1313			prefix = "alt+"
1314			esclen = 1
1315		}
1316		kind := r.Intn(3)
1317		switch kind {
1318		case 0:
1319			// A control character.
1320			if alt == 1 {
1321				res.data = append(res.data, '\x1b')
1322			}
1323			res.data = append(res.data, 1)
1324			res.names = append(res.names, "ctrl+"+prefix+"a")
1325			res.lengths = append(res.lengths, 1+esclen)
1326
1327		case 1, 2:
1328			// A sequence.
1329			seqi := r.Intn(len(allseqs))
1330			s := allseqs[seqi]
1331			if strings.Contains(s.name, "alt+") || strings.Contains(s.name, "meta+") {
1332				esclen = 0
1333				prefix = ""
1334				alt = 0
1335			}
1336			if alt == 1 {
1337				res.data = append(res.data, '\x1b')
1338			}
1339			res.data = append(res.data, s.seq...)
1340			if strings.HasPrefix(s.name, "ctrl+") {
1341				prefix = "ctrl+" + prefix
1342			}
1343			name := prefix + strings.TrimPrefix(s.name, "ctrl+")
1344			res.names = append(res.names, name)
1345			res.lengths = append(res.lengths, len(s.seq)+esclen)
1346		}
1347	}
1348	return res
1349}
1350
1351func FuzzParseSequence(f *testing.F) {
1352	var p EventDecoder
1353	for seq := range sequences {
1354		f.Add(seq)
1355	}
1356	f.Add("\x1b]52;?\x07")                      // OSC 52
1357	f.Add("\x1b]11;rgb:0000/0000/0000\x1b\\")   // OSC 11
1358	f.Add("\x1bP>|charm terminal(0.1.2)\x1b\\") // DCS (XTVERSION)
1359	f.Add("\x1b_Gi=123\x1b\\")                  // APC
1360	f.Fuzz(func(t *testing.T, seq string) {
1361		n, _ := p.Decode([]byte(seq))
1362		if n == 0 && seq != "" {
1363			t.Errorf("expected a non-zero width for %q", seq)
1364		}
1365	})
1366}
1367
1368// BenchmarkDetectSequenceMap benchmarks the map-based sequence
1369// detector.
1370func BenchmarkDetectSequenceMap(b *testing.B) {
1371	var p EventDecoder
1372	td := genRandomDataWithSeed(123, 10000)
1373	for i := 0; i < b.N; i++ {
1374		for j, w := 0, 0; j < len(td.data); j += w {
1375			w, _ = p.Decode(td.data[j:])
1376		}
1377	}
1378}
1379
1380func TestMouseEvent_String(t *testing.T) {
1381	tt := []struct {
1382		name     string
1383		event    Event
1384		expected string
1385	}{
1386		{
1387			name:     "unknown",
1388			event:    MouseClickEvent{Button: MouseButton(0xff)},
1389			expected: "unknown",
1390		},
1391		{
1392			name:     "left",
1393			event:    MouseClickEvent{Button: MouseLeft},
1394			expected: "left",
1395		},
1396		{
1397			name:     "right",
1398			event:    MouseClickEvent{Button: MouseRight},
1399			expected: "right",
1400		},
1401		{
1402			name:     "middle",
1403			event:    MouseClickEvent{Button: MouseMiddle},
1404			expected: "middle",
1405		},
1406		{
1407			name:     "release",
1408			event:    MouseReleaseEvent{Button: MouseNone},
1409			expected: "",
1410		},
1411		{
1412			name:     "wheelup",
1413			event:    MouseWheelEvent{Button: MouseWheelUp},
1414			expected: "wheelup",
1415		},
1416		{
1417			name:     "wheeldown",
1418			event:    MouseWheelEvent{Button: MouseWheelDown},
1419			expected: "wheeldown",
1420		},
1421		{
1422			name:     "wheelleft",
1423			event:    MouseWheelEvent{Button: MouseWheelLeft},
1424			expected: "wheelleft",
1425		},
1426		{
1427			name:     "wheelright",
1428			event:    MouseWheelEvent{Button: MouseWheelRight},
1429			expected: "wheelright",
1430		},
1431		{
1432			name:     "motion",
1433			event:    MouseMotionEvent{Button: MouseNone},
1434			expected: "motion",
1435		},
1436		{
1437			name:     "shift+left",
1438			event:    MouseReleaseEvent{Button: MouseLeft, Mod: ModShift},
1439			expected: "shift+left",
1440		},
1441		{
1442			name: "shift+left", event: MouseClickEvent{Button: MouseLeft, Mod: ModShift},
1443			expected: "shift+left",
1444		},
1445		{
1446			name:     "ctrl+shift+left",
1447			event:    MouseClickEvent{Button: MouseLeft, Mod: ModCtrl | ModShift},
1448			expected: "ctrl+shift+left",
1449		},
1450		{
1451			name:     "alt+left",
1452			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt},
1453			expected: "alt+left",
1454		},
1455		{
1456			name:     "ctrl+left",
1457			event:    MouseClickEvent{Button: MouseLeft, Mod: ModCtrl},
1458			expected: "ctrl+left",
1459		},
1460		{
1461			name:     "ctrl+alt+left",
1462			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt | ModCtrl},
1463			expected: "ctrl+alt+left",
1464		},
1465		{
1466			name:     "ctrl+alt+shift+left",
1467			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt | ModCtrl | ModShift},
1468			expected: "ctrl+alt+shift+left",
1469		},
1470		{
1471			name:     "ignore coordinates",
1472			event:    MouseClickEvent{X: 100, Y: 200, Button: MouseLeft},
1473			expected: "left",
1474		},
1475		{
1476			name:     "broken type",
1477			event:    MouseClickEvent{Button: MouseButton(120)},
1478			expected: "unknown",
1479		},
1480	}
1481
1482	for i := range tt {
1483		tc := tt[i]
1484
1485		t.Run(tc.name, func(t *testing.T) {
1486			actual := fmt.Sprint(tc.event)
1487
1488			if tc.expected != actual {
1489				t.Fatalf("expected %q but got %q",
1490					tc.expected,
1491					actual,
1492				)
1493			}
1494		})
1495	}
1496}
1497
1498func TestParseX10MouseDownEvent(t *testing.T) {
1499	encode := func(b byte, x, y int) []byte {
1500		return []byte{
1501			'\x1b',
1502			'[',
1503			'M',
1504			byte(32) + b,
1505			byte(x + 32 + 1),
1506			byte(y + 32 + 1),
1507		}
1508	}
1509
1510	tt := []struct {
1511		name     string
1512		buf      []byte
1513		expected Event
1514	}{
1515		// Position.
1516		{
1517			name:     "zero position",
1518			buf:      encode(0b0000_0000, 0, 0),
1519			expected: MouseClickEvent{X: 0, Y: 0, Button: MouseLeft},
1520		},
1521		{
1522			name:     "max position",
1523			buf:      encode(0b0000_0000, 222, 222), // Because 255 (max int8) - 32 - 1.
1524			expected: MouseClickEvent{X: 222, Y: 222, Button: MouseLeft},
1525		},
1526		// Simple.
1527		{
1528			name:     "left",
1529			buf:      encode(0b0000_0000, 32, 16),
1530			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
1531		},
1532		{
1533			name:     "left in motion",
1534			buf:      encode(0b0010_0000, 32, 16),
1535			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1536		},
1537		{
1538			name:     "middle",
1539			buf:      encode(0b0000_0001, 32, 16),
1540			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseMiddle},
1541		},
1542		{
1543			name:     "middle in motion",
1544			buf:      encode(0b0010_0001, 32, 16),
1545			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseMiddle},
1546		},
1547		{
1548			name:     "right",
1549			buf:      encode(0b0000_0010, 32, 16),
1550			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseRight},
1551		},
1552		{
1553			name:     "right in motion",
1554			buf:      encode(0b0010_0010, 32, 16),
1555			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseRight},
1556		},
1557		{
1558			name:     "motion",
1559			buf:      encode(0b0010_0011, 32, 16),
1560			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseNone},
1561		},
1562		{
1563			name:     "wheel up",
1564			buf:      encode(0b0100_0000, 32, 16),
1565			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1566		},
1567		{
1568			name:     "wheel down",
1569			buf:      encode(0b0100_0001, 32, 16),
1570			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelDown},
1571		},
1572		{
1573			name:     "wheel left",
1574			buf:      encode(0b0100_0010, 32, 16),
1575			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelLeft},
1576		},
1577		{
1578			name:     "wheel right",
1579			buf:      encode(0b0100_0011, 32, 16),
1580			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelRight},
1581		},
1582		{
1583			name:     "release",
1584			buf:      encode(0b0000_0011, 32, 16),
1585			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseNone},
1586		},
1587		{
1588			name:     "backward",
1589			buf:      encode(0b1000_0000, 32, 16),
1590			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseBackward},
1591		},
1592		{
1593			name:     "forward",
1594			buf:      encode(0b1000_0001, 32, 16),
1595			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseForward},
1596		},
1597		{
1598			name:     "button 10",
1599			buf:      encode(0b1000_0010, 32, 16),
1600			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseButton10},
1601		},
1602		{
1603			name:     "button 11",
1604			buf:      encode(0b1000_0011, 32, 16),
1605			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseButton11},
1606		},
1607		// Combinations.
1608		{
1609			name:     "alt+right",
1610			buf:      encode(0b0000_1010, 32, 16),
1611			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1612		},
1613		{
1614			name:     "ctrl+right",
1615			buf:      encode(0b0001_0010, 32, 16),
1616			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1617		},
1618		{
1619			name:     "left in motion",
1620			buf:      encode(0b0010_0000, 32, 16),
1621			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1622		},
1623		{
1624			name:     "alt+right in motion",
1625			buf:      encode(0b0010_1010, 32, 16),
1626			expected: MouseMotionEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1627		},
1628		{
1629			name:     "ctrl+right in motion",
1630			buf:      encode(0b0011_0010, 32, 16),
1631			expected: MouseMotionEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1632		},
1633		{
1634			name:     "ctrl+alt+right",
1635			buf:      encode(0b0001_1010, 32, 16),
1636			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight},
1637		},
1638		{
1639			name:     "ctrl+wheel up",
1640			buf:      encode(0b0101_0000, 32, 16),
1641			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelUp},
1642		},
1643		{
1644			name:     "alt+wheel down",
1645			buf:      encode(0b0100_1001, 32, 16),
1646			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown},
1647		},
1648		{
1649			name:     "ctrl+alt+wheel down",
1650			buf:      encode(0b0101_1001, 32, 16),
1651			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown},
1652		},
1653		// Overflow position.
1654		{
1655			name:     "overflow position",
1656			buf:      encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1.
1657			expected: MouseMotionEvent{X: -6, Y: -33, Button: MouseLeft},
1658		},
1659	}
1660
1661	for i := range tt {
1662		tc := tt[i]
1663
1664		t.Run(tc.name, func(t *testing.T) {
1665			actual := parseX10MouseEvent(tc.buf)
1666
1667			if tc.expected != actual {
1668				t.Fatalf("expected %#v but got %#v",
1669					tc.expected,
1670					actual,
1671				)
1672			}
1673		})
1674	}
1675}
1676
1677func TestParseSGRMouseEvent(t *testing.T) {
1678	type csiSequence struct {
1679		params []ansi.Param
1680		cmd    ansi.Cmd
1681	}
1682	encode := func(b, x, y int, r bool) *csiSequence {
1683		re := 'M'
1684		if r {
1685			re = 'm'
1686		}
1687		return &csiSequence{
1688			params: []ansi.Param{
1689				ansi.Param(b),
1690				ansi.Param(x + 1),
1691				ansi.Param(y + 1),
1692			},
1693			cmd: ansi.Cmd(re) | ('<' << parser.PrefixShift),
1694		}
1695	}
1696
1697	tt := []struct {
1698		name     string
1699		buf      *csiSequence
1700		expected Event
1701	}{
1702		// Position.
1703		{
1704			name:     "zero position",
1705			buf:      encode(0, 0, 0, false),
1706			expected: MouseClickEvent{X: 0, Y: 0, Button: MouseLeft},
1707		},
1708		{
1709			name:     "225 position",
1710			buf:      encode(0, 225, 225, false),
1711			expected: MouseClickEvent{X: 225, Y: 225, Button: MouseLeft},
1712		},
1713		// Simple.
1714		{
1715			name:     "left",
1716			buf:      encode(0, 32, 16, false),
1717			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
1718		},
1719		{
1720			name:     "left in motion",
1721			buf:      encode(32, 32, 16, false),
1722			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1723		},
1724		{
1725			name:     "left",
1726			buf:      encode(0, 32, 16, true),
1727			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseLeft},
1728		},
1729		{
1730			name:     "middle",
1731			buf:      encode(1, 32, 16, false),
1732			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseMiddle},
1733		},
1734		{
1735			name:     "middle in motion",
1736			buf:      encode(33, 32, 16, false),
1737			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseMiddle},
1738		},
1739		{
1740			name:     "middle",
1741			buf:      encode(1, 32, 16, true),
1742			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseMiddle},
1743		},
1744		{
1745			name:     "right",
1746			buf:      encode(2, 32, 16, false),
1747			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseRight},
1748		},
1749		{
1750			name:     "right",
1751			buf:      encode(2, 32, 16, true),
1752			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseRight},
1753		},
1754		{
1755			name:     "motion",
1756			buf:      encode(35, 32, 16, false),
1757			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseNone},
1758		},
1759		{
1760			name:     "wheel up",
1761			buf:      encode(64, 32, 16, false),
1762			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1763		},
1764		{
1765			name:     "wheel down",
1766			buf:      encode(65, 32, 16, false),
1767			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelDown},
1768		},
1769		{
1770			name:     "wheel left",
1771			buf:      encode(66, 32, 16, false),
1772			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelLeft},
1773		},
1774		{
1775			name:     "wheel right",
1776			buf:      encode(67, 32, 16, false),
1777			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelRight},
1778		},
1779		{
1780			name:     "backward",
1781			buf:      encode(128, 32, 16, false),
1782			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseBackward},
1783		},
1784		{
1785			name:     "backward in motion",
1786			buf:      encode(160, 32, 16, false),
1787			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseBackward},
1788		},
1789		{
1790			name:     "forward",
1791			buf:      encode(129, 32, 16, false),
1792			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseForward},
1793		},
1794		{
1795			name:     "forward in motion",
1796			buf:      encode(161, 32, 16, false),
1797			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseForward},
1798		},
1799		// Combinations.
1800		{
1801			name:     "alt+right",
1802			buf:      encode(10, 32, 16, false),
1803			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1804		},
1805		{
1806			name:     "ctrl+right",
1807			buf:      encode(18, 32, 16, false),
1808			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1809		},
1810		{
1811			name:     "ctrl+alt+right",
1812			buf:      encode(26, 32, 16, false),
1813			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight},
1814		},
1815		{
1816			name:     "alt+wheel",
1817			buf:      encode(73, 32, 16, false),
1818			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown},
1819		},
1820		{
1821			name:     "ctrl+wheel",
1822			buf:      encode(81, 32, 16, false),
1823			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelDown},
1824		},
1825		{
1826			name:     "ctrl+alt+wheel",
1827			buf:      encode(89, 32, 16, false),
1828			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown},
1829		},
1830		{
1831			name:     "ctrl+alt+shift+wheel",
1832			buf:      encode(93, 32, 16, false),
1833			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModShift | ModCtrl, Button: MouseWheelDown},
1834		},
1835	}
1836
1837	for i := range tt {
1838		tc := tt[i]
1839
1840		t.Run(tc.name, func(t *testing.T) {
1841			actual := parseSGRMouseEvent(tc.buf.cmd, tc.buf.params)
1842			if tc.expected != actual {
1843				t.Fatalf("expected %#v but got %#v",
1844					tc.expected,
1845					actual,
1846				)
1847			}
1848		})
1849	}
1850}
1851
1852func TestMousePixelToCell(t *testing.T) {
1853	tt := []struct {
1854		name     string
1855		mouse    Mouse
1856		ws       *Winsize
1857		expected Mouse
1858	}{
1859		{
1860			name:     "standard 80x24 terminal",
1861			mouse:    Mouse{X: 480, Y: 312, Button: MouseLeft},
1862			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1863			expected: Mouse{X: 68, Y: 24, Button: MouseLeft},
1864		},
1865		{
1866			name:     "top-left pixel",
1867			mouse:    Mouse{X: 0, Y: 0, Button: MouseLeft},
1868			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1869			expected: Mouse{X: 0, Y: 0, Button: MouseLeft},
1870		},
1871		{
1872			name:     "bottom-right pixel",
1873			mouse:    Mouse{X: 559, Y: 311, Button: MouseRight},
1874			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1875			expected: Mouse{X: 79, Y: 23, Button: MouseRight},
1876		},
1877		{
1878			name:     "exact cell boundary pixel",
1879			mouse:    Mouse{X: 6, Y: 12, Button: MouseLeft},
1880			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 480, Ypixel: 288},
1881			expected: Mouse{X: 1, Y: 1, Button: MouseLeft},
1882		},
1883		{
1884			name:     "preserves modifiers",
1885			mouse:    Mouse{X: 240, Y: 156, Button: MouseMiddle, Mod: ModAlt},
1886			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1887			expected: Mouse{X: 34, Y: 12, Button: MouseMiddle, Mod: ModAlt},
1888		},
1889		{
1890			name:     "zero pixel dimensions returns zero cell",
1891			mouse:    Mouse{X: 480, Y: 312, Button: MouseLeft},
1892			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 0, Ypixel: 0},
1893			expected: Mouse{X: 0, Y: 0, Button: MouseLeft},
1894		},
1895		{
1896			name:     "pixel position larger than window",
1897			mouse:    Mouse{X: 1200, Y: 800, Button: MouseLeft},
1898			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1899			expected: Mouse{X: 171, Y: 61, Button: MouseLeft},
1900		},
1901	}
1902
1903	for i := range tt {
1904		tc := tt[i]
1905
1906		t.Run(tc.name, func(t *testing.T) {
1907			actual := MousePixelToCell(tc.mouse, tc.ws)
1908			if tc.expected != actual {
1909				t.Fatalf("expected %#v but got %#v",
1910					tc.expected,
1911					actual,
1912				)
1913			}
1914		})
1915	}
1916}
1917
1918// TestMatchStrings tests the MatchStrings method
1919func TestMatchStrings(t *testing.T) {
1920	tests := []struct {
1921		name   string
1922		key    Key
1923		inputs []string
1924		want   bool
1925	}{
1926		{
1927			name:   "matches first string",
1928			key:    Key{Code: 'a', Mod: ModCtrl},
1929			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1930			want:   true,
1931		},
1932		{
1933			name:   "matches middle string",
1934			key:    Key{Code: 'b', Mod: ModCtrl},
1935			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1936			want:   true,
1937		},
1938		{
1939			name:   "matches last string",
1940			key:    Key{Code: 'c', Mod: ModCtrl},
1941			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1942			want:   true,
1943		},
1944		{
1945			name:   "no match",
1946			key:    Key{Code: 'd', Mod: ModCtrl},
1947			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1948			want:   false,
1949		},
1950		{
1951			name:   "empty inputs",
1952			key:    Key{Code: 'a', Mod: ModCtrl},
1953			inputs: []string{},
1954			want:   false,
1955		},
1956	}
1957
1958	for _, tt := range tests {
1959		t.Run(tt.name, func(t *testing.T) {
1960			got := tt.key.MatchString(tt.inputs...)
1961			if got != tt.want {
1962				t.Errorf("MatchStrings() = %v, want %v", got, tt.want)
1963			}
1964		})
1965	}
1966}
1967
1968func TestKeyMatchString(t *testing.T) {
1969	cases := []struct {
1970		name  string
1971		key   Key
1972		input string
1973		want  bool
1974	}{
1975		{
1976			name:  "ctrl+a",
1977			key:   Key{Code: 'a', Mod: ModCtrl},
1978			input: "ctrl+a",
1979			want:  true,
1980		},
1981		{
1982			name:  "ctrl+alt+a",
1983			key:   Key{Code: 'a', Mod: ModCtrl | ModAlt},
1984			input: "ctrl+alt+a",
1985			want:  true,
1986		},
1987		{
1988			name:  "ctrl+alt+shift+a",
1989			key:   Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift},
1990			input: "ctrl+alt+shift+a",
1991			want:  true,
1992		},
1993		{
1994			name:  "H",
1995			key:   Key{Code: 'H', Text: "H"},
1996			input: "H",
1997			want:  true,
1998		},
1999		{
2000			name:  "shift+h",
2001			key:   Key{Code: 'h', Mod: ModShift, Text: "H"},
2002			input: "H",
2003			want:  true,
2004		},
2005		{
2006			name:  "?",
2007			key:   Key{Code: '/', Mod: ModShift, Text: "?"},
2008			input: "?",
2009			want:  true,
2010		},
2011		{
2012			name:  "shift+/",
2013			key:   Key{Code: '/', Mod: ModShift, Text: "?"},
2014			input: "shift+/",
2015			want:  true,
2016		},
2017		{
2018			name:  "capslock+a",
2019			key:   Key{Code: 'a', Mod: ModCapsLock, Text: "A"},
2020			input: "A",
2021			want:  true,
2022		},
2023		{
2024			name:  "ctrl+capslock+a",
2025			key:   Key{Code: 'a', Mod: ModCtrl | ModCapsLock},
2026			input: "ctrl+a",
2027			want:  false,
2028		},
2029		{
2030			name:  "space",
2031			key:   Key{Code: KeySpace, Text: " "},
2032			input: "space",
2033			want:  true,
2034		},
2035		{
2036			name:  "whitespace",
2037			key:   Key{Code: KeySpace, Text: " "},
2038			input: " ",
2039			want:  true,
2040		},
2041		{
2042			name:  "ctrl+space",
2043			key:   Key{Code: KeySpace, Mod: ModCtrl},
2044			input: "ctrl+space",
2045			want:  true,
2046		},
2047		{
2048			name:  "shift+whitespace",
2049			key:   Key{Code: KeySpace, Mod: ModShift, Text: " "},
2050			input: " ",
2051			want:  true,
2052		},
2053		{
2054			name:  "shift+space",
2055			key:   Key{Code: KeySpace, Mod: ModShift, Text: " "},
2056			input: "shift+space",
2057			want:  true,
2058		},
2059		{
2060			name:  "meta modifier",
2061			key:   Key{Code: 'a', Mod: ModMeta},
2062			input: "meta+a",
2063			want:  true,
2064		},
2065		{
2066			name:  "hyper modifier",
2067			key:   Key{Code: 'a', Mod: ModHyper},
2068			input: "hyper+a",
2069			want:  true,
2070		},
2071		{
2072			name:  "super modifier",
2073			key:   Key{Code: 'a', Mod: ModSuper},
2074			input: "super+a",
2075			want:  true,
2076		},
2077		{
2078			name:  "scrolllock modifier",
2079			key:   Key{Code: 'a', Mod: ModScrollLock},
2080			input: "scrolllock+a",
2081			want:  true,
2082		},
2083		{
2084			name:  "numlock modifier",
2085			key:   Key{Code: 'a', Mod: ModNumLock},
2086			input: "numlock+a",
2087			want:  true,
2088		},
2089		{
2090			name:  "multi-rune key",
2091			key:   Key{Code: KeyExtended, Text: "hello"},
2092			input: "hello",
2093			want:  true,
2094		},
2095		{
2096			name:  "enter key",
2097			key:   Key{Code: KeyEnter},
2098			input: "enter",
2099			want:  true,
2100		},
2101		{
2102			name:  "tab key",
2103			key:   Key{Code: KeyTab},
2104			input: "tab",
2105			want:  true,
2106		},
2107		{
2108			name:  "escape key",
2109			key:   Key{Code: KeyEscape},
2110			input: "esc",
2111			want:  true,
2112		},
2113		{
2114			name:  "f1 key",
2115			key:   Key{Code: KeyF1},
2116			input: "f1",
2117			want:  true,
2118		},
2119		{
2120			name:  "backspace key",
2121			key:   Key{Code: KeyBackspace},
2122			input: "backspace",
2123			want:  true,
2124		},
2125		{
2126			name:  "delete key",
2127			key:   Key{Code: KeyDelete},
2128			input: "delete",
2129			want:  true,
2130		},
2131		{
2132			name:  "home key",
2133			key:   Key{Code: KeyHome},
2134			input: "home",
2135			want:  true,
2136		},
2137		{
2138			name:  "end key",
2139			key:   Key{Code: KeyEnd},
2140			input: "end",
2141			want:  true,
2142		},
2143		{
2144			name:  "pgup key",
2145			key:   Key{Code: KeyPgUp},
2146			input: "pgup",
2147			want:  true,
2148		},
2149		{
2150			name:  "pgdown key",
2151			key:   Key{Code: KeyPgDown},
2152			input: "pgdown",
2153			want:  true,
2154		},
2155		{
2156			name:  "up arrow",
2157			key:   Key{Code: KeyUp},
2158			input: "up",
2159			want:  true,
2160		},
2161		{
2162			name:  "down arrow",
2163			key:   Key{Code: KeyDown},
2164			input: "down",
2165			want:  true,
2166		},
2167		{
2168			name:  "left arrow",
2169			key:   Key{Code: KeyLeft},
2170			input: "left",
2171			want:  true,
2172		},
2173		{
2174			name:  "right arrow",
2175			key:   Key{Code: KeyRight},
2176			input: "right",
2177			want:  true,
2178		},
2179		{
2180			name:  "insert key",
2181			key:   Key{Code: KeyInsert},
2182			input: "insert",
2183			want:  true,
2184		},
2185		{
2186			name:  "single printable character",
2187			key:   Key{Code: '1', Text: "1"},
2188			input: "1",
2189			want:  true,
2190		},
2191		{
2192			name:  "uppercase letter without shift",
2193			key:   Key{Code: 'A', Text: "A"},
2194			input: "A",
2195			want:  true,
2196		},
2197		{
2198			name:  "no match different key",
2199			key:   Key{Code: 'a', Mod: ModCtrl},
2200			input: "ctrl+b",
2201			want:  false,
2202		},
2203		{
2204			name:  "no match different modifier",
2205			key:   Key{Code: 'a', Mod: ModCtrl},
2206			input: "alt+a",
2207			want:  false,
2208		},
2209		{
2210			name:  "unknown key name",
2211			key:   Key{Code: 'x'},
2212			input: "unknownkey",
2213			want:  false,
2214		},
2215		{
2216			name:  "multi-rune string that doesn't match",
2217			key:   Key{Code: 'a'},
2218			input: "hello",
2219			want:  false,
2220		},
2221		{
2222			name:  "printable character with ctrl modifier",
2223			key:   Key{Code: 'a', Mod: ModCtrl},
2224			input: "a",
2225			want:  false,
2226		},
2227		{
2228			name:  "lowercase letter with shift",
2229			key:   Key{Code: 'h', Mod: ModShift},
2230			input: "shift+h",
2231			want:  true,
2232		},
2233		{
2234			name:  "uppercase letter with capslock",
2235			key:   Key{Code: 'h', Mod: ModCapsLock},
2236			input: "capslock+h",
2237			want:  true,
2238		},
2239	}
2240
2241	for i, tc := range cases {
2242		t.Run(fmt.Sprintf("%d: %s", i, tc.name), func(t *testing.T) {
2243			got := tc.key.MatchString(tc.input)
2244			if got != tc.want {
2245				t.Errorf("expected %v but got %v", tc.want, got)
2246			}
2247		})
2248	}
2249}
2250
2251// TestSplitSequences tests that string-terminated sequences work correctly
2252// when split across multiple read() calls.
2253func TestSplitSequences(t *testing.T) {
2254	tests := []struct {
2255		name   string
2256		chunks [][]byte
2257		want   []Event
2258		delay  time.Duration
2259		limit  int // limit the number of bytes read at once
2260	}{
2261		{
2262			name: "OSC 11 background color with ST terminator",
2263			chunks: [][]byte{
2264				[]byte("\x1b]11;rgb:1a1a/1b1b/2c2c"),
2265				[]byte("\x1b\\"),
2266			},
2267			want: []Event{
2268				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1a1a/1b1b/2c2c")},
2269			},
2270		},
2271		{
2272			name: "OSC 11 background color with BEL terminator",
2273			chunks: [][]byte{
2274				[]byte("\x1b]11;rgb:1a1a/1b1b/2c2c"),
2275				[]byte("\x07"),
2276			},
2277			want: []Event{
2278				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1a1a/1b1b/2c2c")},
2279			},
2280		},
2281		{
2282			name: "OSC 10 foreground color split",
2283			chunks: [][]byte{
2284				[]byte("\x1b]10;rgb:ffff/0000/"),
2285				[]byte("0000\x1b\\"),
2286			},
2287			want: []Event{
2288				ForegroundColorEvent{Color: ansi.XParseColor("rgb:ffff/0000/0000")},
2289			},
2290		},
2291		{
2292			name: "OSC 12 cursor color split",
2293			chunks: [][]byte{
2294				[]byte("\x1b]12;rgb:"),
2295				[]byte("8080/8080/8080\x07"),
2296			},
2297			want: []Event{
2298				CursorColorEvent{Color: ansi.XParseColor("rgb:8080/8080/8080")},
2299			},
2300		},
2301		{
2302			name: "DCS sequence split",
2303			chunks: [][]byte{
2304				[]byte("\x1bP1$r"),
2305				[]byte("test\x1b\\"),
2306			},
2307			want: []Event{
2308				UnknownDcsEvent("\x1bP1$rtest\x1b\\"),
2309			},
2310		},
2311		{
2312			name: "long DCS sequence split",
2313			chunks: [][]byte{
2314				[]byte("\x1bP1$raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef"),
2315				[]byte("test\x1b\\"),
2316			},
2317			want: []Event{
2318				UnknownDcsEvent("\x1bP1$raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdeftest\x1b\\"),
2319			},
2320			limit: 256,
2321		},
2322		{
2323			name: "APC sequence split",
2324			chunks: [][]byte{
2325				[]byte("\x1b_T"),
2326				[]byte("test\x1b\\"),
2327			},
2328			want: []Event{
2329				UnknownApcEvent("\x1b_Ttest\x1b\\"),
2330			},
2331		},
2332		{
2333			name: "Multiple chunks OSC",
2334			chunks: [][]byte{
2335				[]byte("\x1b]11;"),
2336				[]byte("rgb:1234/"),
2337				[]byte("5678/9abc\x07"),
2338			},
2339			want: []Event{
2340				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1234/5678/9abc")},
2341			},
2342		},
2343		{
2344			name: "OSC followed by regular key",
2345			chunks: [][]byte{
2346				[]byte("\x1b]11;rgb:1111/2222/3333"),
2347				[]byte("\x07a"),
2348			},
2349			want: []Event{
2350				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1111/2222/3333")},
2351				KeyPressEvent{Code: 'a', Text: "a"},
2352			},
2353		},
2354		{
2355			name: "unknown sequence after timeout",
2356			chunks: [][]byte{
2357				[]byte("\x1b]11;rgb:1111/2222/3333"),
2358				[]byte("abc"),
2359				[]byte("x"),
2360				[]byte("x"),
2361				[]byte("x"),
2362				[]byte("x"),
2363			},
2364			want: []Event{
2365				UnknownEvent("\x1b]11;rgb:1111/2222/3333"),
2366				KeyPressEvent{Code: 'a', Text: "a"},
2367				KeyPressEvent{Code: 'b', Text: "b"},
2368				KeyPressEvent{Code: 'c', Text: "c"},
2369				KeyPressEvent{Code: 'x', Text: "x"},
2370				KeyPressEvent{Code: 'x', Text: "x"},
2371				KeyPressEvent{Code: 'x', Text: "x"},
2372				KeyPressEvent{Code: 'x', Text: "x"},
2373			},
2374			delay: 60 * time.Millisecond, // Ensure the timeout is triggered.
2375		},
2376		{
2377			name: "multiple broken down sequences",
2378			chunks: [][]byte{
2379				[]byte("\x1b[B"),
2380				[]byte("\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B"),
2381				[]byte("\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b["),
2382				[]byte("B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b"),
2383				[]byte("[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B"),
2384			},
2385			want: []Event{
2386				KeyPressEvent{Code: KeyDown},
2387				KeyPressEvent{Code: KeyDown},
2388				KeyPressEvent{Code: KeyDown},
2389				KeyPressEvent{Code: KeyDown},
2390				KeyPressEvent{Code: KeyDown},
2391				KeyPressEvent{Code: KeyDown},
2392				KeyPressEvent{Code: KeyDown},
2393				KeyPressEvent{Code: KeyDown},
2394				KeyPressEvent{Code: KeyDown},
2395				KeyPressEvent{Code: KeyDown},
2396				KeyPressEvent{Code: KeyDown},
2397				KeyPressEvent{Code: KeyDown},
2398				KeyPressEvent{Code: KeyDown},
2399				KeyPressEvent{Code: KeyDown},
2400				KeyPressEvent{Code: KeyDown},
2401				KeyPressEvent{Code: KeyDown},
2402				KeyPressEvent{Code: KeyDown},
2403				KeyPressEvent{Code: KeyDown},
2404				KeyPressEvent{Code: KeyDown},
2405				KeyPressEvent{Code: KeyDown},
2406				KeyPressEvent{Code: KeyDown},
2407				KeyPressEvent{Code: KeyDown},
2408				KeyPressEvent{Code: KeyDown},
2409				KeyPressEvent{Code: KeyDown},
2410				KeyPressEvent{Code: KeyDown},
2411				KeyPressEvent{Code: KeyDown},
2412				KeyPressEvent{Code: KeyDown},
2413				KeyPressEvent{Code: KeyDown},
2414				KeyPressEvent{Code: KeyDown},
2415				KeyPressEvent{Code: KeyDown},
2416				KeyPressEvent{Code: KeyDown},
2417				KeyPressEvent{Code: KeyDown},
2418				KeyPressEvent{Code: KeyDown},
2419				KeyPressEvent{Code: KeyDown},
2420				KeyPressEvent{Code: KeyDown},
2421				KeyPressEvent{Code: KeyDown},
2422				KeyPressEvent{Code: KeyDown},
2423				KeyPressEvent{Code: KeyDown},
2424				KeyPressEvent{Code: KeyDown},
2425				KeyPressEvent{Code: KeyDown},
2426				KeyPressEvent{Code: KeyDown},
2427				KeyPressEvent{Code: KeyDown},
2428				KeyPressEvent{Code: KeyDown},
2429			},
2430		},
2431	}
2432
2433	for _, tt := range tests {
2434		t.Run(tt.name, func(t *testing.T) {
2435			rds := make([]io.Reader, len(tt.chunks))
2436			for i, chunk := range tt.chunks {
2437				rds[i] = bytes.NewReader(chunk)
2438			}
2439			var r io.Reader
2440			limit := 32
2441			if tt.limit > 0 {
2442				limit = tt.limit
2443			}
2444			if tt.delay > 0 {
2445				r = DelayedLimitedReader(io.MultiReader(rds...), limit, tt.delay)
2446			} else {
2447				r = LimitedReader(io.MultiReader(rds...), limit)
2448			}
2449			ir := NewTerminalReader(r, "xterm-256color")
2450			ir.SetLogger(TLogger{TB: t})
2451
2452			eventc := make(chan Event)
2453			go func(t testing.TB) {
2454				defer close(eventc)
2455				if err := ir.StreamEvents(t.Context(), eventc); err != nil {
2456					t.Errorf("error streaming events: %v", err)
2457				}
2458			}(t)
2459
2460			var events []Event
2461			for ev := range eventc {
2462				events = append(events, ev)
2463			}
2464
2465			if len(events) != len(tt.want) {
2466				t.Fatalf("got %d events, want %d: %#v", len(events), len(tt.want), events)
2467			}
2468
2469			for i, want := range tt.want {
2470				if !reflect.DeepEqual(events[i], want) {
2471					t.Errorf("event %d: got %#v, want %#v", i, events[i], want)
2472				}
2473			}
2474		})
2475	}
2476}
2477
2478// limitedSleepReader is a simple io.Reader that limits the number of bytes
2479// read on each Read call and simulates a delay to mimic terminal input
2480// behavior.
2481type limitedSleepReader struct {
2482	r io.Reader
2483	n int
2484	d time.Duration
2485	i int
2486}
2487
2488func DelayedLimitedReader(r io.Reader, n int, d time.Duration) io.Reader {
2489	return &limitedSleepReader{r: r, n: n, d: d}
2490}
2491
2492func LimitedReader(r io.Reader, n int) io.Reader {
2493	return &limitedSleepReader{r: r, n: n}
2494}
2495
2496func (r *limitedSleepReader) Read(p []byte) (n int, err error) {
2497	if r.i > 0 && r.d > 0 {
2498		time.Sleep(r.d)
2499	}
2500	if r.n <= 0 {
2501		return 0, io.EOF
2502	}
2503	if len(p) > r.n {
2504		p = p[0:r.n]
2505	}
2506	n, err = r.r.Read(p)
2507	r.i++
2508	return n, err
2509}
2510
2511type TLogger struct{ testing.TB }
2512
2513func (t TLogger) Printf(format string, args ...interface{}) {
2514	t.Helper()
2515	if t.TB == nil {
2516		return
2517	}
2518	t.Logf(format, args...)
2519}
2520
2521// TestKeystroke tests the Keystroke method
2522func TestKeystroke(t *testing.T) {
2523	tests := []struct {
2524		name string
2525		key  Key
2526		want string
2527	}{
2528		{
2529			name: "simple key",
2530			key:  Key{Code: 'a'},
2531			want: "a",
2532		},
2533		{
2534			name: "ctrl+a",
2535			key:  Key{Code: 'a', Mod: ModCtrl},
2536			want: "ctrl+a",
2537		},
2538		{
2539			name: "alt+a",
2540			key:  Key{Code: 'a', Mod: ModAlt},
2541			want: "alt+a",
2542		},
2543		{
2544			name: "shift+a",
2545			key:  Key{Code: 'a', Mod: ModShift},
2546			want: "shift+a",
2547		},
2548		{
2549			name: "meta+a",
2550			key:  Key{Code: 'a', Mod: ModMeta},
2551			want: "meta+a",
2552		},
2553		{
2554			name: "hyper+a",
2555			key:  Key{Code: 'a', Mod: ModHyper},
2556			want: "hyper+a",
2557		},
2558		{
2559			name: "super+a",
2560			key:  Key{Code: 'a', Mod: ModSuper},
2561			want: "super+a",
2562		},
2563		{
2564			name: "ctrl+alt+shift+a",
2565			key:  Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift},
2566			want: "ctrl+alt+shift+a",
2567		},
2568		{
2569			name: "all modifiers",
2570			key:  Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift | ModMeta | ModHyper | ModSuper},
2571			want: "ctrl+alt+shift+meta+hyper+super+a",
2572		},
2573		{
2574			name: "space key",
2575			key:  Key{Code: KeySpace},
2576			want: "space",
2577		},
2578		{
2579			name: "extended key with text",
2580			key:  Key{Code: KeyExtended, Text: "hello"},
2581			want: "hello",
2582		},
2583		{
2584			name: "enter key",
2585			key:  Key{Code: KeyEnter},
2586			want: "enter",
2587		},
2588		{
2589			name: "tab key",
2590			key:  Key{Code: KeyTab},
2591			want: "tab",
2592		},
2593		{
2594			name: "escape key",
2595			key:  Key{Code: KeyEscape},
2596			want: "esc",
2597		},
2598		{
2599			name: "f1 key",
2600			key:  Key{Code: KeyF1},
2601			want: "f1",
2602		},
2603		{
2604			name: "backspace key",
2605			key:  Key{Code: KeyBackspace},
2606			want: "backspace",
2607		},
2608		{
2609			name: "left ctrl key alone",
2610			key:  Key{Code: KeyLeftCtrl, Mod: ModCtrl},
2611			want: "leftctrl",
2612		},
2613		{
2614			name: "right ctrl key alone",
2615			key:  Key{Code: KeyRightCtrl, Mod: ModCtrl},
2616			want: "rightctrl",
2617		},
2618		{
2619			name: "left alt key alone",
2620			key:  Key{Code: KeyLeftAlt, Mod: ModAlt},
2621			want: "leftalt",
2622		},
2623		{
2624			name: "right alt key alone",
2625			key:  Key{Code: KeyRightAlt, Mod: ModAlt},
2626			want: "rightalt",
2627		},
2628		{
2629			name: "left shift key alone",
2630			key:  Key{Code: KeyLeftShift, Mod: ModShift},
2631			want: "leftshift",
2632		},
2633		{
2634			name: "right shift key alone",
2635			key:  Key{Code: KeyRightShift, Mod: ModShift},
2636			want: "rightshift",
2637		},
2638		{
2639			name: "left meta key alone",
2640			key:  Key{Code: KeyLeftMeta, Mod: ModMeta},
2641			want: "leftmeta",
2642		},
2643		{
2644			name: "right meta key alone",
2645			key:  Key{Code: KeyRightMeta, Mod: ModMeta},
2646			want: "rightmeta",
2647		},
2648		{
2649			name: "left hyper key alone",
2650			key:  Key{Code: KeyLeftHyper, Mod: ModHyper},
2651			want: "lefthyper",
2652		},
2653		{
2654			name: "right hyper key alone",
2655			key:  Key{Code: KeyRightHyper, Mod: ModHyper},
2656			want: "righthyper",
2657		},
2658		{
2659			name: "left super key alone",
2660			key:  Key{Code: KeyLeftSuper, Mod: ModSuper},
2661			want: "leftsuper",
2662		},
2663		{
2664			name: "right super key alone",
2665			key:  Key{Code: KeyRightSuper, Mod: ModSuper},
2666			want: "rightsuper",
2667		},
2668		{
2669			name: "key with base code",
2670			key:  Key{Code: 'A', BaseCode: 'a'},
2671			want: "a",
2672		},
2673		{
2674			name: "unknown key with base code",
2675			key:  Key{Code: 99999, BaseCode: 'x'},
2676			want: "x",
2677		},
2678		{
2679			name: "printable rune",
2680			key:  Key{Code: '€'},
2681			want: "€",
2682		},
2683		{
2684			name: "unknown key without base code",
2685			key:  Key{Code: 99999},
2686			want: "𘚟",
2687		},
2688	}
2689
2690	for _, tt := range tests {
2691		t.Run(tt.name, func(t *testing.T) {
2692			got := tt.key.Keystroke()
2693			if got != tt.want {
2694				t.Errorf("Keystroke() = %q, want %q", got, tt.want)
2695			}
2696		})
2697	}
2698}
2699
2700// TestKeystrokeCoverage tests edge cases for Keystroke method
2701func TestKeystrokeCoverage(t *testing.T) {
2702	// Test a key that's not in keyTypeString and is KeySpace
2703	// This is actually impossible since KeySpace is in keyTypeString
2704	// But we can test a key that's not in keyTypeString and has BaseCode of KeySpace
2705	k := Key{Code: 999999, BaseCode: KeySpace}
2706	if got := k.Keystroke(); got != "space" {
2707		t.Errorf("Keystroke() = %q, want %q", got, "space")
2708	}
2709
2710	// Test a key that's not in keyTypeString, has no BaseCode, and is KeySpace
2711	// This would require KeySpace to not be in keyTypeString, which it is
2712	// So this branch might be unreachable
2713}
2714
2715func TestKeyStringMore(t *testing.T) {
2716	tests := []struct {
2717		name string
2718		key  Key
2719		want string
2720	}{
2721		{
2722			name: "space character",
2723			key:  Key{Code: KeySpace, Text: " "},
2724			want: "space",
2725		},
2726		{
2727			name: "empty text",
2728			key:  Key{Code: 'a', Text: ""},
2729			want: "a",
2730		},
2731		{
2732			name: "text with multiple characters",
2733			key:  Key{Code: KeyExtended, Text: "hello"},
2734			want: "hello",
2735		},
2736	}
2737
2738	for _, tt := range tests {
2739		t.Run(tt.name, func(t *testing.T) {
2740			got := tt.key.String()
2741			if got != tt.want {
2742				t.Errorf("String() = %q, want %q", got, tt.want)
2743			}
2744		})
2745	}
2746}