summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/input/key.go
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tui/input/key.go')
-rw-r--r--packages/tui/input/key.go574
1 files changed, 574 insertions, 0 deletions
diff --git a/packages/tui/input/key.go b/packages/tui/input/key.go
new file mode 100644
index 000000000..8d3e3ebe3
--- /dev/null
+++ b/packages/tui/input/key.go
@@ -0,0 +1,574 @@
+package input
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+const (
+ // KeyExtended is a special key code used to signify that a key event
+ // contains multiple runes.
+ KeyExtended = unicode.MaxRune + 1
+)
+
+// Special key symbols.
+const (
+
+ // Special keys.
+
+ KeyUp rune = KeyExtended + iota + 1
+ KeyDown
+ KeyRight
+ KeyLeft
+ KeyBegin
+ KeyFind
+ KeyInsert
+ KeyDelete
+ KeySelect
+ KeyPgUp
+ KeyPgDown
+ KeyHome
+ KeyEnd
+
+ // Keypad keys.
+
+ KeyKpEnter
+ KeyKpEqual
+ KeyKpMultiply
+ KeyKpPlus
+ KeyKpComma
+ KeyKpMinus
+ KeyKpDecimal
+ KeyKpDivide
+ KeyKp0
+ KeyKp1
+ KeyKp2
+ KeyKp3
+ KeyKp4
+ KeyKp5
+ KeyKp6
+ KeyKp7
+ KeyKp8
+ KeyKp9
+
+ //nolint:godox
+ // The following are keys defined in the Kitty keyboard protocol.
+ // TODO: Investigate the names of these keys.
+
+ KeyKpSep
+ KeyKpUp
+ KeyKpDown
+ KeyKpLeft
+ KeyKpRight
+ KeyKpPgUp
+ KeyKpPgDown
+ KeyKpHome
+ KeyKpEnd
+ KeyKpInsert
+ KeyKpDelete
+ KeyKpBegin
+
+ // Function keys.
+
+ KeyF1
+ KeyF2
+ KeyF3
+ KeyF4
+ KeyF5
+ KeyF6
+ KeyF7
+ KeyF8
+ KeyF9
+ KeyF10
+ KeyF11
+ KeyF12
+ KeyF13
+ KeyF14
+ KeyF15
+ KeyF16
+ KeyF17
+ KeyF18
+ KeyF19
+ KeyF20
+ KeyF21
+ KeyF22
+ KeyF23
+ KeyF24
+ KeyF25
+ KeyF26
+ KeyF27
+ KeyF28
+ KeyF29
+ KeyF30
+ KeyF31
+ KeyF32
+ KeyF33
+ KeyF34
+ KeyF35
+ KeyF36
+ KeyF37
+ KeyF38
+ KeyF39
+ KeyF40
+ KeyF41
+ KeyF42
+ KeyF43
+ KeyF44
+ KeyF45
+ KeyF46
+ KeyF47
+ KeyF48
+ KeyF49
+ KeyF50
+ KeyF51
+ KeyF52
+ KeyF53
+ KeyF54
+ KeyF55
+ KeyF56
+ KeyF57
+ KeyF58
+ KeyF59
+ KeyF60
+ KeyF61
+ KeyF62
+ KeyF63
+
+ //nolint:godox
+ // The following are keys defined in the Kitty keyboard protocol.
+ // TODO: Investigate the names of these keys.
+
+ KeyCapsLock
+ KeyScrollLock
+ KeyNumLock
+ KeyPrintScreen
+ KeyPause
+ KeyMenu
+
+ KeyMediaPlay
+ KeyMediaPause
+ KeyMediaPlayPause
+ KeyMediaReverse
+ KeyMediaStop
+ KeyMediaFastForward
+ KeyMediaRewind
+ KeyMediaNext
+ KeyMediaPrev
+ KeyMediaRecord
+
+ KeyLowerVol
+ KeyRaiseVol
+ KeyMute
+
+ KeyLeftShift
+ KeyLeftAlt
+ KeyLeftCtrl
+ KeyLeftSuper
+ KeyLeftHyper
+ KeyLeftMeta
+ KeyRightShift
+ KeyRightAlt
+ KeyRightCtrl
+ KeyRightSuper
+ KeyRightHyper
+ KeyRightMeta
+ KeyIsoLevel3Shift
+ KeyIsoLevel5Shift
+
+ // Special names in C0.
+
+ KeyBackspace = rune(ansi.DEL)
+ KeyTab = rune(ansi.HT)
+ KeyEnter = rune(ansi.CR)
+ KeyReturn = KeyEnter
+ KeyEscape = rune(ansi.ESC)
+ KeyEsc = KeyEscape
+
+ // Special names in G0.
+
+ KeySpace = rune(ansi.SP)
+)
+
+// KeyPressEvent represents a key press event.
+type KeyPressEvent Key
+
+// String implements [fmt.Stringer] and is quite useful for matching key
+// events. For details, on what this returns see [Key.String].
+func (k KeyPressEvent) String() string {
+ return Key(k).String()
+}
+
+// Keystroke returns the keystroke representation of the [Key]. While less type
+// safe than looking at the individual fields, it will usually be more
+// convenient and readable to use this method when matching against keys.
+//
+// Note that modifier keys are always printed in the following order:
+// - ctrl
+// - alt
+// - shift
+// - meta
+// - hyper
+// - super
+//
+// For example, you'll always see "ctrl+shift+alt+a" and never
+// "shift+ctrl+alt+a".
+func (k KeyPressEvent) Keystroke() string {
+ return Key(k).Keystroke()
+}
+
+// Key returns the underlying key event. This is a syntactic sugar for casting
+// the key event to a [Key].
+func (k KeyPressEvent) Key() Key {
+ return Key(k)
+}
+
+// KeyReleaseEvent represents a key release event.
+type KeyReleaseEvent Key
+
+// String implements [fmt.Stringer] and is quite useful for matching key
+// events. For details, on what this returns see [Key.String].
+func (k KeyReleaseEvent) String() string {
+ return Key(k).String()
+}
+
+// Keystroke returns the keystroke representation of the [Key]. While less type
+// safe than looking at the individual fields, it will usually be more
+// convenient and readable to use this method when matching against keys.
+//
+// Note that modifier keys are always printed in the following order:
+// - ctrl
+// - alt
+// - shift
+// - meta
+// - hyper
+// - super
+//
+// For example, you'll always see "ctrl+shift+alt+a" and never
+// "shift+ctrl+alt+a".
+func (k KeyReleaseEvent) Keystroke() string {
+ return Key(k).Keystroke()
+}
+
+// Key returns the underlying key event. This is a convenience method and
+// syntactic sugar to satisfy the [KeyEvent] interface, and cast the key event to
+// [Key].
+func (k KeyReleaseEvent) Key() Key {
+ return Key(k)
+}
+
+// KeyEvent represents a key event. This can be either a key press or a key
+// release event.
+type KeyEvent interface {
+ fmt.Stringer
+
+ // Key returns the underlying key event.
+ Key() Key
+}
+
+// Key represents a Key press or release event. It contains information about
+// the Key pressed, like the runes, the type of Key, and the modifiers pressed.
+// There are a couple general patterns you could use to check for key presses
+// or releases:
+//
+// // Switch on the string representation of the key (shorter)
+// switch ev := ev.(type) {
+// case KeyPressEvent:
+// switch ev.String() {
+// case "enter":
+// fmt.Println("you pressed enter!")
+// case "a":
+// fmt.Println("you pressed a!")
+// }
+// }
+//
+// // Switch on the key type (more foolproof)
+// switch ev := ev.(type) {
+// case KeyEvent:
+// // catch both KeyPressEvent and KeyReleaseEvent
+// switch key := ev.Key(); key.Code {
+// case KeyEnter:
+// fmt.Println("you pressed enter!")
+// default:
+// switch key.Text {
+// case "a":
+// fmt.Println("you pressed a!")
+// }
+// }
+// }
+//
+// Note that [Key.Text] will be empty for special keys like [KeyEnter],
+// [KeyTab], and for keys that don't represent printable characters like key
+// combos with modifier keys. In other words, [Key.Text] is populated only for
+// keys that represent printable characters shifted or unshifted (like 'a',
+// 'A', '1', '!', etc.).
+type Key struct {
+ // Text contains the actual characters received. This usually the same as
+ // [Key.Code]. When [Key.Text] is non-empty, it indicates that the key
+ // pressed represents printable character(s).
+ Text string
+
+ // Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on.
+ Mod KeyMod
+
+ // Code represents the key pressed. This is usually a special key like
+ // [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'.
+ Code rune
+
+ // ShiftedCode is the actual, shifted key pressed by the user. For example,
+ // if the user presses shift+a, or caps lock is on, [Key.ShiftedCode] will
+ // be 'A' and [Key.Code] will be 'a'.
+ //
+ // In the case of non-latin keyboards, like Arabic, [Key.ShiftedCode] is the
+ // unshifted key on the keyboard.
+ //
+ // This is only available with the Kitty Keyboard Protocol or the Windows
+ // Console API.
+ ShiftedCode rune
+
+ // BaseCode is the key pressed according to the standard PC-101 key layout.
+ // On international keyboards, this is the key that would be pressed if the
+ // keyboard was set to US PC-101 layout.
+ //
+ // For example, if the user presses 'q' on a French AZERTY keyboard,
+ // [Key.BaseCode] will be 'q'.
+ //
+ // This is only available with the Kitty Keyboard Protocol or the Windows
+ // Console API.
+ BaseCode rune
+
+ // IsRepeat indicates whether the key is being held down and sending events
+ // repeatedly.
+ //
+ // This is only available with the Kitty Keyboard Protocol or the Windows
+ // Console API.
+ IsRepeat bool
+}
+
+// String implements [fmt.Stringer] and is quite useful for matching key
+// events. It will return the textual representation of the [Key] if there is
+// one, otherwise, it will fallback to [Key.Keystroke].
+//
+// For example, you'll always get "?" and instead of "shift+/" on a US ANSI
+// keyboard.
+func (k Key) String() string {
+ if len(k.Text) > 0 && k.Text != " " {
+ return k.Text
+ }
+ return k.Keystroke()
+}
+
+// Keystroke returns the keystroke representation of the [Key]. While less type
+// safe than looking at the individual fields, it will usually be more
+// convenient and readable to use this method when matching against keys.
+//
+// Note that modifier keys are always printed in the following order:
+// - ctrl
+// - alt
+// - shift
+// - meta
+// - hyper
+// - super
+//
+// For example, you'll always see "ctrl+shift+alt+a" and never
+// "shift+ctrl+alt+a".
+func (k Key) Keystroke() string {
+ var sb strings.Builder
+ if k.Mod.Contains(ModCtrl) && k.Code != KeyLeftCtrl && k.Code != KeyRightCtrl {
+ sb.WriteString("ctrl+")
+ }
+ if k.Mod.Contains(ModAlt) && k.Code != KeyLeftAlt && k.Code != KeyRightAlt {
+ sb.WriteString("alt+")
+ }
+ if k.Mod.Contains(ModShift) && k.Code != KeyLeftShift && k.Code != KeyRightShift {
+ sb.WriteString("shift+")
+ }
+ if k.Mod.Contains(ModMeta) && k.Code != KeyLeftMeta && k.Code != KeyRightMeta {
+ sb.WriteString("meta+")
+ }
+ if k.Mod.Contains(ModHyper) && k.Code != KeyLeftHyper && k.Code != KeyRightHyper {
+ sb.WriteString("hyper+")
+ }
+ if k.Mod.Contains(ModSuper) && k.Code != KeyLeftSuper && k.Code != KeyRightSuper {
+ sb.WriteString("super+")
+ }
+
+ if kt, ok := keyTypeString[k.Code]; ok {
+ sb.WriteString(kt)
+ } else {
+ code := k.Code
+ if k.BaseCode != 0 {
+ // If a [Key.BaseCode] is present, use it to represent a key using the standard
+ // PC-101 key layout.
+ code = k.BaseCode
+ }
+
+ switch code {
+ case KeySpace:
+ // Space is the only invisible printable character.
+ sb.WriteString("space")
+ case KeyExtended:
+ // Write the actual text of the key when the key contains multiple
+ // runes.
+ sb.WriteString(k.Text)
+ default:
+ sb.WriteRune(code)
+ }
+ }
+
+ return sb.String()
+}
+
+var keyTypeString = map[rune]string{
+ KeyEnter: "enter",
+ KeyTab: "tab",
+ KeyBackspace: "backspace",
+ KeyEscape: "esc",
+ KeySpace: "space",
+ KeyUp: "up",
+ KeyDown: "down",
+ KeyLeft: "left",
+ KeyRight: "right",
+ KeyBegin: "begin",
+ KeyFind: "find",
+ KeyInsert: "insert",
+ KeyDelete: "delete",
+ KeySelect: "select",
+ KeyPgUp: "pgup",
+ KeyPgDown: "pgdown",
+ KeyHome: "home",
+ KeyEnd: "end",
+ KeyKpEnter: "kpenter",
+ KeyKpEqual: "kpequal",
+ KeyKpMultiply: "kpmul",
+ KeyKpPlus: "kpplus",
+ KeyKpComma: "kpcomma",
+ KeyKpMinus: "kpminus",
+ KeyKpDecimal: "kpperiod",
+ KeyKpDivide: "kpdiv",
+ KeyKp0: "kp0",
+ KeyKp1: "kp1",
+ KeyKp2: "kp2",
+ KeyKp3: "kp3",
+ KeyKp4: "kp4",
+ KeyKp5: "kp5",
+ KeyKp6: "kp6",
+ KeyKp7: "kp7",
+ KeyKp8: "kp8",
+ KeyKp9: "kp9",
+
+ // Kitty keyboard extension
+ KeyKpSep: "kpsep",
+ KeyKpUp: "kpup",
+ KeyKpDown: "kpdown",
+ KeyKpLeft: "kpleft",
+ KeyKpRight: "kpright",
+ KeyKpPgUp: "kppgup",
+ KeyKpPgDown: "kppgdown",
+ KeyKpHome: "kphome",
+ KeyKpEnd: "kpend",
+ KeyKpInsert: "kpinsert",
+ KeyKpDelete: "kpdelete",
+ KeyKpBegin: "kpbegin",
+
+ KeyF1: "f1",
+ KeyF2: "f2",
+ KeyF3: "f3",
+ KeyF4: "f4",
+ KeyF5: "f5",
+ KeyF6: "f6",
+ KeyF7: "f7",
+ KeyF8: "f8",
+ KeyF9: "f9",
+ KeyF10: "f10",
+ KeyF11: "f11",
+ KeyF12: "f12",
+ KeyF13: "f13",
+ KeyF14: "f14",
+ KeyF15: "f15",
+ KeyF16: "f16",
+ KeyF17: "f17",
+ KeyF18: "f18",
+ KeyF19: "f19",
+ KeyF20: "f20",
+ KeyF21: "f21",
+ KeyF22: "f22",
+ KeyF23: "f23",
+ KeyF24: "f24",
+ KeyF25: "f25",
+ KeyF26: "f26",
+ KeyF27: "f27",
+ KeyF28: "f28",
+ KeyF29: "f29",
+ KeyF30: "f30",
+ KeyF31: "f31",
+ KeyF32: "f32",
+ KeyF33: "f33",
+ KeyF34: "f34",
+ KeyF35: "f35",
+ KeyF36: "f36",
+ KeyF37: "f37",
+ KeyF38: "f38",
+ KeyF39: "f39",
+ KeyF40: "f40",
+ KeyF41: "f41",
+ KeyF42: "f42",
+ KeyF43: "f43",
+ KeyF44: "f44",
+ KeyF45: "f45",
+ KeyF46: "f46",
+ KeyF47: "f47",
+ KeyF48: "f48",
+ KeyF49: "f49",
+ KeyF50: "f50",
+ KeyF51: "f51",
+ KeyF52: "f52",
+ KeyF53: "f53",
+ KeyF54: "f54",
+ KeyF55: "f55",
+ KeyF56: "f56",
+ KeyF57: "f57",
+ KeyF58: "f58",
+ KeyF59: "f59",
+ KeyF60: "f60",
+ KeyF61: "f61",
+ KeyF62: "f62",
+ KeyF63: "f63",
+
+ // Kitty keyboard extension
+ KeyCapsLock: "capslock",
+ KeyScrollLock: "scrolllock",
+ KeyNumLock: "numlock",
+ KeyPrintScreen: "printscreen",
+ KeyPause: "pause",
+ KeyMenu: "menu",
+ KeyMediaPlay: "mediaplay",
+ KeyMediaPause: "mediapause",
+ KeyMediaPlayPause: "mediaplaypause",
+ KeyMediaReverse: "mediareverse",
+ KeyMediaStop: "mediastop",
+ KeyMediaFastForward: "mediafastforward",
+ KeyMediaRewind: "mediarewind",
+ KeyMediaNext: "medianext",
+ KeyMediaPrev: "mediaprev",
+ KeyMediaRecord: "mediarecord",
+ KeyLowerVol: "lowervol",
+ KeyRaiseVol: "raisevol",
+ KeyMute: "mute",
+ KeyLeftShift: "leftshift",
+ KeyLeftAlt: "leftalt",
+ KeyLeftCtrl: "leftctrl",
+ KeyLeftSuper: "leftsuper",
+ KeyLeftHyper: "lefthyper",
+ KeyLeftMeta: "leftmeta",
+ KeyRightShift: "rightshift",
+ KeyRightAlt: "rightalt",
+ KeyRightCtrl: "rightctrl",
+ KeyRightSuper: "rightsuper",
+ KeyRightHyper: "righthyper",
+ KeyRightMeta: "rightmeta",
+ KeyIsoLevel3Shift: "isolevel3shift",
+ KeyIsoLevel5Shift: "isolevel5shift",
+}