| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- package input
- import (
- "unicode"
- "unicode/utf8"
- "github.com/charmbracelet/x/ansi"
- "github.com/charmbracelet/x/ansi/kitty"
- )
- // KittyGraphicsEvent represents a Kitty Graphics response event.
- //
- // See https://sw.kovidgoyal.net/kitty/graphics-protocol/
- type KittyGraphicsEvent struct {
- Options kitty.Options
- Payload []byte
- }
- // KittyEnhancementsEvent represents a Kitty enhancements event.
- type KittyEnhancementsEvent int
- // Kitty keyboard enhancement constants.
- // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
- const (
- KittyDisambiguateEscapeCodes KittyEnhancementsEvent = 1 << iota
- KittyReportEventTypes
- KittyReportAlternateKeys
- KittyReportAllKeysAsEscapeCodes
- KittyReportAssociatedText
- )
- // Contains reports whether m contains the given enhancements.
- func (e KittyEnhancementsEvent) Contains(enhancements KittyEnhancementsEvent) bool {
- return e&enhancements == enhancements
- }
- // Kitty Clipboard Control Sequences.
- var kittyKeyMap = map[int]Key{
- ansi.BS: {Code: KeyBackspace},
- ansi.HT: {Code: KeyTab},
- ansi.CR: {Code: KeyEnter},
- ansi.ESC: {Code: KeyEscape},
- ansi.DEL: {Code: KeyBackspace},
- 57344: {Code: KeyEscape},
- 57345: {Code: KeyEnter},
- 57346: {Code: KeyTab},
- 57347: {Code: KeyBackspace},
- 57348: {Code: KeyInsert},
- 57349: {Code: KeyDelete},
- 57350: {Code: KeyLeft},
- 57351: {Code: KeyRight},
- 57352: {Code: KeyUp},
- 57353: {Code: KeyDown},
- 57354: {Code: KeyPgUp},
- 57355: {Code: KeyPgDown},
- 57356: {Code: KeyHome},
- 57357: {Code: KeyEnd},
- 57358: {Code: KeyCapsLock},
- 57359: {Code: KeyScrollLock},
- 57360: {Code: KeyNumLock},
- 57361: {Code: KeyPrintScreen},
- 57362: {Code: KeyPause},
- 57363: {Code: KeyMenu},
- 57364: {Code: KeyF1},
- 57365: {Code: KeyF2},
- 57366: {Code: KeyF3},
- 57367: {Code: KeyF4},
- 57368: {Code: KeyF5},
- 57369: {Code: KeyF6},
- 57370: {Code: KeyF7},
- 57371: {Code: KeyF8},
- 57372: {Code: KeyF9},
- 57373: {Code: KeyF10},
- 57374: {Code: KeyF11},
- 57375: {Code: KeyF12},
- 57376: {Code: KeyF13},
- 57377: {Code: KeyF14},
- 57378: {Code: KeyF15},
- 57379: {Code: KeyF16},
- 57380: {Code: KeyF17},
- 57381: {Code: KeyF18},
- 57382: {Code: KeyF19},
- 57383: {Code: KeyF20},
- 57384: {Code: KeyF21},
- 57385: {Code: KeyF22},
- 57386: {Code: KeyF23},
- 57387: {Code: KeyF24},
- 57388: {Code: KeyF25},
- 57389: {Code: KeyF26},
- 57390: {Code: KeyF27},
- 57391: {Code: KeyF28},
- 57392: {Code: KeyF29},
- 57393: {Code: KeyF30},
- 57394: {Code: KeyF31},
- 57395: {Code: KeyF32},
- 57396: {Code: KeyF33},
- 57397: {Code: KeyF34},
- 57398: {Code: KeyF35},
- 57399: {Code: KeyKp0},
- 57400: {Code: KeyKp1},
- 57401: {Code: KeyKp2},
- 57402: {Code: KeyKp3},
- 57403: {Code: KeyKp4},
- 57404: {Code: KeyKp5},
- 57405: {Code: KeyKp6},
- 57406: {Code: KeyKp7},
- 57407: {Code: KeyKp8},
- 57408: {Code: KeyKp9},
- 57409: {Code: KeyKpDecimal},
- 57410: {Code: KeyKpDivide},
- 57411: {Code: KeyKpMultiply},
- 57412: {Code: KeyKpMinus},
- 57413: {Code: KeyKpPlus},
- 57414: {Code: KeyKpEnter},
- 57415: {Code: KeyKpEqual},
- 57416: {Code: KeyKpSep},
- 57417: {Code: KeyKpLeft},
- 57418: {Code: KeyKpRight},
- 57419: {Code: KeyKpUp},
- 57420: {Code: KeyKpDown},
- 57421: {Code: KeyKpPgUp},
- 57422: {Code: KeyKpPgDown},
- 57423: {Code: KeyKpHome},
- 57424: {Code: KeyKpEnd},
- 57425: {Code: KeyKpInsert},
- 57426: {Code: KeyKpDelete},
- 57427: {Code: KeyKpBegin},
- 57428: {Code: KeyMediaPlay},
- 57429: {Code: KeyMediaPause},
- 57430: {Code: KeyMediaPlayPause},
- 57431: {Code: KeyMediaReverse},
- 57432: {Code: KeyMediaStop},
- 57433: {Code: KeyMediaFastForward},
- 57434: {Code: KeyMediaRewind},
- 57435: {Code: KeyMediaNext},
- 57436: {Code: KeyMediaPrev},
- 57437: {Code: KeyMediaRecord},
- 57438: {Code: KeyLowerVol},
- 57439: {Code: KeyRaiseVol},
- 57440: {Code: KeyMute},
- 57441: {Code: KeyLeftShift},
- 57442: {Code: KeyLeftCtrl},
- 57443: {Code: KeyLeftAlt},
- 57444: {Code: KeyLeftSuper},
- 57445: {Code: KeyLeftHyper},
- 57446: {Code: KeyLeftMeta},
- 57447: {Code: KeyRightShift},
- 57448: {Code: KeyRightCtrl},
- 57449: {Code: KeyRightAlt},
- 57450: {Code: KeyRightSuper},
- 57451: {Code: KeyRightHyper},
- 57452: {Code: KeyRightMeta},
- 57453: {Code: KeyIsoLevel3Shift},
- 57454: {Code: KeyIsoLevel5Shift},
- }
- func init() {
- // These are some faulty C0 mappings some terminals such as WezTerm have
- // and doesn't follow the specs.
- kittyKeyMap[ansi.NUL] = Key{Code: KeySpace, Mod: ModCtrl}
- for i := ansi.SOH; i <= ansi.SUB; i++ {
- if _, ok := kittyKeyMap[i]; !ok {
- kittyKeyMap[i] = Key{Code: rune(i + 0x60), Mod: ModCtrl}
- }
- }
- for i := ansi.FS; i <= ansi.US; i++ {
- if _, ok := kittyKeyMap[i]; !ok {
- kittyKeyMap[i] = Key{Code: rune(i + 0x40), Mod: ModCtrl}
- }
- }
- }
- const (
- kittyShift = 1 << iota
- kittyAlt
- kittyCtrl
- kittySuper
- kittyHyper
- kittyMeta
- kittyCapsLock
- kittyNumLock
- )
- func fromKittyMod(mod int) KeyMod {
- var m KeyMod
- if mod&kittyShift != 0 {
- m |= ModShift
- }
- if mod&kittyAlt != 0 {
- m |= ModAlt
- }
- if mod&kittyCtrl != 0 {
- m |= ModCtrl
- }
- if mod&kittySuper != 0 {
- m |= ModSuper
- }
- if mod&kittyHyper != 0 {
- m |= ModHyper
- }
- if mod&kittyMeta != 0 {
- m |= ModMeta
- }
- if mod&kittyCapsLock != 0 {
- m |= ModCapsLock
- }
- if mod&kittyNumLock != 0 {
- m |= ModNumLock
- }
- return m
- }
- // parseKittyKeyboard parses a Kitty Keyboard Protocol sequence.
- //
- // In `CSI u`, this is parsed as:
- //
- // CSI codepoint ; modifiers u
- // codepoint: ASCII Dec value
- //
- // The Kitty Keyboard Protocol extends this with optional components that can be
- // enabled progressively. The full sequence is parsed as:
- //
- // CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
- //
- // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/
- func parseKittyKeyboard(params ansi.Params) (Event Event) {
- var isRelease bool
- var key Key
- // The index of parameters separated by semicolons ';'. Sub parameters are
- // separated by colons ':'.
- var paramIdx int
- var sudIdx int // The sub parameter index
- for _, p := range params {
- // Kitty Keyboard Protocol has 3 optional components.
- switch paramIdx {
- case 0:
- switch sudIdx {
- case 0:
- var foundKey bool
- code := p.Param(1) // CSI u has a default value of 1
- key, foundKey = kittyKeyMap[code]
- if !foundKey {
- r := rune(code)
- if !utf8.ValidRune(r) {
- r = utf8.RuneError
- }
- key.Code = r
- }
- case 2:
- // shifted key + base key
- if b := rune(p.Param(1)); unicode.IsPrint(b) {
- // XXX: When alternate key reporting is enabled, the protocol
- // can return 3 things, the unicode codepoint of the key,
- // the shifted codepoint of the key, and the standard
- // PC-101 key layout codepoint.
- // This is useful to create an unambiguous mapping of keys
- // when using a different language layout.
- key.BaseCode = b
- }
- fallthrough
- case 1:
- // shifted key
- if s := rune(p.Param(1)); unicode.IsPrint(s) {
- // XXX: We swap keys here because we want the shifted key
- // to be the Rune that is returned by the event.
- // For example, shift+a should produce "A" not "a".
- // In such a case, we set AltRune to the original key "a"
- // and Rune to "A".
- key.ShiftedCode = s
- }
- }
- case 1:
- switch sudIdx {
- case 0:
- mod := p.Param(1)
- if mod > 1 {
- key.Mod = fromKittyMod(mod - 1)
- if key.Mod > ModShift {
- // XXX: We need to clear the text if we have a modifier key
- // other than a [ModShift] key.
- key.Text = ""
- }
- }
- case 1:
- switch p.Param(1) {
- case 2:
- key.IsRepeat = true
- case 3:
- isRelease = true
- }
- case 2:
- }
- case 2:
- if code := p.Param(0); code != 0 {
- key.Text += string(rune(code))
- }
- }
- sudIdx++
- if !p.HasMore() {
- paramIdx++
- sudIdx = 0
- }
- }
- //nolint:nestif
- if len(key.Text) == 0 && unicode.IsPrint(key.Code) &&
- (key.Mod <= ModShift || key.Mod == ModCapsLock || key.Mod == ModShift|ModCapsLock) {
- if key.Mod == 0 {
- key.Text = string(key.Code)
- } else {
- desiredCase := unicode.ToLower
- if key.Mod.Contains(ModShift) || key.Mod.Contains(ModCapsLock) {
- desiredCase = unicode.ToUpper
- }
- if key.ShiftedCode != 0 {
- key.Text = string(key.ShiftedCode)
- } else {
- key.Text = string(desiredCase(key.Code))
- }
- }
- }
- if isRelease {
- return KeyReleaseEvent(key)
- }
- return KeyPressEvent(key)
- }
- // parseKittyKeyboardExt parses a Kitty Keyboard Protocol sequence extensions
- // for non CSI u sequences. This includes things like CSI A, SS3 A and others,
- // and CSI ~.
- func parseKittyKeyboardExt(params ansi.Params, k KeyPressEvent) Event {
- // Handle Kitty keyboard protocol
- if len(params) > 2 && // We have at least 3 parameters
- params[0].Param(1) == 1 && // The first parameter is 1 (defaults to 1)
- params[1].HasMore() { // The second parameter is a subparameter (separated by a ":")
- switch params[2].Param(1) { // The third parameter is the event type (defaults to 1)
- case 2:
- k.IsRepeat = true
- case 3:
- return KeyReleaseEvent(k)
- }
- }
- return k
- }
|