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 the given string. The string
311// can be a key name like "enter", "tab", "a", or a printable character like
312// "1" or " ". It can also have combinations of modifiers like "ctrl+a",
313// "shift+enter", "alt+tab", "ctrl+shift+enter", etc.
314func (k Key) MatchString(s string) bool {
315 var (
316 mod KeyMod
317 code rune
318 text string
319 )
320 parts := strings.Split(s, "+")
321 for _, part := range parts {
322 switch part {
323 case "ctrl":
324 mod |= ModCtrl
325 case "alt":
326 mod |= ModAlt
327 case "shift":
328 mod |= ModShift
329 case "meta":
330 mod |= ModMeta
331 case "hyper":
332 mod |= ModHyper
333 case "super":
334 mod |= ModSuper
335 case "capslock":
336 mod |= ModCapsLock
337 case "scrolllock":
338 mod |= ModScrollLock
339 case "numlock":
340 mod |= ModNumLock
341 default:
342 // Check if the part is a key name.
343 if k, ok := stringKeyType[part]; ok {
344 code = k
345 } else {
346 // Check if the part is a printable character.
347 if utf8.RuneCountInString(part) == 1 {
348 code, _ = utf8.DecodeRuneInString(part)
349 } else {
350 // Multi-rune key.
351 code = KeyExtended
352 text = part
353 }
354 }
355 }
356 }
357
358 // Check if we have a printable character.
359 smod := mod &^ (ModShift | ModCapsLock)
360 if smod == 0 && text == "" && unicode.IsPrint(code) {
361 if mod&ModShift != 0 || mod&ModCapsLock != 0 {
362 // Shifted code we need to use uppercase.
363 text = string(unicode.ToUpper(code))
364 } else {
365 // Otherwise, use the code as is.
366 text = string(code)
367 }
368 }
369
370 // Check if we have a match.
371 return (k.Mod == mod && k.Code == code) ||
372 (k.Text != "" && k.Text == text)
373}
374
375// MatchStrings returns true if the [Key] matches any of the given strings. The
376// strings can be key names like "enter", "tab", "a", or a printable character
377// like "1" or " ". It can also have combinations of modifiers like "ctrl+a",
378// "shift+enter", "alt+tab", "ctrl+shift+enter", etc.
379// See [Key.MatchString] for more details.
380func (k Key) MatchStrings(ss ...string) bool {
381 for _, s := range ss {
382 if k.MatchString(s) {
383 return true
384 }
385 }
386 return false
387}
388
389// String implements [fmt.Stringer] and is quite useful for matching key
390// events. It will return the textual representation of the [Key] if there is
391// one, otherwise, it will fallback to [Key.Keystroke].
392//
393// For example, you'll always get "?" and instead of "shift+/" on a US ANSI
394// keyboard.
395func (k Key) String() string {
396 if len(k.Text) > 0 && k.Text != " " {
397 return k.Text
398 }
399 return k.Keystroke()
400}
401
402// Keystroke returns the keystroke representation of the [Key]. While less type
403// safe than looking at the individual fields, it will usually be more
404// convenient and readable to use this method when matching against keys.
405//
406// Note that modifier keys are always printed in the following order:
407// - ctrl
408// - alt
409// - shift
410// - meta
411// - hyper
412// - super
413//
414// For example, you'll always see "ctrl+shift+alt+a" and never
415// "shift+ctrl+alt+a".
416func (k Key) Keystroke() string {
417 var sb strings.Builder
418 if k.Mod.Contains(ModCtrl) && k.Code != KeyLeftCtrl && k.Code != KeyRightCtrl {
419 sb.WriteString("ctrl+")
420 }
421 if k.Mod.Contains(ModAlt) && k.Code != KeyLeftAlt && k.Code != KeyRightAlt {
422 sb.WriteString("alt+")
423 }
424 if k.Mod.Contains(ModShift) && k.Code != KeyLeftShift && k.Code != KeyRightShift {
425 sb.WriteString("shift+")
426 }
427 if k.Mod.Contains(ModMeta) && k.Code != KeyLeftMeta && k.Code != KeyRightMeta {
428 sb.WriteString("meta+")
429 }
430 if k.Mod.Contains(ModHyper) && k.Code != KeyLeftHyper && k.Code != KeyRightHyper {
431 sb.WriteString("hyper+")
432 }
433 if k.Mod.Contains(ModSuper) && k.Code != KeyLeftSuper && k.Code != KeyRightSuper {
434 sb.WriteString("super+")
435 }
436
437 if kt, ok := keyTypeString[k.Code]; ok {
438 sb.WriteString(kt)
439 } else {
440 code := k.Code
441 if k.BaseCode != 0 {
442 // If a [Key.BaseCode] is present, use it to represent a key using the standard
443 // PC-101 key layout.
444 code = k.BaseCode
445 }
446
447 switch code {
448 case KeySpace:
449 // Space is the only invisible printable character.
450 sb.WriteString("space")
451 case KeyExtended:
452 // Write the actual text of the key when the key contains multiple
453 // runes.
454 sb.WriteString(k.Text)
455 default:
456 sb.WriteRune(code)
457 }
458 }
459
460 return sb.String()
461}
462
463var keyTypeString = map[rune]string{
464 KeyEnter: "enter",
465 KeyTab: "tab",
466 KeyBackspace: "backspace",
467 KeyEscape: "esc",
468 KeySpace: "space",
469 KeyUp: "up",
470 KeyDown: "down",
471 KeyLeft: "left",
472 KeyRight: "right",
473 KeyBegin: "begin",
474 KeyFind: "find",
475 KeyInsert: "insert",
476 KeyDelete: "delete",
477 KeySelect: "select",
478 KeyPgUp: "pgup",
479 KeyPgDown: "pgdown",
480 KeyHome: "home",
481 KeyEnd: "end",
482 KeyKpEnter: "kpenter",
483 KeyKpEqual: "kpequal",
484 KeyKpMultiply: "kpmul",
485 KeyKpPlus: "kpplus",
486 KeyKpComma: "kpcomma",
487 KeyKpMinus: "kpminus",
488 KeyKpDecimal: "kpperiod",
489 KeyKpDivide: "kpdiv",
490 KeyKp0: "kp0",
491 KeyKp1: "kp1",
492 KeyKp2: "kp2",
493 KeyKp3: "kp3",
494 KeyKp4: "kp4",
495 KeyKp5: "kp5",
496 KeyKp6: "kp6",
497 KeyKp7: "kp7",
498 KeyKp8: "kp8",
499 KeyKp9: "kp9",
500
501 // Kitty keyboard extension
502 KeyKpSep: "kpsep",
503 KeyKpUp: "kpup",
504 KeyKpDown: "kpdown",
505 KeyKpLeft: "kpleft",
506 KeyKpRight: "kpright",
507 KeyKpPgUp: "kppgup",
508 KeyKpPgDown: "kppgdown",
509 KeyKpHome: "kphome",
510 KeyKpEnd: "kpend",
511 KeyKpInsert: "kpinsert",
512 KeyKpDelete: "kpdelete",
513 KeyKpBegin: "kpbegin",
514
515 KeyF1: "f1",
516 KeyF2: "f2",
517 KeyF3: "f3",
518 KeyF4: "f4",
519 KeyF5: "f5",
520 KeyF6: "f6",
521 KeyF7: "f7",
522 KeyF8: "f8",
523 KeyF9: "f9",
524 KeyF10: "f10",
525 KeyF11: "f11",
526 KeyF12: "f12",
527 KeyF13: "f13",
528 KeyF14: "f14",
529 KeyF15: "f15",
530 KeyF16: "f16",
531 KeyF17: "f17",
532 KeyF18: "f18",
533 KeyF19: "f19",
534 KeyF20: "f20",
535 KeyF21: "f21",
536 KeyF22: "f22",
537 KeyF23: "f23",
538 KeyF24: "f24",
539 KeyF25: "f25",
540 KeyF26: "f26",
541 KeyF27: "f27",
542 KeyF28: "f28",
543 KeyF29: "f29",
544 KeyF30: "f30",
545 KeyF31: "f31",
546 KeyF32: "f32",
547 KeyF33: "f33",
548 KeyF34: "f34",
549 KeyF35: "f35",
550 KeyF36: "f36",
551 KeyF37: "f37",
552 KeyF38: "f38",
553 KeyF39: "f39",
554 KeyF40: "f40",
555 KeyF41: "f41",
556 KeyF42: "f42",
557 KeyF43: "f43",
558 KeyF44: "f44",
559 KeyF45: "f45",
560 KeyF46: "f46",
561 KeyF47: "f47",
562 KeyF48: "f48",
563 KeyF49: "f49",
564 KeyF50: "f50",
565 KeyF51: "f51",
566 KeyF52: "f52",
567 KeyF53: "f53",
568 KeyF54: "f54",
569 KeyF55: "f55",
570 KeyF56: "f56",
571 KeyF57: "f57",
572 KeyF58: "f58",
573 KeyF59: "f59",
574 KeyF60: "f60",
575 KeyF61: "f61",
576 KeyF62: "f62",
577 KeyF63: "f63",
578
579 // Kitty keyboard extension
580 KeyCapsLock: "capslock",
581 KeyScrollLock: "scrolllock",
582 KeyNumLock: "numlock",
583 KeyPrintScreen: "printscreen",
584 KeyPause: "pause",
585 KeyMenu: "menu",
586 KeyMediaPlay: "mediaplay",
587 KeyMediaPause: "mediapause",
588 KeyMediaPlayPause: "mediaplaypause",
589 KeyMediaReverse: "mediareverse",
590 KeyMediaStop: "mediastop",
591 KeyMediaFastForward: "mediafastforward",
592 KeyMediaRewind: "mediarewind",
593 KeyMediaNext: "medianext",
594 KeyMediaPrev: "mediaprev",
595 KeyMediaRecord: "mediarecord",
596 KeyLowerVol: "lowervol",
597 KeyRaiseVol: "raisevol",
598 KeyMute: "mute",
599 KeyLeftShift: "leftshift",
600 KeyLeftAlt: "leftalt",
601 KeyLeftCtrl: "leftctrl",
602 KeyLeftSuper: "leftsuper",
603 KeyLeftHyper: "lefthyper",
604 KeyLeftMeta: "leftmeta",
605 KeyRightShift: "rightshift",
606 KeyRightAlt: "rightalt",
607 KeyRightCtrl: "rightctrl",
608 KeyRightSuper: "rightsuper",
609 KeyRightHyper: "righthyper",
610 KeyRightMeta: "rightmeta",
611 KeyIsoLevel3Shift: "isolevel3shift",
612 KeyIsoLevel5Shift: "isolevel5shift",
613}
614
615var stringKeyType = map[string]rune{
616 "enter": KeyEnter,
617 "tab": KeyTab,
618 "backspace": KeyBackspace,
619 "escape": KeyEscape,
620 "esc": KeyEscape,
621 "space": KeySpace,
622 "up": KeyUp,
623 "down": KeyDown,
624 "left": KeyLeft,
625 "right": KeyRight,
626 "begin": KeyBegin,
627 "find": KeyFind,
628 "insert": KeyInsert,
629 "delete": KeyDelete,
630 "select": KeySelect,
631 "pgup": KeyPgUp,
632 "pgdown": KeyPgDown,
633 "home": KeyHome,
634 "end": KeyEnd,
635 "kpenter": KeyKpEnter,
636 "kpequal": KeyKpEqual,
637 "kpmul": KeyKpMultiply,
638 "kpplus": KeyKpPlus,
639 "kpcomma": KeyKpComma,
640 "kpminus": KeyKpMinus,
641 "kpperiod": KeyKpDecimal,
642 "kpdiv": KeyKpDivide,
643 "kp0": KeyKp0,
644 "kp1": KeyKp1,
645 "kp2": KeyKp2,
646 "kp3": KeyKp3,
647 "kp4": KeyKp4,
648 "kp5": KeyKp5,
649 "kp6": KeyKp6,
650 "kp7": KeyKp7,
651 "kp8": KeyKp8,
652 "kp9": KeyKp9,
653
654 // Kitty keyboard extension
655 "kpsep": KeyKpSep,
656 "kpup": KeyKpUp,
657 "kpdown": KeyKpDown,
658 "kpleft": KeyKpLeft,
659 "kpright": KeyKpRight,
660 "kppgup": KeyKpPgUp,
661 "kppgdown": KeyKpPgDown,
662 "kphome": KeyKpHome,
663 "kpend": KeyKpEnd,
664 "kpinsert": KeyKpInsert,
665 "kpdelete": KeyKpDelete,
666 "kpbegin": KeyKpBegin,
667
668 "f1": KeyF1,
669 "f2": KeyF2,
670 "f3": KeyF3,
671 "f4": KeyF4,
672 "f5": KeyF5,
673 "f6": KeyF6,
674 "f7": KeyF7,
675 "f8": KeyF8,
676 "f9": KeyF9,
677 "f10": KeyF10,
678 "f11": KeyF11,
679 "f12": KeyF12,
680 "f13": KeyF13,
681 "f14": KeyF14,
682 "f15": KeyF15,
683 "f16": KeyF16,
684 "f17": KeyF17,
685 "f18": KeyF18,
686 "f19": KeyF19,
687 "f20": KeyF20,
688 "f21": KeyF21,
689 "f22": KeyF22,
690 "f23": KeyF23,
691 "f24": KeyF24,
692 "f25": KeyF25,
693 "f26": KeyF26,
694 "f27": KeyF27,
695 "f28": KeyF28,
696 "f29": KeyF29,
697 "f30": KeyF30,
698 "f31": KeyF31,
699 "f32": KeyF32,
700 "f33": KeyF33,
701 "f34": KeyF34,
702 "f35": KeyF35,
703 "f36": KeyF36,
704 "f37": KeyF37,
705 "f38": KeyF38,
706 "f39": KeyF39,
707 "f40": KeyF40,
708 "f41": KeyF41,
709 "f42": KeyF42,
710 "f43": KeyF43,
711 "f44": KeyF44,
712 "f45": KeyF45,
713 "f46": KeyF46,
714 "f47": KeyF47,
715 "f48": KeyF48,
716 "f49": KeyF49,
717 "f50": KeyF50,
718 "f51": KeyF51,
719 "f52": KeyF52,
720 "f53": KeyF53,
721 "f54": KeyF54,
722 "f55": KeyF55,
723 "f56": KeyF56,
724 "f57": KeyF57,
725 "f58": KeyF58,
726 "f59": KeyF59,
727 "f60": KeyF60,
728 "f61": KeyF61,
729 "f62": KeyF62,
730 "f63": KeyF63,
731
732 // Kitty keyboard extension
733 "capslock": KeyCapsLock,
734 "scrolllock": KeyScrollLock,
735 "numlock": KeyNumLock,
736 "printscreen": KeyPrintScreen,
737 "pause": KeyPause,
738 "menu": KeyMenu,
739 "mediaplay": KeyMediaPlay,
740 "mediapause": KeyMediaPause,
741 "mediaplaypause": KeyMediaPlayPause,
742 "mediareverse": KeyMediaReverse,
743 "mediastop": KeyMediaStop,
744 "mediafastforward": KeyMediaFastForward,
745 "mediarewind": KeyMediaRewind,
746 "medianext": KeyMediaNext,
747 "mediaprev": KeyMediaPrev,
748 "mediarecord": KeyMediaRecord,
749 "lowervol": KeyLowerVol,
750 "raisevol": KeyRaiseVol,
751 "mute": KeyMute,
752 "leftshift": KeyLeftShift,
753 "leftalt": KeyLeftAlt,
754 "leftctrl": KeyLeftCtrl,
755 "leftsuper": KeyLeftSuper,
756 "lefthyper": KeyLeftHyper,
757 "leftmeta": KeyLeftMeta,
758 "rightshift": KeyRightShift,
759 "rightalt": KeyRightAlt,
760 "rightctrl": KeyRightCtrl,
761 "rightsuper": KeyRightSuper,
762 "righthyper": KeyRightHyper,
763 "rightmeta": KeyRightMeta,
764 "isolevel3shift": KeyIsoLevel3Shift,
765 "isolevel5shift": KeyIsoLevel5Shift,
766}