mirror of
https://github.com/sst/opencode.git
synced 2025-08-24 06:54:09 +00:00
353 lines
8.7 KiB
Go
353 lines
8.7 KiB
Go
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
|
|
}
|