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", }