okami.mom/ultraviolet

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

nekomimi.petfix(input): keep control strings whole when they span reads7bfbaab

main
63.1 KiB2748 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			// An OSC cut off after the ESC of its ST may still be
958			// arriving, so input that ends there flushes as unknown.
959			[]Event{UnknownEvent("\x1b]11;#123456\x1b")},
960		},
961		{
962			"ignored apc",
963			[]byte("\x9f\x9c\x1b_hello\x1b\x1b_hello\x18\x1b_abc\x1b\\\x1ba"),
964			[]Event{
965				UnknownApcEvent("\x9f\x9c"),
966				UnknownApcEvent("\x1b_abc\x1b\\"),
967				KeyPressEvent{Code: 'a', Mod: ModAlt},
968			},
969		},
970		{
971			"alt+] alt+'",
972			[]byte("\x1b]\x1b'"),
973			[]Event{
974				KeyPressEvent{Code: ']', Mod: ModAlt},
975				KeyPressEvent{Code: '\'', Mod: ModAlt},
976			},
977		},
978		{
979			"alt+^ alt+&",
980			[]byte("\x1b^\x1b&"),
981			[]Event{
982				KeyPressEvent{Code: '^', Mod: ModAlt},
983				KeyPressEvent{Code: '&', Mod: ModAlt},
984			},
985		},
986		{
987			"a",
988			[]byte{'a'},
989			[]Event{
990				KeyPressEvent{Code: 'a', Text: "a"},
991			},
992		},
993		{
994			"space",
995			[]byte{' '},
996			[]Event{
997				KeyPressEvent{Code: KeySpace, Text: " "},
998			},
999		},
1000		{
1001			"a alt+a",
1002			[]byte{'a', '\x1b', 'a'},
1003			[]Event{
1004				KeyPressEvent{Code: 'a', Text: "a"},
1005				KeyPressEvent{Code: 'a', Mod: ModAlt},
1006			},
1007		},
1008		{
1009			"a alt+a a",
1010			[]byte{'a', '\x1b', 'a', 'a'},
1011			[]Event{
1012				KeyPressEvent{Code: 'a', Text: "a"},
1013				KeyPressEvent{Code: 'a', Mod: ModAlt},
1014				KeyPressEvent{Code: 'a', Text: "a"},
1015			},
1016		},
1017		{
1018			"ctrl+a",
1019			[]byte{byte(ansi.SOH)},
1020			[]Event{
1021				KeyPressEvent{Code: 'a', Mod: ModCtrl},
1022			},
1023		},
1024		{
1025			"ctrl+a ctrl+b",
1026			[]byte{byte(ansi.SOH), byte(ansi.STX)},
1027			[]Event{
1028				KeyPressEvent{Code: 'a', Mod: ModCtrl},
1029				KeyPressEvent{Code: 'b', Mod: ModCtrl},
1030			},
1031		},
1032		{
1033			"alt+a",
1034			[]byte{byte(0x1b), 'a'},
1035			[]Event{
1036				KeyPressEvent{Code: 'a', Mod: ModAlt},
1037			},
1038		},
1039		{
1040			"a b c d",
1041			[]byte{'a', 'b', 'c', 'd'},
1042			[]Event{
1043				KeyPressEvent{Code: 'a', Text: "a"},
1044				KeyPressEvent{Code: 'b', Text: "b"},
1045				KeyPressEvent{Code: 'c', Text: "c"},
1046				KeyPressEvent{Code: 'd', Text: "d"},
1047			},
1048		},
1049		{
1050			"up",
1051			[]byte("\x1b[A"),
1052			[]Event{
1053				KeyPressEvent{Code: KeyUp},
1054			},
1055		},
1056		{
1057			"wheel up",
1058			[]byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)},
1059			[]Event{
1060				MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1061			},
1062		},
1063		{
1064			"left motion release",
1065			[]byte{
1066				'\x1b', '[', 'M', byte(32) + 0b0010_0000, byte(32 + 33), byte(16 + 33),
1067				'\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33),
1068			},
1069			[]Event{
1070				MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1071				MouseReleaseEvent{X: 64, Y: 32, Button: MouseNone},
1072			},
1073		},
1074		{
1075			"shift+tab",
1076			[]byte{'\x1b', '[', 'Z'},
1077			[]Event{
1078				KeyPressEvent{Code: KeyTab, Mod: ModShift},
1079			},
1080		},
1081		{
1082			"enter",
1083			[]byte{'\r'},
1084			[]Event{KeyPressEvent{Code: KeyEnter}},
1085		},
1086		{
1087			"alt+enter",
1088			[]byte{'\x1b', '\r'},
1089			[]Event{
1090				KeyPressEvent{Code: KeyEnter, Mod: ModAlt},
1091			},
1092		},
1093		{
1094			"insert",
1095			[]byte{'\x1b', '[', '2', '~'},
1096			[]Event{
1097				KeyPressEvent{Code: KeyInsert},
1098			},
1099		},
1100		{
1101			"ctrl+alt+a",
1102			[]byte{'\x1b', byte(ansi.SOH)},
1103			[]Event{
1104				KeyPressEvent{Code: 'a', Mod: ModCtrl | ModAlt},
1105			},
1106		},
1107		{
1108			"CSI?----X?",
1109			[]byte{'\x1b', '[', '-', '-', '-', '-', 'X'},
1110			[]Event{UnknownCsiEvent([]byte{'\x1b', '[', '-', '-', '-', '-', 'X'})},
1111		},
1112		// Powershell sequences.
1113		{
1114			"up",
1115			[]byte{'\x1b', 'O', 'A'},
1116			[]Event{KeyPressEvent{Code: KeyUp}},
1117		},
1118		{
1119			"down",
1120			[]byte{'\x1b', 'O', 'B'},
1121			[]Event{KeyPressEvent{Code: KeyDown}},
1122		},
1123		{
1124			"right",
1125			[]byte{'\x1b', 'O', 'C'},
1126			[]Event{KeyPressEvent{Code: KeyRight}},
1127		},
1128		{
1129			"left",
1130			[]byte{'\x1b', 'O', 'D'},
1131			[]Event{KeyPressEvent{Code: KeyLeft}},
1132		},
1133		{
1134			"alt+enter",
1135			[]byte{'\x1b', '\x0d'},
1136			[]Event{KeyPressEvent{Code: KeyEnter, Mod: ModAlt}},
1137		},
1138		{
1139			"alt+backspace",
1140			[]byte{'\x1b', '\x7f'},
1141			[]Event{KeyPressEvent{Code: KeyBackspace, Mod: ModAlt}},
1142		},
1143		{
1144			"ctrl+space",
1145			[]byte{'\x00'},
1146			[]Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl}},
1147		},
1148		{
1149			"ctrl+alt+space",
1150			[]byte{'\x1b', '\x00'},
1151			[]Event{KeyPressEvent{Code: KeySpace, Mod: ModCtrl | ModAlt}},
1152		},
1153		{
1154			"esc",
1155			[]byte{'\x1b'},
1156			[]Event{KeyPressEvent{Code: KeyEscape}},
1157		},
1158		{
1159			"alt+esc",
1160			[]byte{'\x1b', '\x1b'},
1161			[]Event{KeyPressEvent{Code: KeyEscape, Mod: ModAlt}},
1162		},
1163		{
1164			"a b o",
1165			[]byte{
1166				'\x1b', '[', '2', '0', '0', '~',
1167				'a', ' ', 'b',
1168				'\x1b', '[', '2', '0', '1', '~',
1169				'o',
1170			},
1171			[]Event{
1172				PasteStartEvent{},
1173				PasteEvent{"a b"},
1174				PasteEndEvent{},
1175				KeyPressEvent{Code: 'o', Text: "o"},
1176			},
1177		},
1178		{
1179			"a\x03\nb",
1180			[]byte{
1181				'\x1b', '[', '2', '0', '0', '~',
1182				'a', '\x03', '\n', 'b',
1183				'\x1b', '[', '2', '0', '1', '~',
1184			},
1185			[]Event{
1186				PasteStartEvent{},
1187				PasteEvent{"a\x03\nb"},
1188				PasteEndEvent{},
1189			},
1190		},
1191		{
1192			"?0xfe?",
1193			[]byte{'\xfe'},
1194			[]Event{
1195				UnknownEvent(rune(0xfe)),
1196			},
1197		},
1198		{
1199			"a ?0xfe?   b",
1200			[]byte{'a', '\xfe', ' ', 'b'},
1201			[]Event{
1202				KeyPressEvent{Code: 'a', Text: "a"},
1203				UnknownEvent(rune(0xfe)),
1204				KeyPressEvent{Code: KeySpace, Text: " "},
1205				KeyPressEvent{Code: 'b', Text: "b"},
1206			},
1207		},
1208	}
1209
1210	for i, td := range testData {
1211		t.Run(fmt.Sprintf("%d: %s", i, td.keyname), func(t *testing.T) {
1212			events := testReadInputs(t, bytes.NewReader(td.in))
1213			var buf strings.Builder
1214			for i, event := range events {
1215				if i > 0 {
1216					buf.WriteByte(' ')
1217				}
1218				if s, ok := event.(fmt.Stringer); ok {
1219					buf.WriteString(s.String())
1220				} else {
1221					fmt.Fprintf(&buf, "%#v:%T", event, event)
1222				}
1223			}
1224
1225			if len(events) != len(td.out) {
1226				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)
1227			}
1228
1229			if len(td.out) != len(events) {
1230				t.Fatalf("expected %d events, got %d: %s", len(td.out), len(events), buf.String())
1231			}
1232			for i, e := range events {
1233				if !reflect.DeepEqual(td.out[i], e) {
1234					t.Errorf("expected event %d to be %T %v, got %T %v", i, td.out[i], td.out[i], e, e)
1235				}
1236			}
1237			if !reflect.DeepEqual(td.out, events) {
1238				t.Fatalf("expected:\n%#v\ngot:\n%#v", td.out, events)
1239			}
1240		})
1241	}
1242}
1243
1244func testReadInputs(t *testing.T, input io.Reader) []Event {
1245	drv := NewTerminalReader(input, "dumb")
1246	drv.SetLogger(TLogger{t})
1247
1248	eventc := make(chan Event)
1249	go func(t testing.TB) {
1250		defer close(eventc)
1251		if err := drv.StreamEvents(context.TODO(), eventc); err != nil {
1252			t.Errorf("error streaming events: %v", err)
1253		}
1254	}(t)
1255
1256	var events []Event
1257	for ev := range eventc {
1258		events = append(events, ev)
1259	}
1260
1261	return events
1262}
1263
1264// randTest defines the test input and expected output for a sequence
1265// of interleaved control sequences and control characters.
1266type randTest struct {
1267	data    []byte
1268	lengths []int
1269	names   []string
1270}
1271
1272// seed is the random seed to randomize the input. This helps check
1273// that all the sequences get ultimately exercised.
1274var seed = flag.Int64("seed", 0, "random seed (0 to autoselect)")
1275
1276// genRandomData generates a randomized test, with a random seed unless
1277// the seed flag was set.
1278func genRandomData(logfn func(int64), length int) randTest {
1279	// We'll use a random source. However, we give the user the option
1280	// to override it to a specific value for reproduceability.
1281	s := *seed
1282	if s == 0 {
1283		s = time.Now().UnixNano()
1284	}
1285	// Inform the user so they know what to reuse to get the same data.
1286	logfn(s)
1287	return genRandomDataWithSeed(s, length)
1288}
1289
1290// genRandomDataWithSeed generates a randomized test with a fixed seed.
1291func genRandomDataWithSeed(s int64, length int) randTest {
1292	src := rand.NewSource(s)
1293	r := rand.New(src)
1294
1295	// allseqs contains all the sequences, in sorted order. We sort
1296	// to make the test deterministic (when the seed is also fixed).
1297	type seqpair struct {
1298		seq  string
1299		name string
1300	}
1301	var allseqs []seqpair
1302	for seq, key := range sequences {
1303		allseqs = append(allseqs, seqpair{seq, key.String()})
1304	}
1305	sort.Slice(allseqs, func(i, j int) bool { return allseqs[i].seq < allseqs[j].seq })
1306
1307	// res contains the computed test.
1308	var res randTest
1309
1310	for len(res.data) < length {
1311		alt := r.Intn(2)
1312		prefix := ""
1313		esclen := 0
1314		if alt == 1 {
1315			prefix = "alt+"
1316			esclen = 1
1317		}
1318		kind := r.Intn(3)
1319		switch kind {
1320		case 0:
1321			// A control character.
1322			if alt == 1 {
1323				res.data = append(res.data, '\x1b')
1324			}
1325			res.data = append(res.data, 1)
1326			res.names = append(res.names, "ctrl+"+prefix+"a")
1327			res.lengths = append(res.lengths, 1+esclen)
1328
1329		case 1, 2:
1330			// A sequence.
1331			seqi := r.Intn(len(allseqs))
1332			s := allseqs[seqi]
1333			if strings.Contains(s.name, "alt+") || strings.Contains(s.name, "meta+") {
1334				esclen = 0
1335				prefix = ""
1336				alt = 0
1337			}
1338			if alt == 1 {
1339				res.data = append(res.data, '\x1b')
1340			}
1341			res.data = append(res.data, s.seq...)
1342			if strings.HasPrefix(s.name, "ctrl+") {
1343				prefix = "ctrl+" + prefix
1344			}
1345			name := prefix + strings.TrimPrefix(s.name, "ctrl+")
1346			res.names = append(res.names, name)
1347			res.lengths = append(res.lengths, len(s.seq)+esclen)
1348		}
1349	}
1350	return res
1351}
1352
1353func FuzzParseSequence(f *testing.F) {
1354	var p EventDecoder
1355	for seq := range sequences {
1356		f.Add(seq)
1357	}
1358	f.Add("\x1b]52;?\x07")                      // OSC 52
1359	f.Add("\x1b]11;rgb:0000/0000/0000\x1b\\")   // OSC 11
1360	f.Add("\x1bP>|charm terminal(0.1.2)\x1b\\") // DCS (XTVERSION)
1361	f.Add("\x1b_Gi=123\x1b\\")                  // APC
1362	f.Fuzz(func(t *testing.T, seq string) {
1363		n, _ := p.Decode([]byte(seq))
1364		if n == 0 && seq != "" {
1365			t.Errorf("expected a non-zero width for %q", seq)
1366		}
1367	})
1368}
1369
1370// BenchmarkDetectSequenceMap benchmarks the map-based sequence
1371// detector.
1372func BenchmarkDetectSequenceMap(b *testing.B) {
1373	var p EventDecoder
1374	td := genRandomDataWithSeed(123, 10000)
1375	for i := 0; i < b.N; i++ {
1376		for j, w := 0, 0; j < len(td.data); j += w {
1377			w, _ = p.Decode(td.data[j:])
1378		}
1379	}
1380}
1381
1382func TestMouseEvent_String(t *testing.T) {
1383	tt := []struct {
1384		name     string
1385		event    Event
1386		expected string
1387	}{
1388		{
1389			name:     "unknown",
1390			event:    MouseClickEvent{Button: MouseButton(0xff)},
1391			expected: "unknown",
1392		},
1393		{
1394			name:     "left",
1395			event:    MouseClickEvent{Button: MouseLeft},
1396			expected: "left",
1397		},
1398		{
1399			name:     "right",
1400			event:    MouseClickEvent{Button: MouseRight},
1401			expected: "right",
1402		},
1403		{
1404			name:     "middle",
1405			event:    MouseClickEvent{Button: MouseMiddle},
1406			expected: "middle",
1407		},
1408		{
1409			name:     "release",
1410			event:    MouseReleaseEvent{Button: MouseNone},
1411			expected: "",
1412		},
1413		{
1414			name:     "wheelup",
1415			event:    MouseWheelEvent{Button: MouseWheelUp},
1416			expected: "wheelup",
1417		},
1418		{
1419			name:     "wheeldown",
1420			event:    MouseWheelEvent{Button: MouseWheelDown},
1421			expected: "wheeldown",
1422		},
1423		{
1424			name:     "wheelleft",
1425			event:    MouseWheelEvent{Button: MouseWheelLeft},
1426			expected: "wheelleft",
1427		},
1428		{
1429			name:     "wheelright",
1430			event:    MouseWheelEvent{Button: MouseWheelRight},
1431			expected: "wheelright",
1432		},
1433		{
1434			name:     "motion",
1435			event:    MouseMotionEvent{Button: MouseNone},
1436			expected: "motion",
1437		},
1438		{
1439			name:     "shift+left",
1440			event:    MouseReleaseEvent{Button: MouseLeft, Mod: ModShift},
1441			expected: "shift+left",
1442		},
1443		{
1444			name: "shift+left", event: MouseClickEvent{Button: MouseLeft, Mod: ModShift},
1445			expected: "shift+left",
1446		},
1447		{
1448			name:     "ctrl+shift+left",
1449			event:    MouseClickEvent{Button: MouseLeft, Mod: ModCtrl | ModShift},
1450			expected: "ctrl+shift+left",
1451		},
1452		{
1453			name:     "alt+left",
1454			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt},
1455			expected: "alt+left",
1456		},
1457		{
1458			name:     "ctrl+left",
1459			event:    MouseClickEvent{Button: MouseLeft, Mod: ModCtrl},
1460			expected: "ctrl+left",
1461		},
1462		{
1463			name:     "ctrl+alt+left",
1464			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt | ModCtrl},
1465			expected: "ctrl+alt+left",
1466		},
1467		{
1468			name:     "ctrl+alt+shift+left",
1469			event:    MouseClickEvent{Button: MouseLeft, Mod: ModAlt | ModCtrl | ModShift},
1470			expected: "ctrl+alt+shift+left",
1471		},
1472		{
1473			name:     "ignore coordinates",
1474			event:    MouseClickEvent{X: 100, Y: 200, Button: MouseLeft},
1475			expected: "left",
1476		},
1477		{
1478			name:     "broken type",
1479			event:    MouseClickEvent{Button: MouseButton(120)},
1480			expected: "unknown",
1481		},
1482	}
1483
1484	for i := range tt {
1485		tc := tt[i]
1486
1487		t.Run(tc.name, func(t *testing.T) {
1488			actual := fmt.Sprint(tc.event)
1489
1490			if tc.expected != actual {
1491				t.Fatalf("expected %q but got %q",
1492					tc.expected,
1493					actual,
1494				)
1495			}
1496		})
1497	}
1498}
1499
1500func TestParseX10MouseDownEvent(t *testing.T) {
1501	encode := func(b byte, x, y int) []byte {
1502		return []byte{
1503			'\x1b',
1504			'[',
1505			'M',
1506			byte(32) + b,
1507			byte(x + 32 + 1),
1508			byte(y + 32 + 1),
1509		}
1510	}
1511
1512	tt := []struct {
1513		name     string
1514		buf      []byte
1515		expected Event
1516	}{
1517		// Position.
1518		{
1519			name:     "zero position",
1520			buf:      encode(0b0000_0000, 0, 0),
1521			expected: MouseClickEvent{X: 0, Y: 0, Button: MouseLeft},
1522		},
1523		{
1524			name:     "max position",
1525			buf:      encode(0b0000_0000, 222, 222), // Because 255 (max int8) - 32 - 1.
1526			expected: MouseClickEvent{X: 222, Y: 222, Button: MouseLeft},
1527		},
1528		// Simple.
1529		{
1530			name:     "left",
1531			buf:      encode(0b0000_0000, 32, 16),
1532			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
1533		},
1534		{
1535			name:     "left in motion",
1536			buf:      encode(0b0010_0000, 32, 16),
1537			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1538		},
1539		{
1540			name:     "middle",
1541			buf:      encode(0b0000_0001, 32, 16),
1542			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseMiddle},
1543		},
1544		{
1545			name:     "middle in motion",
1546			buf:      encode(0b0010_0001, 32, 16),
1547			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseMiddle},
1548		},
1549		{
1550			name:     "right",
1551			buf:      encode(0b0000_0010, 32, 16),
1552			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseRight},
1553		},
1554		{
1555			name:     "right in motion",
1556			buf:      encode(0b0010_0010, 32, 16),
1557			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseRight},
1558		},
1559		{
1560			name:     "motion",
1561			buf:      encode(0b0010_0011, 32, 16),
1562			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseNone},
1563		},
1564		{
1565			name:     "wheel up",
1566			buf:      encode(0b0100_0000, 32, 16),
1567			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1568		},
1569		{
1570			name:     "wheel down",
1571			buf:      encode(0b0100_0001, 32, 16),
1572			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelDown},
1573		},
1574		{
1575			name:     "wheel left",
1576			buf:      encode(0b0100_0010, 32, 16),
1577			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelLeft},
1578		},
1579		{
1580			name:     "wheel right",
1581			buf:      encode(0b0100_0011, 32, 16),
1582			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelRight},
1583		},
1584		{
1585			name:     "release",
1586			buf:      encode(0b0000_0011, 32, 16),
1587			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseNone},
1588		},
1589		{
1590			name:     "backward",
1591			buf:      encode(0b1000_0000, 32, 16),
1592			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseBackward},
1593		},
1594		{
1595			name:     "forward",
1596			buf:      encode(0b1000_0001, 32, 16),
1597			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseForward},
1598		},
1599		{
1600			name:     "button 10",
1601			buf:      encode(0b1000_0010, 32, 16),
1602			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseButton10},
1603		},
1604		{
1605			name:     "button 11",
1606			buf:      encode(0b1000_0011, 32, 16),
1607			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseButton11},
1608		},
1609		// Combinations.
1610		{
1611			name:     "alt+right",
1612			buf:      encode(0b0000_1010, 32, 16),
1613			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1614		},
1615		{
1616			name:     "ctrl+right",
1617			buf:      encode(0b0001_0010, 32, 16),
1618			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1619		},
1620		{
1621			name:     "left in motion",
1622			buf:      encode(0b0010_0000, 32, 16),
1623			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1624		},
1625		{
1626			name:     "alt+right in motion",
1627			buf:      encode(0b0010_1010, 32, 16),
1628			expected: MouseMotionEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1629		},
1630		{
1631			name:     "ctrl+right in motion",
1632			buf:      encode(0b0011_0010, 32, 16),
1633			expected: MouseMotionEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1634		},
1635		{
1636			name:     "ctrl+alt+right",
1637			buf:      encode(0b0001_1010, 32, 16),
1638			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight},
1639		},
1640		{
1641			name:     "ctrl+wheel up",
1642			buf:      encode(0b0101_0000, 32, 16),
1643			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelUp},
1644		},
1645		{
1646			name:     "alt+wheel down",
1647			buf:      encode(0b0100_1001, 32, 16),
1648			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown},
1649		},
1650		{
1651			name:     "ctrl+alt+wheel down",
1652			buf:      encode(0b0101_1001, 32, 16),
1653			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown},
1654		},
1655		// Overflow position.
1656		{
1657			name:     "overflow position",
1658			buf:      encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1.
1659			expected: MouseMotionEvent{X: -6, Y: -33, Button: MouseLeft},
1660		},
1661	}
1662
1663	for i := range tt {
1664		tc := tt[i]
1665
1666		t.Run(tc.name, func(t *testing.T) {
1667			actual := parseX10MouseEvent(tc.buf)
1668
1669			if tc.expected != actual {
1670				t.Fatalf("expected %#v but got %#v",
1671					tc.expected,
1672					actual,
1673				)
1674			}
1675		})
1676	}
1677}
1678
1679func TestParseSGRMouseEvent(t *testing.T) {
1680	type csiSequence struct {
1681		params []ansi.Param
1682		cmd    ansi.Cmd
1683	}
1684	encode := func(b, x, y int, r bool) *csiSequence {
1685		re := 'M'
1686		if r {
1687			re = 'm'
1688		}
1689		return &csiSequence{
1690			params: []ansi.Param{
1691				ansi.Param(b),
1692				ansi.Param(x + 1),
1693				ansi.Param(y + 1),
1694			},
1695			cmd: ansi.Cmd(re) | ('<' << parser.PrefixShift),
1696		}
1697	}
1698
1699	tt := []struct {
1700		name     string
1701		buf      *csiSequence
1702		expected Event
1703	}{
1704		// Position.
1705		{
1706			name:     "zero position",
1707			buf:      encode(0, 0, 0, false),
1708			expected: MouseClickEvent{X: 0, Y: 0, Button: MouseLeft},
1709		},
1710		{
1711			name:     "225 position",
1712			buf:      encode(0, 225, 225, false),
1713			expected: MouseClickEvent{X: 225, Y: 225, Button: MouseLeft},
1714		},
1715		// Simple.
1716		{
1717			name:     "left",
1718			buf:      encode(0, 32, 16, false),
1719			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseLeft},
1720		},
1721		{
1722			name:     "left in motion",
1723			buf:      encode(32, 32, 16, false),
1724			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseLeft},
1725		},
1726		{
1727			name:     "left",
1728			buf:      encode(0, 32, 16, true),
1729			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseLeft},
1730		},
1731		{
1732			name:     "middle",
1733			buf:      encode(1, 32, 16, false),
1734			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseMiddle},
1735		},
1736		{
1737			name:     "middle in motion",
1738			buf:      encode(33, 32, 16, false),
1739			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseMiddle},
1740		},
1741		{
1742			name:     "middle",
1743			buf:      encode(1, 32, 16, true),
1744			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseMiddle},
1745		},
1746		{
1747			name:     "right",
1748			buf:      encode(2, 32, 16, false),
1749			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseRight},
1750		},
1751		{
1752			name:     "right",
1753			buf:      encode(2, 32, 16, true),
1754			expected: MouseReleaseEvent{X: 32, Y: 16, Button: MouseRight},
1755		},
1756		{
1757			name:     "motion",
1758			buf:      encode(35, 32, 16, false),
1759			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseNone},
1760		},
1761		{
1762			name:     "wheel up",
1763			buf:      encode(64, 32, 16, false),
1764			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelUp},
1765		},
1766		{
1767			name:     "wheel down",
1768			buf:      encode(65, 32, 16, false),
1769			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelDown},
1770		},
1771		{
1772			name:     "wheel left",
1773			buf:      encode(66, 32, 16, false),
1774			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelLeft},
1775		},
1776		{
1777			name:     "wheel right",
1778			buf:      encode(67, 32, 16, false),
1779			expected: MouseWheelEvent{X: 32, Y: 16, Button: MouseWheelRight},
1780		},
1781		{
1782			name:     "backward",
1783			buf:      encode(128, 32, 16, false),
1784			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseBackward},
1785		},
1786		{
1787			name:     "backward in motion",
1788			buf:      encode(160, 32, 16, false),
1789			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseBackward},
1790		},
1791		{
1792			name:     "forward",
1793			buf:      encode(129, 32, 16, false),
1794			expected: MouseClickEvent{X: 32, Y: 16, Button: MouseForward},
1795		},
1796		{
1797			name:     "forward in motion",
1798			buf:      encode(161, 32, 16, false),
1799			expected: MouseMotionEvent{X: 32, Y: 16, Button: MouseForward},
1800		},
1801		// Combinations.
1802		{
1803			name:     "alt+right",
1804			buf:      encode(10, 32, 16, false),
1805			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight},
1806		},
1807		{
1808			name:     "ctrl+right",
1809			buf:      encode(18, 32, 16, false),
1810			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight},
1811		},
1812		{
1813			name:     "ctrl+alt+right",
1814			buf:      encode(26, 32, 16, false),
1815			expected: MouseClickEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight},
1816		},
1817		{
1818			name:     "alt+wheel",
1819			buf:      encode(73, 32, 16, false),
1820			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown},
1821		},
1822		{
1823			name:     "ctrl+wheel",
1824			buf:      encode(81, 32, 16, false),
1825			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelDown},
1826		},
1827		{
1828			name:     "ctrl+alt+wheel",
1829			buf:      encode(89, 32, 16, false),
1830			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown},
1831		},
1832		{
1833			name:     "ctrl+alt+shift+wheel",
1834			buf:      encode(93, 32, 16, false),
1835			expected: MouseWheelEvent{X: 32, Y: 16, Mod: ModAlt | ModShift | ModCtrl, Button: MouseWheelDown},
1836		},
1837	}
1838
1839	for i := range tt {
1840		tc := tt[i]
1841
1842		t.Run(tc.name, func(t *testing.T) {
1843			actual := parseSGRMouseEvent(tc.buf.cmd, tc.buf.params)
1844			if tc.expected != actual {
1845				t.Fatalf("expected %#v but got %#v",
1846					tc.expected,
1847					actual,
1848				)
1849			}
1850		})
1851	}
1852}
1853
1854func TestMousePixelToCell(t *testing.T) {
1855	tt := []struct {
1856		name     string
1857		mouse    Mouse
1858		ws       *Winsize
1859		expected Mouse
1860	}{
1861		{
1862			name:     "standard 80x24 terminal",
1863			mouse:    Mouse{X: 480, Y: 312, Button: MouseLeft},
1864			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1865			expected: Mouse{X: 68, Y: 24, Button: MouseLeft},
1866		},
1867		{
1868			name:     "top-left pixel",
1869			mouse:    Mouse{X: 0, Y: 0, Button: MouseLeft},
1870			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1871			expected: Mouse{X: 0, Y: 0, Button: MouseLeft},
1872		},
1873		{
1874			name:     "bottom-right pixel",
1875			mouse:    Mouse{X: 559, Y: 311, Button: MouseRight},
1876			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1877			expected: Mouse{X: 79, Y: 23, Button: MouseRight},
1878		},
1879		{
1880			name:     "exact cell boundary pixel",
1881			mouse:    Mouse{X: 6, Y: 12, Button: MouseLeft},
1882			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 480, Ypixel: 288},
1883			expected: Mouse{X: 1, Y: 1, Button: MouseLeft},
1884		},
1885		{
1886			name:     "preserves modifiers",
1887			mouse:    Mouse{X: 240, Y: 156, Button: MouseMiddle, Mod: ModAlt},
1888			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1889			expected: Mouse{X: 34, Y: 12, Button: MouseMiddle, Mod: ModAlt},
1890		},
1891		{
1892			name:     "zero pixel dimensions returns zero cell",
1893			mouse:    Mouse{X: 480, Y: 312, Button: MouseLeft},
1894			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 0, Ypixel: 0},
1895			expected: Mouse{X: 0, Y: 0, Button: MouseLeft},
1896		},
1897		{
1898			name:     "pixel position larger than window",
1899			mouse:    Mouse{X: 1200, Y: 800, Button: MouseLeft},
1900			ws:       &Winsize{Row: 24, Col: 80, Xpixel: 560, Ypixel: 312},
1901			expected: Mouse{X: 171, Y: 61, Button: MouseLeft},
1902		},
1903	}
1904
1905	for i := range tt {
1906		tc := tt[i]
1907
1908		t.Run(tc.name, func(t *testing.T) {
1909			actual := MousePixelToCell(tc.mouse, tc.ws)
1910			if tc.expected != actual {
1911				t.Fatalf("expected %#v but got %#v",
1912					tc.expected,
1913					actual,
1914				)
1915			}
1916		})
1917	}
1918}
1919
1920// TestMatchStrings tests the MatchStrings method
1921func TestMatchStrings(t *testing.T) {
1922	tests := []struct {
1923		name   string
1924		key    Key
1925		inputs []string
1926		want   bool
1927	}{
1928		{
1929			name:   "matches first string",
1930			key:    Key{Code: 'a', Mod: ModCtrl},
1931			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1932			want:   true,
1933		},
1934		{
1935			name:   "matches middle string",
1936			key:    Key{Code: 'b', Mod: ModCtrl},
1937			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1938			want:   true,
1939		},
1940		{
1941			name:   "matches last string",
1942			key:    Key{Code: 'c', Mod: ModCtrl},
1943			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1944			want:   true,
1945		},
1946		{
1947			name:   "no match",
1948			key:    Key{Code: 'd', Mod: ModCtrl},
1949			inputs: []string{"ctrl+a", "ctrl+b", "ctrl+c"},
1950			want:   false,
1951		},
1952		{
1953			name:   "empty inputs",
1954			key:    Key{Code: 'a', Mod: ModCtrl},
1955			inputs: []string{},
1956			want:   false,
1957		},
1958	}
1959
1960	for _, tt := range tests {
1961		t.Run(tt.name, func(t *testing.T) {
1962			got := tt.key.MatchString(tt.inputs...)
1963			if got != tt.want {
1964				t.Errorf("MatchStrings() = %v, want %v", got, tt.want)
1965			}
1966		})
1967	}
1968}
1969
1970func TestKeyMatchString(t *testing.T) {
1971	cases := []struct {
1972		name  string
1973		key   Key
1974		input string
1975		want  bool
1976	}{
1977		{
1978			name:  "ctrl+a",
1979			key:   Key{Code: 'a', Mod: ModCtrl},
1980			input: "ctrl+a",
1981			want:  true,
1982		},
1983		{
1984			name:  "ctrl+alt+a",
1985			key:   Key{Code: 'a', Mod: ModCtrl | ModAlt},
1986			input: "ctrl+alt+a",
1987			want:  true,
1988		},
1989		{
1990			name:  "ctrl+alt+shift+a",
1991			key:   Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift},
1992			input: "ctrl+alt+shift+a",
1993			want:  true,
1994		},
1995		{
1996			name:  "H",
1997			key:   Key{Code: 'H', Text: "H"},
1998			input: "H",
1999			want:  true,
2000		},
2001		{
2002			name:  "shift+h",
2003			key:   Key{Code: 'h', Mod: ModShift, Text: "H"},
2004			input: "H",
2005			want:  true,
2006		},
2007		{
2008			name:  "?",
2009			key:   Key{Code: '/', Mod: ModShift, Text: "?"},
2010			input: "?",
2011			want:  true,
2012		},
2013		{
2014			name:  "shift+/",
2015			key:   Key{Code: '/', Mod: ModShift, Text: "?"},
2016			input: "shift+/",
2017			want:  true,
2018		},
2019		{
2020			name:  "capslock+a",
2021			key:   Key{Code: 'a', Mod: ModCapsLock, Text: "A"},
2022			input: "A",
2023			want:  true,
2024		},
2025		{
2026			name:  "ctrl+capslock+a",
2027			key:   Key{Code: 'a', Mod: ModCtrl | ModCapsLock},
2028			input: "ctrl+a",
2029			want:  false,
2030		},
2031		{
2032			name:  "space",
2033			key:   Key{Code: KeySpace, Text: " "},
2034			input: "space",
2035			want:  true,
2036		},
2037		{
2038			name:  "whitespace",
2039			key:   Key{Code: KeySpace, Text: " "},
2040			input: " ",
2041			want:  true,
2042		},
2043		{
2044			name:  "ctrl+space",
2045			key:   Key{Code: KeySpace, Mod: ModCtrl},
2046			input: "ctrl+space",
2047			want:  true,
2048		},
2049		{
2050			name:  "shift+whitespace",
2051			key:   Key{Code: KeySpace, Mod: ModShift, Text: " "},
2052			input: " ",
2053			want:  true,
2054		},
2055		{
2056			name:  "shift+space",
2057			key:   Key{Code: KeySpace, Mod: ModShift, Text: " "},
2058			input: "shift+space",
2059			want:  true,
2060		},
2061		{
2062			name:  "meta modifier",
2063			key:   Key{Code: 'a', Mod: ModMeta},
2064			input: "meta+a",
2065			want:  true,
2066		},
2067		{
2068			name:  "hyper modifier",
2069			key:   Key{Code: 'a', Mod: ModHyper},
2070			input: "hyper+a",
2071			want:  true,
2072		},
2073		{
2074			name:  "super modifier",
2075			key:   Key{Code: 'a', Mod: ModSuper},
2076			input: "super+a",
2077			want:  true,
2078		},
2079		{
2080			name:  "scrolllock modifier",
2081			key:   Key{Code: 'a', Mod: ModScrollLock},
2082			input: "scrolllock+a",
2083			want:  true,
2084		},
2085		{
2086			name:  "numlock modifier",
2087			key:   Key{Code: 'a', Mod: ModNumLock},
2088			input: "numlock+a",
2089			want:  true,
2090		},
2091		{
2092			name:  "multi-rune key",
2093			key:   Key{Code: KeyExtended, Text: "hello"},
2094			input: "hello",
2095			want:  true,
2096		},
2097		{
2098			name:  "enter key",
2099			key:   Key{Code: KeyEnter},
2100			input: "enter",
2101			want:  true,
2102		},
2103		{
2104			name:  "tab key",
2105			key:   Key{Code: KeyTab},
2106			input: "tab",
2107			want:  true,
2108		},
2109		{
2110			name:  "escape key",
2111			key:   Key{Code: KeyEscape},
2112			input: "esc",
2113			want:  true,
2114		},
2115		{
2116			name:  "f1 key",
2117			key:   Key{Code: KeyF1},
2118			input: "f1",
2119			want:  true,
2120		},
2121		{
2122			name:  "backspace key",
2123			key:   Key{Code: KeyBackspace},
2124			input: "backspace",
2125			want:  true,
2126		},
2127		{
2128			name:  "delete key",
2129			key:   Key{Code: KeyDelete},
2130			input: "delete",
2131			want:  true,
2132		},
2133		{
2134			name:  "home key",
2135			key:   Key{Code: KeyHome},
2136			input: "home",
2137			want:  true,
2138		},
2139		{
2140			name:  "end key",
2141			key:   Key{Code: KeyEnd},
2142			input: "end",
2143			want:  true,
2144		},
2145		{
2146			name:  "pgup key",
2147			key:   Key{Code: KeyPgUp},
2148			input: "pgup",
2149			want:  true,
2150		},
2151		{
2152			name:  "pgdown key",
2153			key:   Key{Code: KeyPgDown},
2154			input: "pgdown",
2155			want:  true,
2156		},
2157		{
2158			name:  "up arrow",
2159			key:   Key{Code: KeyUp},
2160			input: "up",
2161			want:  true,
2162		},
2163		{
2164			name:  "down arrow",
2165			key:   Key{Code: KeyDown},
2166			input: "down",
2167			want:  true,
2168		},
2169		{
2170			name:  "left arrow",
2171			key:   Key{Code: KeyLeft},
2172			input: "left",
2173			want:  true,
2174		},
2175		{
2176			name:  "right arrow",
2177			key:   Key{Code: KeyRight},
2178			input: "right",
2179			want:  true,
2180		},
2181		{
2182			name:  "insert key",
2183			key:   Key{Code: KeyInsert},
2184			input: "insert",
2185			want:  true,
2186		},
2187		{
2188			name:  "single printable character",
2189			key:   Key{Code: '1', Text: "1"},
2190			input: "1",
2191			want:  true,
2192		},
2193		{
2194			name:  "uppercase letter without shift",
2195			key:   Key{Code: 'A', Text: "A"},
2196			input: "A",
2197			want:  true,
2198		},
2199		{
2200			name:  "no match different key",
2201			key:   Key{Code: 'a', Mod: ModCtrl},
2202			input: "ctrl+b",
2203			want:  false,
2204		},
2205		{
2206			name:  "no match different modifier",
2207			key:   Key{Code: 'a', Mod: ModCtrl},
2208			input: "alt+a",
2209			want:  false,
2210		},
2211		{
2212			name:  "unknown key name",
2213			key:   Key{Code: 'x'},
2214			input: "unknownkey",
2215			want:  false,
2216		},
2217		{
2218			name:  "multi-rune string that doesn't match",
2219			key:   Key{Code: 'a'},
2220			input: "hello",
2221			want:  false,
2222		},
2223		{
2224			name:  "printable character with ctrl modifier",
2225			key:   Key{Code: 'a', Mod: ModCtrl},
2226			input: "a",
2227			want:  false,
2228		},
2229		{
2230			name:  "lowercase letter with shift",
2231			key:   Key{Code: 'h', Mod: ModShift},
2232			input: "shift+h",
2233			want:  true,
2234		},
2235		{
2236			name:  "uppercase letter with capslock",
2237			key:   Key{Code: 'h', Mod: ModCapsLock},
2238			input: "capslock+h",
2239			want:  true,
2240		},
2241	}
2242
2243	for i, tc := range cases {
2244		t.Run(fmt.Sprintf("%d: %s", i, tc.name), func(t *testing.T) {
2245			got := tc.key.MatchString(tc.input)
2246			if got != tc.want {
2247				t.Errorf("expected %v but got %v", tc.want, got)
2248			}
2249		})
2250	}
2251}
2252
2253// TestSplitSequences tests that string-terminated sequences work correctly
2254// when split across multiple read() calls.
2255func TestSplitSequences(t *testing.T) {
2256	tests := []struct {
2257		name   string
2258		chunks [][]byte
2259		want   []Event
2260		delay  time.Duration
2261		limit  int // limit the number of bytes read at once
2262	}{
2263		{
2264			name: "OSC 11 background color with ST terminator",
2265			chunks: [][]byte{
2266				[]byte("\x1b]11;rgb:1a1a/1b1b/2c2c"),
2267				[]byte("\x1b\\"),
2268			},
2269			want: []Event{
2270				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1a1a/1b1b/2c2c")},
2271			},
2272		},
2273		{
2274			name: "OSC 11 background color with BEL terminator",
2275			chunks: [][]byte{
2276				[]byte("\x1b]11;rgb:1a1a/1b1b/2c2c"),
2277				[]byte("\x07"),
2278			},
2279			want: []Event{
2280				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1a1a/1b1b/2c2c")},
2281			},
2282		},
2283		{
2284			name: "OSC 10 foreground color split",
2285			chunks: [][]byte{
2286				[]byte("\x1b]10;rgb:ffff/0000/"),
2287				[]byte("0000\x1b\\"),
2288			},
2289			want: []Event{
2290				ForegroundColorEvent{Color: ansi.XParseColor("rgb:ffff/0000/0000")},
2291			},
2292		},
2293		{
2294			name: "OSC 12 cursor color split",
2295			chunks: [][]byte{
2296				[]byte("\x1b]12;rgb:"),
2297				[]byte("8080/8080/8080\x07"),
2298			},
2299			want: []Event{
2300				CursorColorEvent{Color: ansi.XParseColor("rgb:8080/8080/8080")},
2301			},
2302		},
2303		{
2304			name: "DCS sequence split",
2305			chunks: [][]byte{
2306				[]byte("\x1bP1$r"),
2307				[]byte("test\x1b\\"),
2308			},
2309			want: []Event{
2310				UnknownDcsEvent("\x1bP1$rtest\x1b\\"),
2311			},
2312		},
2313		{
2314			name: "long DCS sequence split",
2315			chunks: [][]byte{
2316				[]byte("\x1bP1$raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdef"),
2317				[]byte("test\x1b\\"),
2318			},
2319			want: []Event{
2320				UnknownDcsEvent("\x1bP1$raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcdeftest\x1b\\"),
2321			},
2322			limit: 256,
2323		},
2324		{
2325			name: "APC sequence split",
2326			chunks: [][]byte{
2327				[]byte("\x1b_T"),
2328				[]byte("test\x1b\\"),
2329			},
2330			want: []Event{
2331				UnknownApcEvent("\x1b_Ttest\x1b\\"),
2332			},
2333		},
2334		{
2335			name: "Multiple chunks OSC",
2336			chunks: [][]byte{
2337				[]byte("\x1b]11;"),
2338				[]byte("rgb:1234/"),
2339				[]byte("5678/9abc\x07"),
2340			},
2341			want: []Event{
2342				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1234/5678/9abc")},
2343			},
2344		},
2345		{
2346			name: "OSC followed by regular key",
2347			chunks: [][]byte{
2348				[]byte("\x1b]11;rgb:1111/2222/3333"),
2349				[]byte("\x07a"),
2350			},
2351			want: []Event{
2352				BackgroundColorEvent{Color: ansi.XParseColor("rgb:1111/2222/3333")},
2353				KeyPressEvent{Code: 'a', Text: "a"},
2354			},
2355		},
2356		{
2357			name: "unknown sequence after timeout",
2358			chunks: [][]byte{
2359				[]byte("\x1b]11;rgb:1111/2222/3333"),
2360				[]byte("abc"),
2361				[]byte("x"),
2362				[]byte("x"),
2363				[]byte("x"),
2364				[]byte("x"),
2365			},
2366			want: []Event{
2367				UnknownEvent("\x1b]11;rgb:1111/2222/3333"),
2368				KeyPressEvent{Code: 'a', Text: "a"},
2369				KeyPressEvent{Code: 'b', Text: "b"},
2370				KeyPressEvent{Code: 'c', Text: "c"},
2371				KeyPressEvent{Code: 'x', Text: "x"},
2372				KeyPressEvent{Code: 'x', Text: "x"},
2373				KeyPressEvent{Code: 'x', Text: "x"},
2374				KeyPressEvent{Code: 'x', Text: "x"},
2375			},
2376			delay: 60 * time.Millisecond, // Ensure the timeout is triggered.
2377		},
2378		{
2379			name: "multiple broken down sequences",
2380			chunks: [][]byte{
2381				[]byte("\x1b[B"),
2382				[]byte("\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B"),
2383				[]byte("\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b["),
2384				[]byte("B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b"),
2385				[]byte("[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B\x1b[B"),
2386			},
2387			want: []Event{
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				KeyPressEvent{Code: KeyDown},
2430				KeyPressEvent{Code: KeyDown},
2431			},
2432		},
2433	}
2434
2435	for _, tt := range tests {
2436		t.Run(tt.name, func(t *testing.T) {
2437			rds := make([]io.Reader, len(tt.chunks))
2438			for i, chunk := range tt.chunks {
2439				rds[i] = bytes.NewReader(chunk)
2440			}
2441			var r io.Reader
2442			limit := 32
2443			if tt.limit > 0 {
2444				limit = tt.limit
2445			}
2446			if tt.delay > 0 {
2447				r = DelayedLimitedReader(io.MultiReader(rds...), limit, tt.delay)
2448			} else {
2449				r = LimitedReader(io.MultiReader(rds...), limit)
2450			}
2451			ir := NewTerminalReader(r, "xterm-256color")
2452			ir.SetLogger(TLogger{TB: t})
2453
2454			eventc := make(chan Event)
2455			go func(t testing.TB) {
2456				defer close(eventc)
2457				if err := ir.StreamEvents(t.Context(), eventc); err != nil {
2458					t.Errorf("error streaming events: %v", err)
2459				}
2460			}(t)
2461
2462			var events []Event
2463			for ev := range eventc {
2464				events = append(events, ev)
2465			}
2466
2467			if len(events) != len(tt.want) {
2468				t.Fatalf("got %d events, want %d: %#v", len(events), len(tt.want), events)
2469			}
2470
2471			for i, want := range tt.want {
2472				if !reflect.DeepEqual(events[i], want) {
2473					t.Errorf("event %d: got %#v, want %#v", i, events[i], want)
2474				}
2475			}
2476		})
2477	}
2478}
2479
2480// limitedSleepReader is a simple io.Reader that limits the number of bytes
2481// read on each Read call and simulates a delay to mimic terminal input
2482// behavior.
2483type limitedSleepReader struct {
2484	r io.Reader
2485	n int
2486	d time.Duration
2487	i int
2488}
2489
2490func DelayedLimitedReader(r io.Reader, n int, d time.Duration) io.Reader {
2491	return &limitedSleepReader{r: r, n: n, d: d}
2492}
2493
2494func LimitedReader(r io.Reader, n int) io.Reader {
2495	return &limitedSleepReader{r: r, n: n}
2496}
2497
2498func (r *limitedSleepReader) Read(p []byte) (n int, err error) {
2499	if r.i > 0 && r.d > 0 {
2500		time.Sleep(r.d)
2501	}
2502	if r.n <= 0 {
2503		return 0, io.EOF
2504	}
2505	if len(p) > r.n {
2506		p = p[0:r.n]
2507	}
2508	n, err = r.r.Read(p)
2509	r.i++
2510	return n, err
2511}
2512
2513type TLogger struct{ testing.TB }
2514
2515func (t TLogger) Printf(format string, args ...interface{}) {
2516	t.Helper()
2517	if t.TB == nil {
2518		return
2519	}
2520	t.Logf(format, args...)
2521}
2522
2523// TestKeystroke tests the Keystroke method
2524func TestKeystroke(t *testing.T) {
2525	tests := []struct {
2526		name string
2527		key  Key
2528		want string
2529	}{
2530		{
2531			name: "simple key",
2532			key:  Key{Code: 'a'},
2533			want: "a",
2534		},
2535		{
2536			name: "ctrl+a",
2537			key:  Key{Code: 'a', Mod: ModCtrl},
2538			want: "ctrl+a",
2539		},
2540		{
2541			name: "alt+a",
2542			key:  Key{Code: 'a', Mod: ModAlt},
2543			want: "alt+a",
2544		},
2545		{
2546			name: "shift+a",
2547			key:  Key{Code: 'a', Mod: ModShift},
2548			want: "shift+a",
2549		},
2550		{
2551			name: "meta+a",
2552			key:  Key{Code: 'a', Mod: ModMeta},
2553			want: "meta+a",
2554		},
2555		{
2556			name: "hyper+a",
2557			key:  Key{Code: 'a', Mod: ModHyper},
2558			want: "hyper+a",
2559		},
2560		{
2561			name: "super+a",
2562			key:  Key{Code: 'a', Mod: ModSuper},
2563			want: "super+a",
2564		},
2565		{
2566			name: "ctrl+alt+shift+a",
2567			key:  Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift},
2568			want: "ctrl+alt+shift+a",
2569		},
2570		{
2571			name: "all modifiers",
2572			key:  Key{Code: 'a', Mod: ModCtrl | ModAlt | ModShift | ModMeta | ModHyper | ModSuper},
2573			want: "ctrl+alt+shift+meta+hyper+super+a",
2574		},
2575		{
2576			name: "space key",
2577			key:  Key{Code: KeySpace},
2578			want: "space",
2579		},
2580		{
2581			name: "extended key with text",
2582			key:  Key{Code: KeyExtended, Text: "hello"},
2583			want: "hello",
2584		},
2585		{
2586			name: "enter key",
2587			key:  Key{Code: KeyEnter},
2588			want: "enter",
2589		},
2590		{
2591			name: "tab key",
2592			key:  Key{Code: KeyTab},
2593			want: "tab",
2594		},
2595		{
2596			name: "escape key",
2597			key:  Key{Code: KeyEscape},
2598			want: "esc",
2599		},
2600		{
2601			name: "f1 key",
2602			key:  Key{Code: KeyF1},
2603			want: "f1",
2604		},
2605		{
2606			name: "backspace key",
2607			key:  Key{Code: KeyBackspace},
2608			want: "backspace",
2609		},
2610		{
2611			name: "left ctrl key alone",
2612			key:  Key{Code: KeyLeftCtrl, Mod: ModCtrl},
2613			want: "leftctrl",
2614		},
2615		{
2616			name: "right ctrl key alone",
2617			key:  Key{Code: KeyRightCtrl, Mod: ModCtrl},
2618			want: "rightctrl",
2619		},
2620		{
2621			name: "left alt key alone",
2622			key:  Key{Code: KeyLeftAlt, Mod: ModAlt},
2623			want: "leftalt",
2624		},
2625		{
2626			name: "right alt key alone",
2627			key:  Key{Code: KeyRightAlt, Mod: ModAlt},
2628			want: "rightalt",
2629		},
2630		{
2631			name: "left shift key alone",
2632			key:  Key{Code: KeyLeftShift, Mod: ModShift},
2633			want: "leftshift",
2634		},
2635		{
2636			name: "right shift key alone",
2637			key:  Key{Code: KeyRightShift, Mod: ModShift},
2638			want: "rightshift",
2639		},
2640		{
2641			name: "left meta key alone",
2642			key:  Key{Code: KeyLeftMeta, Mod: ModMeta},
2643			want: "leftmeta",
2644		},
2645		{
2646			name: "right meta key alone",
2647			key:  Key{Code: KeyRightMeta, Mod: ModMeta},
2648			want: "rightmeta",
2649		},
2650		{
2651			name: "left hyper key alone",
2652			key:  Key{Code: KeyLeftHyper, Mod: ModHyper},
2653			want: "lefthyper",
2654		},
2655		{
2656			name: "right hyper key alone",
2657			key:  Key{Code: KeyRightHyper, Mod: ModHyper},
2658			want: "righthyper",
2659		},
2660		{
2661			name: "left super key alone",
2662			key:  Key{Code: KeyLeftSuper, Mod: ModSuper},
2663			want: "leftsuper",
2664		},
2665		{
2666			name: "right super key alone",
2667			key:  Key{Code: KeyRightSuper, Mod: ModSuper},
2668			want: "rightsuper",
2669		},
2670		{
2671			name: "key with base code",
2672			key:  Key{Code: 'A', BaseCode: 'a'},
2673			want: "a",
2674		},
2675		{
2676			name: "unknown key with base code",
2677			key:  Key{Code: 99999, BaseCode: 'x'},
2678			want: "x",
2679		},
2680		{
2681			name: "printable rune",
2682			key:  Key{Code: '€'},
2683			want: "€",
2684		},
2685		{
2686			name: "unknown key without base code",
2687			key:  Key{Code: 99999},
2688			want: "𘚟",
2689		},
2690	}
2691
2692	for _, tt := range tests {
2693		t.Run(tt.name, func(t *testing.T) {
2694			got := tt.key.Keystroke()
2695			if got != tt.want {
2696				t.Errorf("Keystroke() = %q, want %q", got, tt.want)
2697			}
2698		})
2699	}
2700}
2701
2702// TestKeystrokeCoverage tests edge cases for Keystroke method
2703func TestKeystrokeCoverage(t *testing.T) {
2704	// Test a key that's not in keyTypeString and is KeySpace
2705	// This is actually impossible since KeySpace is in keyTypeString
2706	// But we can test a key that's not in keyTypeString and has BaseCode of KeySpace
2707	k := Key{Code: 999999, BaseCode: KeySpace}
2708	if got := k.Keystroke(); got != "space" {
2709		t.Errorf("Keystroke() = %q, want %q", got, "space")
2710	}
2711
2712	// Test a key that's not in keyTypeString, has no BaseCode, and is KeySpace
2713	// This would require KeySpace to not be in keyTypeString, which it is
2714	// So this branch might be unreachable
2715}
2716
2717func TestKeyStringMore(t *testing.T) {
2718	tests := []struct {
2719		name string
2720		key  Key
2721		want string
2722	}{
2723		{
2724			name: "space character",
2725			key:  Key{Code: KeySpace, Text: " "},
2726			want: "space",
2727		},
2728		{
2729			name: "empty text",
2730			key:  Key{Code: 'a', Text: ""},
2731			want: "a",
2732		},
2733		{
2734			name: "text with multiple characters",
2735			key:  Key{Code: KeyExtended, Text: "hello"},
2736			want: "hello",
2737		},
2738	}
2739
2740	for _, tt := range tests {
2741		t.Run(tt.name, func(t *testing.T) {
2742			got := tt.key.String()
2743			if got != tt.want {
2744				t.Errorf("String() = %q, want %q", got, tt.want)
2745			}
2746		})
2747	}
2748}