okami.mom/ultraviolet

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

Ayman Bagabasrefactor: simplify Key matching methodsbf44e60

main
16.2 KiB762 linesraw
1package uv
2
3import (
4	"strings"
5	"unicode"
6	"unicode/utf8"
7
8	"github.com/charmbracelet/x/ansi"
9)
10
11// KeyMod represents modifier keys.
12type KeyMod int
13
14// Modifier keys.
15const (
16	ModShift KeyMod = 1 << iota
17	ModAlt
18	ModCtrl
19	ModMeta
20
21	// These modifiers are used with the Kitty protocol.
22	// XXX: Meta and Super are swapped in the Kitty protocol,
23	// this is to preserve compatibility with XTerm modifiers.
24
25	ModHyper
26	ModSuper // Windows/Command keys
27
28	// These are key lock states.
29
30	ModCapsLock
31	ModNumLock
32	ModScrollLock // Defined in Windows API only
33)
34
35// Contains reports whether m contains the given modifiers.
36//
37// Example:
38//
39//	m := ModAlt | ModCtrl
40//	m.Contains(ModCtrl) // true
41//	m.Contains(ModAlt | ModCtrl) // true
42//	m.Contains(ModAlt | ModCtrl | ModShift) // false
43func (m KeyMod) Contains(mods KeyMod) bool {
44	return m&mods == mods
45}
46
47const (
48	// KeyExtended is a special key code used to signify that a key event
49	// contains multiple runes.
50	KeyExtended = unicode.MaxRune + 1
51)
52
53// Special key symbols.
54const (
55
56	// Special keys.
57
58	KeyUp rune = KeyExtended + iota + 1
59	KeyDown
60	KeyRight
61	KeyLeft
62	KeyBegin
63	KeyFind
64	KeyInsert
65	KeyDelete
66	KeySelect
67	KeyPgUp
68	KeyPgDown
69	KeyHome
70	KeyEnd
71
72	// Keypad keys.
73
74	KeyKpEnter
75	KeyKpEqual
76	KeyKpMultiply
77	KeyKpPlus
78	KeyKpComma
79	KeyKpMinus
80	KeyKpDecimal
81	KeyKpDivide
82	KeyKp0
83	KeyKp1
84	KeyKp2
85	KeyKp3
86	KeyKp4
87	KeyKp5
88	KeyKp6
89	KeyKp7
90	KeyKp8
91	KeyKp9
92
93	//nolint:godox
94	// The following are keys defined in the Kitty keyboard protocol.
95	// TODO: Investigate the names of these keys.
96
97	KeyKpSep
98	KeyKpUp
99	KeyKpDown
100	KeyKpLeft
101	KeyKpRight
102	KeyKpPgUp
103	KeyKpPgDown
104	KeyKpHome
105	KeyKpEnd
106	KeyKpInsert
107	KeyKpDelete
108	KeyKpBegin
109
110	// Function keys.
111
112	KeyF1
113	KeyF2
114	KeyF3
115	KeyF4
116	KeyF5
117	KeyF6
118	KeyF7
119	KeyF8
120	KeyF9
121	KeyF10
122	KeyF11
123	KeyF12
124	KeyF13
125	KeyF14
126	KeyF15
127	KeyF16
128	KeyF17
129	KeyF18
130	KeyF19
131	KeyF20
132	KeyF21
133	KeyF22
134	KeyF23
135	KeyF24
136	KeyF25
137	KeyF26
138	KeyF27
139	KeyF28
140	KeyF29
141	KeyF30
142	KeyF31
143	KeyF32
144	KeyF33
145	KeyF34
146	KeyF35
147	KeyF36
148	KeyF37
149	KeyF38
150	KeyF39
151	KeyF40
152	KeyF41
153	KeyF42
154	KeyF43
155	KeyF44
156	KeyF45
157	KeyF46
158	KeyF47
159	KeyF48
160	KeyF49
161	KeyF50
162	KeyF51
163	KeyF52
164	KeyF53
165	KeyF54
166	KeyF55
167	KeyF56
168	KeyF57
169	KeyF58
170	KeyF59
171	KeyF60
172	KeyF61
173	KeyF62
174	KeyF63
175
176	//nolint:godox
177	// The following are keys defined in the Kitty keyboard protocol.
178	// TODO: Investigate the names of these keys.
179
180	KeyCapsLock
181	KeyScrollLock
182	KeyNumLock
183	KeyPrintScreen
184	KeyPause
185	KeyMenu
186
187	KeyMediaPlay
188	KeyMediaPause
189	KeyMediaPlayPause
190	KeyMediaReverse
191	KeyMediaStop
192	KeyMediaFastForward
193	KeyMediaRewind
194	KeyMediaNext
195	KeyMediaPrev
196	KeyMediaRecord
197
198	KeyLowerVol
199	KeyRaiseVol
200	KeyMute
201
202	KeyLeftShift
203	KeyLeftAlt
204	KeyLeftCtrl
205	KeyLeftSuper
206	KeyLeftHyper
207	KeyLeftMeta
208	KeyRightShift
209	KeyRightAlt
210	KeyRightCtrl
211	KeyRightSuper
212	KeyRightHyper
213	KeyRightMeta
214	KeyIsoLevel3Shift
215	KeyIsoLevel5Shift
216
217	// Special names in C0.
218
219	KeyBackspace = rune(ansi.DEL)
220	KeyTab       = rune(ansi.HT)
221	KeyEnter     = rune(ansi.CR)
222	KeyReturn    = KeyEnter
223	KeyEscape    = rune(ansi.ESC)
224	KeyEsc       = KeyEscape
225
226	// Special names in G0.
227
228	KeySpace = rune(ansi.SP)
229)
230
231// Key represents a Key press or release event. It contains information about
232// the Key pressed, like the runes, the type of Key, and the modifiers pressed.
233// There are a couple general patterns you could use to check for key presses
234// or releases:
235//
236//	// Switch on the string representation of the key (shorter)
237//	switch ev := ev.(type) {
238//	case KeyPressEvent:
239//	    switch ev.String() {
240//	    case "enter":
241//	        fmt.Println("you pressed enter!")
242//	    case "a":
243//	        fmt.Println("you pressed a!")
244//	    }
245//	}
246//
247//	// Switch on the key type (more foolproof)
248//	switch ev := ev.(type) {
249//	case KeyEvent:
250//	    // catch both KeyPressEvent and KeyReleaseEvent
251//	    switch key := ev.Key(); key.Code {
252//	    case KeyEnter:
253//	        fmt.Println("you pressed enter!")
254//	    default:
255//	        switch key.Text {
256//	        case "a":
257//	            fmt.Println("you pressed a!")
258//	        }
259//	    }
260//	}
261//
262// Note that [Key.Text] will be empty for special keys like [KeyEnter],
263// [KeyTab], and for keys that don't represent printable characters like key
264// combos with modifier keys. In other words, [Key.Text] is populated only for
265// keys that represent printable characters shifted or unshifted (like 'a',
266// 'A', '1', '!', etc.).
267type Key struct {
268	// Text contains the actual characters received. This usually the same as
269	// [Key.Code]. When [Key.Text] is non-empty, it indicates that the key
270	// pressed represents printable character(s).
271	Text string
272
273	// Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on.
274	Mod KeyMod
275
276	// Code represents the key pressed. This is usually a special key like
277	// [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'.
278	Code rune
279
280	// ShiftedCode is the actual, shifted key pressed by the user. For example,
281	// if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will
282	// be 'A' and [Key.Code] will be 'a'.
283	//
284	// In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the
285	// unshifted key on the keyboard.
286	//
287	// This is only available with the Kitty Keyboard Protocol or the Windows
288	// Console API.
289	ShiftedCode rune
290
291	// BaseCode is the key pressed according to the standard PC-101 key layout.
292	// On international keyboards, this is the key that would be pressed if the
293	// keyboard was set to US PC-101 layout.
294	//
295	// For example, if the user presses 'q' on a French AZERTY keyboard,
296	// [Key.BaseCode] will be 'q'.
297	//
298	// This is only available with the Kitty Keyboard Protocol or the Windows
299	// Console API.
300	BaseCode rune
301
302	// IsRepeat indicates whether the key is being held down and sending events
303	// repeatedly.
304	//
305	// This is only available with the Kitty Keyboard Protocol or the Windows
306	// Console API.
307	IsRepeat bool
308}
309
310// MatchString returns true if the [Key] matches one of the given strings.
311//
312// A string can be a key name like "enter", "tab", "a", or a printable
313// character like "1" or " ". It can also have combinations of modifiers like
314// "ctrl+a", "shift+enter", "alt+tab", "ctrl+shift+enter", etc.
315func (k Key) MatchString(s ...string) bool {
316	for _, s := range s {
317		if keyMatchString(k, s) {
318			return true
319		}
320	}
321	return false
322}
323
324func keyMatchString(k Key, s string) bool {
325	var (
326		mod  KeyMod
327		code rune
328		text string
329	)
330	parts := strings.Split(s, "+")
331	for _, part := range parts {
332		switch part {
333		case "ctrl":
334			mod |= ModCtrl
335		case "alt":
336			mod |= ModAlt
337		case "shift":
338			mod |= ModShift
339		case "meta":
340			mod |= ModMeta
341		case "hyper":
342			mod |= ModHyper
343		case "super":
344			mod |= ModSuper
345		case "capslock":
346			mod |= ModCapsLock
347		case "scrolllock":
348			mod |= ModScrollLock
349		case "numlock":
350			mod |= ModNumLock
351		default:
352			// Check if the part is a key name.
353			if k, ok := stringKeyType[part]; ok {
354				code = k
355			} else {
356				// Check if the part is a printable character.
357				if utf8.RuneCountInString(part) == 1 {
358					code, _ = utf8.DecodeRuneInString(part)
359				} else {
360					// Multi-rune key.
361					code = KeyExtended
362					text = part
363				}
364			}
365		}
366	}
367
368	// Check if we have a printable character.
369	smod := mod &^ (ModShift | ModCapsLock)
370	if smod == 0 && text == "" && unicode.IsPrint(code) {
371		if mod&ModShift != 0 || mod&ModCapsLock != 0 {
372			// Shifted code we need to use uppercase.
373			text = string(unicode.ToUpper(code))
374		} else {
375			// Otherwise, use the code as is.
376			text = string(code)
377		}
378	}
379
380	// Check if we have a match.
381	return (k.Mod == mod && k.Code == code) ||
382		(k.Text != "" && k.Text == text)
383}
384
385// String implements [fmt.Stringer] and is quite useful for matching key
386// events. It will return the textual representation of the [Key] if there is
387// one, otherwise, it will fallback to [Key.Keystroke].
388//
389// For example, you'll always get "?" and instead of "shift+/" on a US ANSI
390// keyboard.
391func (k Key) String() string {
392	if len(k.Text) > 0 && k.Text != " " {
393		return k.Text
394	}
395	return k.Keystroke()
396}
397
398// Keystroke returns the keystroke representation of the [Key]. While less type
399// safe than looking at the individual fields, it will usually be more
400// convenient and readable to use this method when matching against keys.
401//
402// Note that modifier keys are always printed in the following order:
403//   - ctrl
404//   - alt
405//   - shift
406//   - meta
407//   - hyper
408//   - super
409//
410// For example, you'll always see "ctrl+shift+alt+a" and never
411// "shift+ctrl+alt+a".
412func (k Key) Keystroke() string {
413	var sb strings.Builder
414	if k.Mod.Contains(ModCtrl) && k.Code != KeyLeftCtrl && k.Code != KeyRightCtrl {
415		sb.WriteString("ctrl+")
416	}
417	if k.Mod.Contains(ModAlt) && k.Code != KeyLeftAlt && k.Code != KeyRightAlt {
418		sb.WriteString("alt+")
419	}
420	if k.Mod.Contains(ModShift) && k.Code != KeyLeftShift && k.Code != KeyRightShift {
421		sb.WriteString("shift+")
422	}
423	if k.Mod.Contains(ModMeta) && k.Code != KeyLeftMeta && k.Code != KeyRightMeta {
424		sb.WriteString("meta+")
425	}
426	if k.Mod.Contains(ModHyper) && k.Code != KeyLeftHyper && k.Code != KeyRightHyper {
427		sb.WriteString("hyper+")
428	}
429	if k.Mod.Contains(ModSuper) && k.Code != KeyLeftSuper && k.Code != KeyRightSuper {
430		sb.WriteString("super+")
431	}
432
433	if kt, ok := keyTypeString[k.Code]; ok {
434		sb.WriteString(kt)
435	} else {
436		code := k.Code
437		if k.BaseCode != 0 {
438			// If a [Key.BaseCode] is present, use it to represent a key using the standard
439			// PC-101 key layout.
440			code = k.BaseCode
441		}
442
443		switch code {
444		case KeySpace:
445			// Space is the only invisible printable character.
446			sb.WriteString("space")
447		case KeyExtended:
448			// Write the actual text of the key when the key contains multiple
449			// runes.
450			sb.WriteString(k.Text)
451		default:
452			sb.WriteRune(code)
453		}
454	}
455
456	return sb.String()
457}
458
459var keyTypeString = map[rune]string{
460	KeyEnter:      "enter",
461	KeyTab:        "tab",
462	KeyBackspace:  "backspace",
463	KeyEscape:     "esc",
464	KeySpace:      "space",
465	KeyUp:         "up",
466	KeyDown:       "down",
467	KeyLeft:       "left",
468	KeyRight:      "right",
469	KeyBegin:      "begin",
470	KeyFind:       "find",
471	KeyInsert:     "insert",
472	KeyDelete:     "delete",
473	KeySelect:     "select",
474	KeyPgUp:       "pgup",
475	KeyPgDown:     "pgdown",
476	KeyHome:       "home",
477	KeyEnd:        "end",
478	KeyKpEnter:    "enter",
479	KeyKpEqual:    "equal",
480	KeyKpMultiply: "mul",
481	KeyKpPlus:     "plus",
482	KeyKpComma:    "comma",
483	KeyKpMinus:    "minus",
484	KeyKpDecimal:  "period",
485	KeyKpDivide:   "div",
486	KeyKp0:        "0",
487	KeyKp1:        "1",
488	KeyKp2:        "2",
489	KeyKp3:        "3",
490	KeyKp4:        "4",
491	KeyKp5:        "5",
492	KeyKp6:        "6",
493	KeyKp7:        "7",
494	KeyKp8:        "8",
495	KeyKp9:        "9",
496
497	// Kitty keyboard extension
498	KeyKpSep:    "sep",
499	KeyKpUp:     "up",
500	KeyKpDown:   "down",
501	KeyKpLeft:   "left",
502	KeyKpRight:  "right",
503	KeyKpPgUp:   "pgup",
504	KeyKpPgDown: "pgdown",
505	KeyKpHome:   "home",
506	KeyKpEnd:    "end",
507	KeyKpInsert: "insert",
508	KeyKpDelete: "delete",
509	KeyKpBegin:  "begin",
510
511	KeyF1:  "f1",
512	KeyF2:  "f2",
513	KeyF3:  "f3",
514	KeyF4:  "f4",
515	KeyF5:  "f5",
516	KeyF6:  "f6",
517	KeyF7:  "f7",
518	KeyF8:  "f8",
519	KeyF9:  "f9",
520	KeyF10: "f10",
521	KeyF11: "f11",
522	KeyF12: "f12",
523	KeyF13: "f13",
524	KeyF14: "f14",
525	KeyF15: "f15",
526	KeyF16: "f16",
527	KeyF17: "f17",
528	KeyF18: "f18",
529	KeyF19: "f19",
530	KeyF20: "f20",
531	KeyF21: "f21",
532	KeyF22: "f22",
533	KeyF23: "f23",
534	KeyF24: "f24",
535	KeyF25: "f25",
536	KeyF26: "f26",
537	KeyF27: "f27",
538	KeyF28: "f28",
539	KeyF29: "f29",
540	KeyF30: "f30",
541	KeyF31: "f31",
542	KeyF32: "f32",
543	KeyF33: "f33",
544	KeyF34: "f34",
545	KeyF35: "f35",
546	KeyF36: "f36",
547	KeyF37: "f37",
548	KeyF38: "f38",
549	KeyF39: "f39",
550	KeyF40: "f40",
551	KeyF41: "f41",
552	KeyF42: "f42",
553	KeyF43: "f43",
554	KeyF44: "f44",
555	KeyF45: "f45",
556	KeyF46: "f46",
557	KeyF47: "f47",
558	KeyF48: "f48",
559	KeyF49: "f49",
560	KeyF50: "f50",
561	KeyF51: "f51",
562	KeyF52: "f52",
563	KeyF53: "f53",
564	KeyF54: "f54",
565	KeyF55: "f55",
566	KeyF56: "f56",
567	KeyF57: "f57",
568	KeyF58: "f58",
569	KeyF59: "f59",
570	KeyF60: "f60",
571	KeyF61: "f61",
572	KeyF62: "f62",
573	KeyF63: "f63",
574
575	// Kitty keyboard extension
576	KeyCapsLock:         "capslock",
577	KeyScrollLock:       "scrolllock",
578	KeyNumLock:          "numlock",
579	KeyPrintScreen:      "printscreen",
580	KeyPause:            "pause",
581	KeyMenu:             "menu",
582	KeyMediaPlay:        "mediaplay",
583	KeyMediaPause:       "mediapause",
584	KeyMediaPlayPause:   "mediaplaypause",
585	KeyMediaReverse:     "mediareverse",
586	KeyMediaStop:        "mediastop",
587	KeyMediaFastForward: "mediafastforward",
588	KeyMediaRewind:      "mediarewind",
589	KeyMediaNext:        "medianext",
590	KeyMediaPrev:        "mediaprev",
591	KeyMediaRecord:      "mediarecord",
592	KeyLowerVol:         "lowervol",
593	KeyRaiseVol:         "raisevol",
594	KeyMute:             "mute",
595	KeyLeftShift:        "leftshift",
596	KeyLeftAlt:          "leftalt",
597	KeyLeftCtrl:         "leftctrl",
598	KeyLeftSuper:        "leftsuper",
599	KeyLeftHyper:        "lefthyper",
600	KeyLeftMeta:         "leftmeta",
601	KeyRightShift:       "rightshift",
602	KeyRightAlt:         "rightalt",
603	KeyRightCtrl:        "rightctrl",
604	KeyRightSuper:       "rightsuper",
605	KeyRightHyper:       "righthyper",
606	KeyRightMeta:        "rightmeta",
607	KeyIsoLevel3Shift:   "isolevel3shift",
608	KeyIsoLevel5Shift:   "isolevel5shift",
609}
610
611var stringKeyType = map[string]rune{
612	"enter":     KeyEnter,
613	"tab":       KeyTab,
614	"backspace": KeyBackspace,
615	"escape":    KeyEscape,
616	"esc":       KeyEscape,
617	"space":     KeySpace,
618	"up":        KeyUp,
619	"down":      KeyDown,
620	"left":      KeyLeft,
621	"right":     KeyRight,
622	"begin":     KeyBegin,
623	"find":      KeyFind,
624	"insert":    KeyInsert,
625	"delete":    KeyDelete,
626	"select":    KeySelect,
627	"pgup":      KeyPgUp,
628	"pgdown":    KeyPgDown,
629	"home":      KeyHome,
630	"end":       KeyEnd,
631	"kpenter":   KeyKpEnter,
632	"kpequal":   KeyKpEqual,
633	"kpmul":     KeyKpMultiply,
634	"kpplus":    KeyKpPlus,
635	"kpcomma":   KeyKpComma,
636	"kpminus":   KeyKpMinus,
637	"kpperiod":  KeyKpDecimal,
638	"kpdiv":     KeyKpDivide,
639	"kp0":       KeyKp0,
640	"kp1":       KeyKp1,
641	"kp2":       KeyKp2,
642	"kp3":       KeyKp3,
643	"kp4":       KeyKp4,
644	"kp5":       KeyKp5,
645	"kp6":       KeyKp6,
646	"kp7":       KeyKp7,
647	"kp8":       KeyKp8,
648	"kp9":       KeyKp9,
649
650	// Kitty keyboard extension
651	"kpsep":    KeyKpSep,
652	"kpup":     KeyKpUp,
653	"kpdown":   KeyKpDown,
654	"kpleft":   KeyKpLeft,
655	"kpright":  KeyKpRight,
656	"kppgup":   KeyKpPgUp,
657	"kppgdown": KeyKpPgDown,
658	"kphome":   KeyKpHome,
659	"kpend":    KeyKpEnd,
660	"kpinsert": KeyKpInsert,
661	"kpdelete": KeyKpDelete,
662	"kpbegin":  KeyKpBegin,
663
664	"f1":  KeyF1,
665	"f2":  KeyF2,
666	"f3":  KeyF3,
667	"f4":  KeyF4,
668	"f5":  KeyF5,
669	"f6":  KeyF6,
670	"f7":  KeyF7,
671	"f8":  KeyF8,
672	"f9":  KeyF9,
673	"f10": KeyF10,
674	"f11": KeyF11,
675	"f12": KeyF12,
676	"f13": KeyF13,
677	"f14": KeyF14,
678	"f15": KeyF15,
679	"f16": KeyF16,
680	"f17": KeyF17,
681	"f18": KeyF18,
682	"f19": KeyF19,
683	"f20": KeyF20,
684	"f21": KeyF21,
685	"f22": KeyF22,
686	"f23": KeyF23,
687	"f24": KeyF24,
688	"f25": KeyF25,
689	"f26": KeyF26,
690	"f27": KeyF27,
691	"f28": KeyF28,
692	"f29": KeyF29,
693	"f30": KeyF30,
694	"f31": KeyF31,
695	"f32": KeyF32,
696	"f33": KeyF33,
697	"f34": KeyF34,
698	"f35": KeyF35,
699	"f36": KeyF36,
700	"f37": KeyF37,
701	"f38": KeyF38,
702	"f39": KeyF39,
703	"f40": KeyF40,
704	"f41": KeyF41,
705	"f42": KeyF42,
706	"f43": KeyF43,
707	"f44": KeyF44,
708	"f45": KeyF45,
709	"f46": KeyF46,
710	"f47": KeyF47,
711	"f48": KeyF48,
712	"f49": KeyF49,
713	"f50": KeyF50,
714	"f51": KeyF51,
715	"f52": KeyF52,
716	"f53": KeyF53,
717	"f54": KeyF54,
718	"f55": KeyF55,
719	"f56": KeyF56,
720	"f57": KeyF57,
721	"f58": KeyF58,
722	"f59": KeyF59,
723	"f60": KeyF60,
724	"f61": KeyF61,
725	"f62": KeyF62,
726	"f63": KeyF63,
727
728	// Kitty keyboard extension
729	"capslock":         KeyCapsLock,
730	"scrolllock":       KeyScrollLock,
731	"numlock":          KeyNumLock,
732	"printscreen":      KeyPrintScreen,
733	"pause":            KeyPause,
734	"menu":             KeyMenu,
735	"mediaplay":        KeyMediaPlay,
736	"mediapause":       KeyMediaPause,
737	"mediaplaypause":   KeyMediaPlayPause,
738	"mediareverse":     KeyMediaReverse,
739	"mediastop":        KeyMediaStop,
740	"mediafastforward": KeyMediaFastForward,
741	"mediarewind":      KeyMediaRewind,
742	"medianext":        KeyMediaNext,
743	"mediaprev":        KeyMediaPrev,
744	"mediarecord":      KeyMediaRecord,
745	"lowervol":         KeyLowerVol,
746	"raisevol":         KeyRaiseVol,
747	"mute":             KeyMute,
748	"leftshift":        KeyLeftShift,
749	"leftalt":          KeyLeftAlt,
750	"leftctrl":         KeyLeftCtrl,
751	"leftsuper":        KeyLeftSuper,
752	"lefthyper":        KeyLeftHyper,
753	"leftmeta":         KeyLeftMeta,
754	"rightshift":       KeyRightShift,
755	"rightalt":         KeyRightAlt,
756	"rightctrl":        KeyRightCtrl,
757	"rightsuper":       KeyRightSuper,
758	"righthyper":       KeyRightHyper,
759	"rightmeta":        KeyRightMeta,
760	"isolevel3shift":   KeyIsoLevel3Shift,
761	"isolevel5shift":   KeyIsoLevel5Shift,
762}