From 6e060ca1bd4c3c676a785ce31a422476c9022c35 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 22 Aug 2024 13:36:05 -0400 Subject: [PATCH 1/7] refactor!: remove backwards compatibility --- tty.go | 97 ---------------------------------------------------------- 1 file changed, 97 deletions(-) diff --git a/tty.go b/tty.go index eb3f3052b0..0dfb9f0558 100644 --- a/tty.go +++ b/tty.go @@ -131,103 +131,6 @@ func readInputs(ctx context.Context, msgs chan<- Msg, reader *driver) error { for _, msg := range events { incomingMsgs := []Msg{msg} - // We need to translate new e types to deprecated ones to keep - // compatibility. - switch e := msg.(type) { - case PasteMsg: - var k KeyMsg - k.Paste = true - k.Runes = []rune(e) - incomingMsgs = append(incomingMsgs, k) - case KeyPressMsg: - k := KeyMsg{ - Alt: e.Mod.Contains(ModAlt), - Runes: e.Runes, - Type: e.Type, - } - - // Backwards compatibility for ctrl- and shift- keys - switch { - case e.Mod.Contains(ModCtrl | ModShift): - switch e.Type { - case KeyUp, KeyDown, KeyRight, KeyLeft: - k.Runes = nil - k.Type = KeyCtrlShiftUp - e.Type + KeyUp - case KeyHome, KeyEnd: - k.Runes = nil - k.Type = KeyCtrlShiftHome - e.Type + KeyHome - } - case e.Mod.Contains(ModCtrl): - switch e.Type { - case KeyRunes: // KeyRunes - switch r := e.Rune(); r { - case ' ': - k.Runes = nil - k.Type = KeyCtrlAt - case '[', '\\', ']', '^', '_': - k.Runes = nil - k.Type = KeyCtrlOpenBracket - KeyType(r) + '[' - case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z': - k.Runes = nil - k.Type = KeyCtrlA - KeyType(r) + 'a' - case '?': - k.Runes = nil - k.Type = KeyCtrlQuestionMark - } - case KeyPgUp, KeyPgDown, KeyHome, KeyEnd: - k.Runes = nil - k.Type = KeyCtrlPgUp - e.Type + KeyPgUp - case KeyUp, KeyDown, KeyRight, KeyLeft: - k.Runes = nil - k.Type = KeyCtrlUp - e.Type + KeyUp - } - case e.Mod.Contains(ModShift): - switch e.Type { - case KeyTab: - k.Runes = nil - k.Type = KeyShiftTab - case KeyUp, KeyDown, KeyRight, KeyLeft: - k.Runes = nil - k.Type = KeyShiftUp - e.Type + KeyUp - k.Runes = nil - case KeyHome, KeyEnd: - k.Runes = nil - k.Type = KeyShiftHome - e.Type + KeyHome - } - } - - switch k.Type { - case KeyRunes: // KeyRunes - if len(k.Runes) > 0 { - incomingMsgs = append(incomingMsgs, k) - } - default: - incomingMsgs = append(incomingMsgs, k) - } - case MouseClickMsg: - m := toMouseMsg(Mouse(e)) - m.Action = MouseActionPress - m.Type = e.Button - incomingMsgs = append(incomingMsgs, m) - case MouseReleaseMsg: - m := toMouseMsg(Mouse(e)) - m.Action = MouseActionRelease - m.Type = MouseRelease - incomingMsgs = append(incomingMsgs, m) - case MouseWheelMsg: - m := toMouseMsg(Mouse(e)) - m.Action = MouseActionPress - m.Type = e.Button - incomingMsgs = append(incomingMsgs, m) - case MouseMotionMsg: - m := toMouseMsg(Mouse(e)) - m.Action = MouseActionMotion - m.Type = MouseMotion - incomingMsgs = append(incomingMsgs, m) - } - for _, m := range incomingMsgs { select { case msgs <- m: From 7b87642c8759c34f599989502961103a5b651305 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 22 Aug 2024 17:28:56 -0400 Subject: [PATCH 2/7] refactor!: use key/mouse msg interfaces Use key and mouse interfaces to catch their respective types as an interface. --- driver.go | 2 +- key.go | 195 ++++++++++++++++----- key_deprecated.go | 243 -------------------------- key_test.go | 168 +++++++++--------- kitty.go | 16 +- mouse.go | 176 ++++++++++++++++--- mouse_deprecated.go | 165 ------------------ mouse_test.go | 146 ++++++++-------- parse.go | 126 +++++++------- parse_test.go | 14 +- table.go | 414 ++++++++++++++++++++++---------------------- terminfo.go | 404 +++++++++++++++++++++--------------------- win32input.go | 174 +++++++++---------- xterm.go | 14 +- 14 files changed, 1048 insertions(+), 1209 deletions(-) delete mode 100644 key_deprecated.go delete mode 100644 mouse_deprecated.go diff --git a/driver.go b/driver.go index 2e2070c09d..1b3457d4ee 100644 --- a/driver.go +++ b/driver.go @@ -13,7 +13,7 @@ import ( // buffer. type driver struct { rd cancelreader.CancelReader - table map[string]Key // table is a lookup table for key sequences. + table map[string]key // table is a lookup table for key sequences. term string // term is the terminal name $TERM. diff --git a/key.go b/key.go index 33679414f2..5b07fd5142 100644 --- a/key.go +++ b/key.go @@ -192,12 +192,15 @@ const ( KeyIsoLevel5Shift ) -// Key contains information about a key or release. Keys are always sent to the +// KeyMsg represents an interface that all key messages must implement. +// KeyMsg contains information about a key or release. Keys are always sent to the // program's update function. 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 msg := msg.(type) { +// case KeyMsg: // catch all key messages (presses and releases) +// fmt.Println(msg.String()) // case KeyPressMsg: // switch msg.String() { // case "enter": @@ -210,29 +213,67 @@ const ( // // Switch on the key type (more foolproof) // switch msg := msg.(type) { // case KeyReleaseMsg: -// switch msg.Sym { +// switch msg.Type() { // case KeyEnter: // fmt.Println("you pressed enter!") // case KeyRunes: -// switch string(msg.Runes) { +// switch string(msg.Runes()) { // case "a": // fmt.Println("you pressed a!") // } // } // } // -// Note that Key.Runes will always contain at least one character, so you can -// always safely call Key.Runes[0]. In most cases Key.Runes will only contain -// one character, though certain input method editors (most notably Chinese -// IMEs) can input multiple runes at once. -type Key struct { - // Runes contains the actual characters received. This usually has a length +// Note that in the case of [KeyRunes], `key.Runes()` will always contain at +// least one character, so you can always safely call `key.Runes()[0]`. +// Instead, use `key.Rune()` to get the first key rune received. Though, in +// certain input method editors (most notably Chinese IMEs) can input multiple +// runes at once. +type KeyMsg interface { + // String returns a friendly string representation for a key. It's safe (and + // encouraged) for use in key comparison. + // + // For example: + // k := Key{Type: KeyEnter} + // fmt.Println(k) // Output: enter + // k = Key{Type: KeyRunes, Runes: []rune{'A'}, Mod: ModShift} + // fmt.Println(k) // Output: shift+a + // k = Key{Type: KeySpace, Runes: []rune{' '}, Mod: ModCtrl|ModShift} + // fmt.Println(k) // Output: ctrl+shift+space + String() string + + // Type returns the key type. A key type is either a special key or + // KeyRunes. Special keys are things like KeyEnter, KeyBackspace, and so + // on. + Type() KeyType + + // Rune returns the first rune in the Runes field. If the Runes field is + // empty, it returns 0. + Rune() rune + + // Runes returns the runes of the key. If the key is a special key, this + // will return nil. Use [Rune()] if you only care about the first rune. + Runes() []rune + + // Mod returns the key modifiers. Modifiers are things like ModCtrl, + // ModAlt, ModShift, and so on. + Mod() KeyMod + + // IsRepeat indicates whether the key is being held down and sending events + // repeatedly. + IsRepeat() bool +} + +// 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. +type key struct { + // runes contains the actual characters received. This usually has a length // of 1. Use [Rune()] to get the first key rune received. If the user - // presses shift+a, the Runes will be `[]rune{'A'}`. - Runes []rune + // presses shift+a, the runes will be `[]rune{'A'}`. + runes []rune - // Type is a special key, like enter, tab, backspace, and so on. - Type KeyType + // typ is a special key, like enter, tab, backspace, and so on. + typ KeyType // altRune is the actual, unshifted key pressed by the user. For example, // if the user presses shift+a, or caps lock is on, the altRune will be @@ -256,54 +297,130 @@ type Key struct { // Console API. baseRune rune - // Mod is a modifier key, like ctrl, alt, and so on. - Mod KeyMod + // mod is a modifier key, like ctrl, alt, and so on. + mod KeyMod - // IsRepeat indicates whether the key is being held down and sending events + // 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 + isRepeat bool +} + +// Mod represents a key modifier. Modifiers are things like ModCtrl, ModAlt, +// ModShift, and so on. +func (k key) Mod() KeyMod { + return k.mod +} + +// IsRepeat indicates whether the key is being held down and sending events +// repeatedly. +func (k key) IsRepeat() bool { + return k.isRepeat } // KeyPressMsg represents a key press message. -type KeyPressMsg Key +type KeyPressMsg key + +var _ KeyMsg = KeyPressMsg{} // String implements fmt.Stringer and is quite useful for matching key -// events. For details, on what this returns see [Key.String]. +// events. For details, on what this returns see [key.String]. func (k KeyPressMsg) String() string { - return Key(k).String() + return key(k).String() } // Rune returns the first rune in the Runes field. If the Runes field is empty, // it returns 0. func (k KeyPressMsg) Rune() rune { - return Key(k).Rune() + return key(k).Rune() +} + +// Runes returns the runes of the key. If the key is a special key, this will +// return nil. +func (k KeyPressMsg) Runes() []rune { + return key(k).Runes() +} + +// Type returns the key type. A key type is either a special key or KeyRunes. +// Special keys are things like KeyEnter, KeyBackspace, and so on. +func (k KeyPressMsg) Type() KeyType { + return key(k).Type() +} + +// Mod returns the key modifiers. Modifiers are things like ModCtrl, ModAlt, +// ModShift, and so on. +func (k KeyPressMsg) Mod() KeyMod { + return key(k).Mod() +} + +// IsRepeat indicates whether the key is being held down and sending events +// repeatedly. +func (k KeyPressMsg) IsRepeat() bool { + return key(k).IsRepeat() } // KeyReleaseMsg represents a key release message. -type KeyReleaseMsg Key +type KeyReleaseMsg key + +var _ KeyMsg = KeyReleaseMsg{} // String implements fmt.Stringer and is quite useful for matching complex key -// events. For details, on what this returns see [Key.String]. +// events. For details, on what this returns see [key.String]. func (k KeyReleaseMsg) String() string { - return Key(k).String() + return key(k).String() } // Rune returns the first rune in the Runes field. If the Runes field is empty, // it returns 0. func (k KeyReleaseMsg) Rune() rune { - return Key(k).Rune() + return key(k).Rune() +} + +// Runes returns the runes of the key. If the key is a special key, this will +// return nil. +func (k KeyReleaseMsg) Runes() []rune { + return key(k).Runes() +} + +// Type returns the key type. A key type is either a special key or KeyRunes. +// Special keys are things like KeyEnter, KeyBackspace, and so on. +func (k KeyReleaseMsg) Type() KeyType { + return key(k).Type() +} + +// Mod returns the key modifiers. Modifiers are things like ModCtrl, ModAlt, +// ModShift, and so on. +func (k KeyReleaseMsg) Mod() KeyMod { + return key(k).Mod() +} + +// IsRepeat indicates whether the key is being held down and sending events +// repeatedly. +func (k KeyReleaseMsg) IsRepeat() bool { + return key(k).IsRepeat() +} + +// Runes returns the runes of the key. If the key is a special key, this will +// return nil. +func (k key) Runes() []rune { + return k.runes } // Rune returns the first rune in the Runes field. If the Runes field is empty, // it returns 0. -func (k Key) Rune() rune { - if len(k.Runes) == 0 { +func (k key) Rune() rune { + if len(k.runes) == 0 { return 0 } - return k.Runes[0] + return k.runes[0] +} + +// Type returns the key type. A key type is either a special key or KeyRunes. +// Special keys are things like KeyEnter, KeyBackspace, and so on. +func (k key) Type() KeyType { + return k.typ } // String implements fmt.Stringer and is used to convert a key to a string. @@ -321,24 +438,24 @@ func (k Key) Rune() rune { // // For example, you'll always see "ctrl+shift+alt+a" and never // "shift+ctrl+alt+a". -func (k Key) String() string { +func (k key) String() string { var s string - if k.Mod.Contains(ModCtrl) && k.Type != KeyLeftCtrl && k.Type != KeyRightCtrl { + if k.mod.Contains(ModCtrl) && k.typ != KeyLeftCtrl && k.typ != KeyRightCtrl { s += "ctrl+" } - if k.Mod.Contains(ModAlt) && k.Type != KeyLeftAlt && k.Type != KeyRightAlt { + if k.mod.Contains(ModAlt) && k.typ != KeyLeftAlt && k.typ != KeyRightAlt { s += "alt+" } - if k.Mod.Contains(ModShift) && k.Type != KeyLeftShift && k.Type != KeyRightShift { + if k.mod.Contains(ModShift) && k.typ != KeyLeftShift && k.typ != KeyRightShift { s += "shift+" } - if k.Mod.Contains(ModMeta) && k.Type != KeyLeftMeta && k.Type != KeyRightMeta { + if k.mod.Contains(ModMeta) && k.typ != KeyLeftMeta && k.typ != KeyRightMeta { s += "meta+" } - if k.Mod.Contains(ModHyper) && k.Type != KeyLeftHyper && k.Type != KeyRightHyper { + if k.mod.Contains(ModHyper) && k.typ != KeyLeftHyper && k.typ != KeyRightHyper { s += "hyper+" } - if k.Mod.Contains(ModSuper) && k.Type != KeyLeftSuper && k.Type != KeyRightSuper { + if k.mod.Contains(ModSuper) && k.typ != KeyLeftSuper && k.typ != KeyRightSuper { s += "super+" } @@ -356,15 +473,15 @@ func (k Key) String() string { } else if k.altRune != 0 { // Otherwise, use the AltRune aka the non-shifted one if present. s += runeStr(k.altRune) - } else if len(k.Runes) > 0 { + } else if len(k.runes) > 0 { // Else, just print the rune. - if len(k.Runes) > 1 { - s += string(k.Runes) + if len(k.runes) > 1 { + s += string(k.runes) } else { s += runeStr(k.Rune()) } } else { - s += k.Type.String() + s += k.typ.String() } return s } diff --git a/key_deprecated.go b/key_deprecated.go deleted file mode 100644 index 0ef83a980e..0000000000 --- a/key_deprecated.go +++ /dev/null @@ -1,243 +0,0 @@ -package tea - -import ( - "strings" -) - -// KeyMsg contains information about a keypress. KeyMsgs are always sent to -// the program's update function. There are a couple general patterns you could -// use to check for keypresses: -// -// // Switch on the string representation of the key (shorter) -// switch msg := msg.(type) { -// case KeyMsg: -// switch msg.String() { -// case "enter": -// fmt.Println("you pressed enter!") -// case "a": -// fmt.Println("you pressed a!") -// } -// } -// -// // Switch on the key type (more foolproof) -// switch msg := msg.(type) { -// case KeyMsg: -// switch msg.Type { -// case KeyEnter: -// fmt.Println("you pressed enter!") -// case KeyRunes: -// switch string(msg.Runes) { -// case "a": -// fmt.Println("you pressed a!") -// } -// } -// } -// -// Note that Key.Runes will always contain at least one character, so you can -// always safely call Key.Runes[0]. In most cases Key.Runes will only contain -// one character, though certain input method editors (most notably Chinese -// IMEs) can input multiple runes at once. -// -// TODO(v2): Add a KeyMsg interface that incorporates all the key message -// types. -// -// Deprecated: KeyMsg is deprecated in favor of KeyPressMsg and KeyReleaseMsg. -type KeyMsg struct { - Type KeyType - Runes []rune - Alt bool - Paste bool -} - -// String returns a friendly string representation for a key. It's safe (and -// encouraged) for use in key comparison. -// -// k := Key{Type: KeyEnter} -// fmt.Println(k) -// // Output: enter -func (k KeyMsg) String() (str string) { - var buf strings.Builder - if k.Alt { - buf.WriteString("alt+") - } - if k.Type == KeyRunes { - if k.Paste { - // Note: bubbles/keys bindings currently do string compares to - // recognize shortcuts. Since pasted text should never activate - // shortcuts, we need to ensure that the binding code doesn't - // match Key events that result from pastes. We achieve this - // here by enclosing pastes in '[...]' so that the string - // comparison in Matches() fails in that case. - buf.WriteByte('[') - } - buf.WriteString(string(k.Runes)) - if k.Paste { - buf.WriteByte(']') - } - return buf.String() - } else if s, ok := keyNames[k.Type]; ok { - buf.WriteString(s) - return buf.String() - } - return "" -} - -// Control key aliases. -const ( - KeyNull KeyType = -iota - 10 - KeyBreak - - KeyCtrlAt // ctrl+@ - KeyCtrlA - KeyCtrlB - KeyCtrlC - KeyCtrlD - KeyCtrlE - KeyCtrlF - KeyCtrlG - KeyCtrlH - KeyCtrlI - KeyCtrlJ - KeyCtrlK - KeyCtrlL - KeyCtrlM - KeyCtrlN - KeyCtrlO - KeyCtrlP - KeyCtrlQ - KeyCtrlR - KeyCtrlS - KeyCtrlT - KeyCtrlU - KeyCtrlV - KeyCtrlW - KeyCtrlX - KeyCtrlY - KeyCtrlZ - KeyCtrlOpenBracket // ctrl+[ - KeyCtrlBackslash // ctrl+\ - KeyCtrlCloseBracket // ctrl+] - KeyCtrlCaret // ctrl+^ - KeyCtrlUnderscore // ctrl+_ - KeyCtrlQuestionMark // ctrl+? - KeyCtrlUp - KeyCtrlDown - KeyCtrlRight - KeyCtrlLeft - KeyCtrlPgUp - KeyCtrlPgDown - KeyCtrlHome - KeyCtrlEnd - - KeyShiftTab - KeyShiftUp - KeyShiftDown - KeyShiftRight - KeyShiftLeft - KeyShiftHome - KeyShiftEnd - - KeyCtrlShiftUp - KeyCtrlShiftDown - KeyCtrlShiftLeft - KeyCtrlShiftRight - KeyCtrlShiftHome - KeyCtrlShiftEnd - - // Deprecated: Use KeyEscape instead. - KeyEsc = KeyEscape -) - -// Mappings for control keys and other special keys to friendly consts. -var keyNames = map[KeyType]string{ - // Control keys. - KeyCtrlAt: "ctrl+@", // also ctrl+` (that's ctrl+backtick) - KeyCtrlA: "ctrl+a", - KeyCtrlB: "ctrl+b", - KeyCtrlC: "ctrl+c", - KeyCtrlD: "ctrl+d", - KeyCtrlE: "ctrl+e", - KeyCtrlF: "ctrl+f", - KeyCtrlG: "ctrl+g", - KeyCtrlH: "ctrl+h", - KeyTab: "tab", // also ctrl+i - KeyCtrlJ: "ctrl+j", - KeyCtrlK: "ctrl+k", - KeyCtrlL: "ctrl+l", - KeyEnter: "enter", - KeyCtrlN: "ctrl+n", - KeyCtrlO: "ctrl+o", - KeyCtrlP: "ctrl+p", - KeyCtrlQ: "ctrl+q", - KeyCtrlR: "ctrl+r", - KeyCtrlS: "ctrl+s", - KeyCtrlT: "ctrl+t", - KeyCtrlU: "ctrl+u", - KeyCtrlV: "ctrl+v", - KeyCtrlW: "ctrl+w", - KeyCtrlX: "ctrl+x", - KeyCtrlY: "ctrl+y", - KeyCtrlZ: "ctrl+z", - KeyEscape: "esc", - KeyCtrlOpenBracket: "ctrl+[", - KeyCtrlBackslash: "ctrl+\\", - KeyCtrlCloseBracket: "ctrl+]", - KeyCtrlCaret: "ctrl+^", - KeyCtrlUnderscore: "ctrl+_", - KeyBackspace: "backspace", - - // Other keys. - KeyRunes: "runes", - KeyUp: "up", - KeyDown: "down", - KeyRight: "right", - KeySpace: " ", // for backwards compatibility - KeyLeft: "left", - KeyShiftTab: "shift+tab", - KeyHome: "home", - KeyEnd: "end", - KeyCtrlHome: "ctrl+home", - KeyCtrlEnd: "ctrl+end", - KeyShiftHome: "shift+home", - KeyShiftEnd: "shift+end", - KeyCtrlShiftHome: "ctrl+shift+home", - KeyCtrlShiftEnd: "ctrl+shift+end", - KeyPgUp: "pgup", - KeyPgDown: "pgdown", - KeyCtrlPgUp: "ctrl+pgup", - KeyCtrlPgDown: "ctrl+pgdown", - KeyDelete: "delete", - KeyInsert: "insert", - KeyCtrlUp: "ctrl+up", - KeyCtrlDown: "ctrl+down", - KeyCtrlRight: "ctrl+right", - KeyCtrlLeft: "ctrl+left", - KeyShiftUp: "shift+up", - KeyShiftDown: "shift+down", - KeyShiftRight: "shift+right", - KeyShiftLeft: "shift+left", - KeyCtrlShiftUp: "ctrl+shift+up", - KeyCtrlShiftDown: "ctrl+shift+down", - KeyCtrlShiftLeft: "ctrl+shift+left", - KeyCtrlShiftRight: "ctrl+shift+right", - 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", -} diff --git a/key_test.go b/key_test.go index 0b3eae31f1..b7824a0d99 100644 --- a/key_test.go +++ b/key_test.go @@ -24,21 +24,21 @@ var sequences = buildKeysTable(_FlagTerminfo, "dumb") func TestKeyString(t *testing.T) { t.Run("alt+space", func(t *testing.T) { - k := KeyPressMsg{Type: KeySpace, Runes: []rune{' '}, Mod: ModAlt} + k := KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModAlt} if got := k.String(); got != "alt+space" { t.Fatalf(`expected a "alt+space ", got %q`, got) } }) t.Run("runes", func(t *testing.T) { - k := KeyPressMsg{Runes: []rune{'a'}} + k := KeyPressMsg{runes: []rune{'a'}} if got := k.String(); got != "a" { t.Fatalf(`expected an "a", got %q`, got) } }) t.Run("invalid", func(t *testing.T) { - k := KeyPressMsg{Type: 99999} + k := KeyPressMsg{typ: 99999} if got := k.String(); got != "" { t.Fatalf(`expected a "unknown", got %q`, got) } @@ -78,7 +78,7 @@ func buildBaseSeqTests() []seqTest { // position report having the same sequence. See [parseCsi] for more // information. if f3CurPosRegexp.MatchString(seq) { - st.msgs = []Msg{k, CursorPositionMsg{Row: 1, Column: int(key.Mod) + 1}} + st.msgs = []Msg{k, CursorPositionMsg{Row: 1, Column: int(key.mod) + 1}} } td = append(td, st) } @@ -96,14 +96,14 @@ func buildBaseSeqTests() []seqTest { seqTest{ []byte{' '}, []Msg{ - KeyPressMsg{Type: KeySpace, Runes: []rune{' '}}, + KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, }, }, // An escape character with the alt modifier. seqTest{ []byte{'\x1b', ' '}, []Msg{ - KeyPressMsg{Type: KeySpace, Runes: []rune{' '}, Mod: ModAlt}, + KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModAlt}, }, }, ) @@ -116,101 +116,101 @@ func TestParseSequence(t *testing.T) { // Xterm modifyOtherKeys CSI 27 ; ; ~ seqTest{ []byte("\x1b[27;3;20320~"), - []Msg{KeyPressMsg{Runes: []rune{'你'}, Mod: ModAlt}}, + []Msg{KeyPressMsg{runes: []rune{'你'}, mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;65~"), - []Msg{KeyPressMsg{Runes: []rune{'A'}, Mod: ModAlt}}, + []Msg{KeyPressMsg{runes: []rune{'A'}, mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;8~"), - []Msg{KeyPressMsg{Type: KeyBackspace, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;27~"), - []Msg{KeyPressMsg{Type: KeyEscape, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyEscape, mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;127~"), - []Msg{KeyPressMsg{Type: KeyBackspace, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, }, // Kitty keyboard / CSI u (fixterms) seqTest{ []byte("\x1b[1B"), - []Msg{KeyPressMsg{Type: KeyDown}}, + []Msg{KeyPressMsg{typ: KeyDown}}, }, seqTest{ []byte("\x1b[1;B"), - []Msg{KeyPressMsg{Type: KeyDown}}, + []Msg{KeyPressMsg{typ: KeyDown}}, }, seqTest{ []byte("\x1b[1;4B"), - []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyDown}}, + []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyDown}}, }, seqTest{ []byte("\x1b[8~"), - []Msg{KeyPressMsg{Type: KeyEnd}}, + []Msg{KeyPressMsg{typ: KeyEnd}}, }, seqTest{ []byte("\x1b[8;~"), - []Msg{KeyPressMsg{Type: KeyEnd}}, + []Msg{KeyPressMsg{typ: KeyEnd}}, }, seqTest{ []byte("\x1b[8;10~"), - []Msg{KeyPressMsg{Mod: ModShift | ModMeta, Type: KeyEnd}}, + []Msg{KeyPressMsg{mod: ModShift | ModMeta, typ: KeyEnd}}, }, seqTest{ []byte("\x1b[27;4u"), - []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyEscape}}, + []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyEscape}}, }, seqTest{ []byte("\x1b[127;4u"), - []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyBackspace}}, + []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyBackspace}}, }, seqTest{ []byte("\x1b[57358;4u"), - []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Type: KeyCapsLock}}, + []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyCapsLock}}, }, seqTest{ []byte("\x1b[9;2u"), - []Msg{KeyPressMsg{Mod: ModShift, Type: KeyTab}}, + []Msg{KeyPressMsg{mod: ModShift, typ: KeyTab}}, }, seqTest{ []byte("\x1b[195;u"), - []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Type: KeyRunes}}, + []Msg{KeyPressMsg{runes: []rune{'Ã'}, typ: KeyRunes}}, }, seqTest{ []byte("\x1b[20320;2u"), - []Msg{KeyPressMsg{Runes: []rune{'你'}, Mod: ModShift, Type: KeyRunes}}, + []Msg{KeyPressMsg{runes: []rune{'你'}, mod: ModShift, typ: KeyRunes}}, }, seqTest{ []byte("\x1b[195;:1u"), - []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Type: KeyRunes}}, + []Msg{KeyPressMsg{runes: []rune{'Ã'}, typ: KeyRunes}}, }, seqTest{ []byte("\x1b[195;2:3u"), - []Msg{KeyReleaseMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + []Msg{KeyReleaseMsg{runes: []rune{'Ã'}, mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:2u"), - []Msg{KeyPressMsg{Runes: []rune{'Ã'}, IsRepeat: true, Mod: ModShift}}, + []Msg{KeyPressMsg{runes: []rune{'Ã'}, isRepeat: true, mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:1u"), - []Msg{KeyPressMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + []Msg{KeyPressMsg{runes: []rune{'Ã'}, mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:3u"), - []Msg{KeyReleaseMsg{Runes: []rune{'Ã'}, Mod: ModShift}}, + []Msg{KeyReleaseMsg{runes: []rune{'Ã'}, mod: ModShift}}, }, seqTest{ []byte("\x1b[97;2;65u"), - []Msg{KeyPressMsg{Runes: []rune{'A'}, Mod: ModShift, altRune: 'a'}}, + []Msg{KeyPressMsg{runes: []rune{'A'}, mod: ModShift, altRune: 'a'}}, }, seqTest{ []byte("\x1b[97;;229u"), - []Msg{KeyPressMsg{Runes: []rune{'å'}, altRune: 'a'}}, + []Msg{KeyPressMsg{runes: []rune{'å'}, altRune: 'a'}}, }, // focus/blur @@ -230,86 +230,86 @@ func TestParseSequence(t *testing.T) { seqTest{ []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, []Msg{ - MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, + MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, }, }, // SGR Mouse event. seqTest{ []byte("\x1b[<0;33;17M"), []Msg{ - MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, + MouseClickMsg{x: 32, y: 16, button: MouseLeft}, }, }, // Runes. seqTest{ []byte{'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, }, }, seqTest{ []byte{'\x1b', 'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModAlt}, + KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, }, }, seqTest{ []byte{'a', 'a', 'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, - KeyPressMsg{Runes: []rune{'a'}}, - KeyPressMsg{Runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, }, }, // Multi-byte rune. seqTest{ []byte("☃"), []Msg{ - KeyPressMsg{Runes: []rune{'☃'}}, + KeyPressMsg{runes: []rune{'☃'}}, }, }, seqTest{ []byte("\x1b☃"), []Msg{ - KeyPressMsg{Runes: []rune{'☃'}, Mod: ModAlt}, + KeyPressMsg{runes: []rune{'☃'}, mod: ModAlt}, }, }, // Standalone control chacters. seqTest{ []byte{'\x1b'}, []Msg{ - KeyPressMsg{Type: KeyEscape}, + KeyPressMsg{typ: KeyEscape}, }, }, seqTest{ []byte{ansi.SOH}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModCtrl}, + KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, }, }, seqTest{ []byte{'\x1b', ansi.SOH}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModCtrl | ModAlt}, + KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl | ModAlt}, }, }, seqTest{ []byte{ansi.NUL}, []Msg{ - KeyPressMsg{Runes: []rune{' '}, Type: KeySpace, Mod: ModCtrl}, + KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl}, }, }, seqTest{ []byte{'\x1b', ansi.NUL}, []Msg{ - KeyPressMsg{Runes: []rune{' '}, Type: KeySpace, Mod: ModCtrl | ModAlt}, + KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl | ModAlt}, }, }, // C1 control characters. seqTest{ []byte{'\x80'}, []Msg{ - KeyPressMsg{Runes: []rune{0x80 - '@'}, Mod: ModCtrl | ModAlt}, + KeyPressMsg{runes: []rune{0x80 - '@'}, mod: ModCtrl | ModAlt}, }, }, ) @@ -349,7 +349,7 @@ func TestParseSequence(t *testing.T) { func TestReadLongInput(t *testing.T) { expect := make([]Msg, 1000) for i := 0; i < 1000; i++ { - expect[i] = KeyPressMsg{Runes: []rune{'a'}} + expect[i] = KeyPressMsg{runes: []rune{'a'}} } input := strings.Repeat("a", 1000) drv, err := newDriver(strings.NewReader(input), "dumb", 0) @@ -385,77 +385,77 @@ func TestReadInput(t *testing.T) { "a", []byte{'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, }, }, { "space", []byte{' '}, []Msg{ - KeyPressMsg{Type: KeySpace, Runes: []rune{' '}}, + KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, }, }, { "a alt+a", []byte{'a', '\x1b', 'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, - KeyPressMsg{Runes: []rune{'a'}, Mod: ModAlt}, + KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, }, }, { "a alt+a a", []byte{'a', '\x1b', 'a', 'a'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, - KeyPressMsg{Runes: []rune{'a'}, Mod: ModAlt}, - KeyPressMsg{Runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, + KeyPressMsg{runes: []rune{'a'}}, }, }, { "ctrl+a", []byte{byte(ansi.SOH)}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModCtrl}, + KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, }, }, { "ctrl+a ctrl+b", []byte{byte(ansi.SOH), byte(ansi.STX)}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModCtrl}, - KeyPressMsg{Runes: []rune{'b'}, Mod: ModCtrl}, + KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, + KeyPressMsg{runes: []rune{'b'}, mod: ModCtrl}, }, }, { "alt+a", []byte{byte(0x1b), 'a'}, []Msg{ - KeyPressMsg{Mod: ModAlt, Runes: []rune{'a'}}, + KeyPressMsg{mod: ModAlt, runes: []rune{'a'}}, }, }, { "a b c d", []byte{'a', 'b', 'c', 'd'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, - KeyPressMsg{Runes: []rune{'b'}}, - KeyPressMsg{Runes: []rune{'c'}}, - KeyPressMsg{Runes: []rune{'d'}}, + KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'b'}}, + KeyPressMsg{runes: []rune{'c'}}, + KeyPressMsg{runes: []rune{'d'}}, }, }, { "up", []byte("\x1b[A"), []Msg{ - KeyPressMsg{Type: KeyUp}, + KeyPressMsg{typ: KeyUp}, }, }, { "wheel up", []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, []Msg{ - MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, + MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, }, }, { @@ -465,41 +465,41 @@ func TestReadInput(t *testing.T) { '\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33), }, []Msg{ - MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, - MouseReleaseMsg{X: 64, Y: 32, Button: MouseNone}, + MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, + MouseReleaseMsg{x: 64, y: 32, button: MouseNone}, }, }, { "shift+tab", []byte{'\x1b', '[', 'Z'}, []Msg{ - KeyPressMsg{Type: KeyTab, Mod: ModShift}, + KeyPressMsg{typ: KeyTab, mod: ModShift}, }, }, { "enter", []byte{'\r'}, - []Msg{KeyPressMsg{Type: KeyEnter}}, + []Msg{KeyPressMsg{typ: KeyEnter}}, }, { "alt+enter", []byte{'\x1b', '\r'}, []Msg{ - KeyPressMsg{Type: KeyEnter, Mod: ModAlt}, + KeyPressMsg{typ: KeyEnter, mod: ModAlt}, }, }, { "insert", []byte{'\x1b', '[', '2', '~'}, []Msg{ - KeyPressMsg{Type: KeyInsert}, + KeyPressMsg{typ: KeyInsert}, }, }, { "ctrl+alt+a", []byte{'\x1b', byte(ansi.SOH)}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}, Mod: ModCtrl | ModAlt}, + KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl | ModAlt}, }, }, { @@ -511,52 +511,52 @@ func TestReadInput(t *testing.T) { { "up", []byte{'\x1b', 'O', 'A'}, - []Msg{KeyPressMsg{Type: KeyUp}}, + []Msg{KeyPressMsg{typ: KeyUp}}, }, { "down", []byte{'\x1b', 'O', 'B'}, - []Msg{KeyPressMsg{Type: KeyDown}}, + []Msg{KeyPressMsg{typ: KeyDown}}, }, { "right", []byte{'\x1b', 'O', 'C'}, - []Msg{KeyPressMsg{Type: KeyRight}}, + []Msg{KeyPressMsg{typ: KeyRight}}, }, { "left", []byte{'\x1b', 'O', 'D'}, - []Msg{KeyPressMsg{Type: KeyLeft}}, + []Msg{KeyPressMsg{typ: KeyLeft}}, }, { "alt+enter", []byte{'\x1b', '\x0d'}, - []Msg{KeyPressMsg{Type: KeyEnter, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyEnter, mod: ModAlt}}, }, { "alt+backspace", []byte{'\x1b', '\x7f'}, - []Msg{KeyPressMsg{Type: KeyBackspace, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, }, { "ctrl+space", []byte{'\x00'}, - []Msg{KeyPressMsg{Type: KeySpace, Runes: []rune{' '}, Mod: ModCtrl}}, + []Msg{KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl}}, }, { "ctrl+alt+space", []byte{'\x1b', '\x00'}, - []Msg{KeyPressMsg{Type: KeySpace, Runes: []rune{' '}, Mod: ModCtrl | ModAlt}}, + []Msg{KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl | ModAlt}}, }, { "esc", []byte{'\x1b'}, - []Msg{KeyPressMsg{Type: KeyEscape}}, + []Msg{KeyPressMsg{typ: KeyEscape}}, }, { "alt+esc", []byte{'\x1b', '\x1b'}, - []Msg{KeyPressMsg{Type: KeyEscape, Mod: ModAlt}}, + []Msg{KeyPressMsg{typ: KeyEscape, mod: ModAlt}}, }, { "a b o", @@ -570,7 +570,7 @@ func TestReadInput(t *testing.T) { PasteStartMsg{}, PasteMsg("a b"), PasteEndMsg{}, - KeyPressMsg{Runes: []rune{'o'}}, + KeyPressMsg{runes: []rune{'o'}}, }, }, { @@ -597,10 +597,10 @@ func TestReadInput(t *testing.T) { "a ?0xfe? b", []byte{'a', '\xfe', ' ', 'b'}, []Msg{ - KeyPressMsg{Runes: []rune{'a'}}, + KeyPressMsg{runes: []rune{'a'}}, UnknownMsg(rune(0xfe)), - KeyPressMsg{Type: KeySpace, Runes: []rune{' '}}, - KeyPressMsg{Runes: []rune{'b'}}, + KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, + KeyPressMsg{runes: []rune{'b'}}, }, }, } diff --git a/kitty.go b/kitty.go index a644888b00..5522a708ae 100644 --- a/kitty.go +++ b/kitty.go @@ -224,20 +224,20 @@ func fromKittyMod(mod int) KeyMod { // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { var isRelease bool - key := Key{} + key := key{} if params := csi.Subparams(0); len(params) > 0 { code := params[0] if sym, ok := kittyKeyMap[code]; ok { - key.Type = sym + key.typ = sym } else { r := rune(code) if !utf8.ValidRune(r) { r = utf8.RuneError } - key.Type = KeyRunes - key.Runes = []rune{r} + key.typ = KeyRunes + key.runes = []rune{r} // alternate key reporting switch len(params) { @@ -262,7 +262,7 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { // In such a case, we set AltRune to the original key "a" // and Rune to "A". key.altRune = key.Rune() - key.Runes = []rune{s} + key.runes = []rune{s} } } } @@ -270,12 +270,12 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { if params := csi.Subparams(1); len(params) > 0 { mod := params[0] if mod > 1 { - key.Mod = fromKittyMod(mod - 1) + key.mod = fromKittyMod(mod - 1) } if len(params) > 1 { switch params[1] { case 2: - key.IsRepeat = true + key.isRepeat = true case 3: isRelease = true } @@ -285,7 +285,7 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { r := rune(params[0]) if unicode.IsPrint(r) { key.altRune = key.Rune() - key.Runes = []rune{r} + key.runes = []rune{r} } } if isRelease { diff --git a/mouse.go b/mouse.go index f3460fa8f8..6946224937 100644 --- a/mouse.go +++ b/mouse.go @@ -54,26 +54,48 @@ var mouseButtons = map[MouseButton]string{ MouseExtra2: "button11", } -// Mouse represents a mouse message. -type Mouse struct { - X, Y int - Button MouseButton - Mod KeyMod +// mouse represents a mouse message. +type mouse struct { + x, y int + button MouseButton + mod KeyMod +} + +var _ MouseMsg = mouse{} + +// Button implements MouseMsg. +func (m mouse) Button() MouseButton { + return m.button +} + +// Mod implements MouseMsg. +func (m mouse) Mod() KeyMod { + return m.mod +} + +// X implements MouseMsg. +func (m mouse) X() int { + return m.x +} + +// Y implements MouseMsg. +func (m mouse) Y() int { + return m.y } // String returns a string representation of the mouse message. -func (m Mouse) String() (s string) { - if m.Mod.Contains(ModCtrl) { +func (m mouse) String() (s string) { + if m.mod.Contains(ModCtrl) { s += "ctrl+" } - if m.Mod.Contains(ModAlt) { + if m.mod.Contains(ModAlt) { s += "alt+" } - if m.Mod.Contains(ModShift) { + if m.mod.Contains(ModShift) { s += "shift+" } - str, ok := mouseButtons[m.Button] + str, ok := mouseButtons[m.button] if !ok { s += "unknown" } else if str != "none" { // motion events don't have a button @@ -83,37 +105,145 @@ func (m Mouse) String() (s string) { return s } +// MouseMsg contains information about a mouse event and are sent to a programs +// update function when mouse activity occurs. Note that the mouse must first +// be enabled in order for the mouse events to be received. +type MouseMsg interface { + // String returns a string representation of the mouse event. + String() string + + // X returns the x-coordinate of the mouse event. + X() int + + // Y returns the y-coordinate of the mouse event. + Y() int + + // Button returns the button that was pressed during the mouse event. + Button() MouseButton + + // Mod returns any modifier keys that were pressed during the mouse event. + Mod() KeyMod +} + // MouseClickMsg represents a mouse button click message. -type MouseClickMsg Mouse +type MouseClickMsg mouse + +var _ MouseMsg = MouseClickMsg{} + +// Button implements MouseMsg. +func (e MouseClickMsg) Button() MouseButton { + return mouse(e).Button() +} + +// Mod implements MouseMsg. +func (e MouseClickMsg) Mod() KeyMod { + return mouse(e).Mod() +} + +// X implements MouseMsg. +func (e MouseClickMsg) X() int { + return mouse(e).X() +} + +// Y implements MouseMsg. +func (e MouseClickMsg) Y() int { + return mouse(e).Y() +} // String returns a string representation of the mouse click message. func (e MouseClickMsg) String() string { - return Mouse(e).String() + return mouse(e).String() } +var _ MouseMsg = MouseReleaseMsg{} + // MouseReleaseMsg represents a mouse button release message. -type MouseReleaseMsg Mouse +type MouseReleaseMsg mouse + +// Button implements MouseMsg. +func (e MouseReleaseMsg) Button() MouseButton { + return mouse(e).Button() +} + +// Mod implements MouseMsg. +func (e MouseReleaseMsg) Mod() KeyMod { + return mouse(e).Mod() +} + +// X implements MouseMsg. +func (e MouseReleaseMsg) X() int { + return mouse(e).X() +} + +// Y implements MouseMsg. +func (e MouseReleaseMsg) Y() int { + return mouse(e).Y() +} // String returns a string representation of the mouse release message. func (e MouseReleaseMsg) String() string { - return Mouse(e).String() + return mouse(e).String() } +var _ MouseMsg = MouseWheelMsg{} + // MouseWheelMsg represents a mouse wheel message event. -type MouseWheelMsg Mouse +type MouseWheelMsg mouse + +// Button implements MouseMsg. +func (e MouseWheelMsg) Button() MouseButton { + return mouse(e).Button() +} + +// Mod implements MouseMsg. +func (e MouseWheelMsg) Mod() KeyMod { + return mouse(e).Mod() +} + +// X implements MouseMsg. +func (e MouseWheelMsg) X() int { + return mouse(e).X() +} + +// Y implements MouseMsg. +func (e MouseWheelMsg) Y() int { + return mouse(e).Y() +} // String returns a string representation of the mouse wheel message. func (e MouseWheelMsg) String() string { - return Mouse(e).String() + return mouse(e).String() } // MouseMotionMsg represents a mouse motion message. -type MouseMotionMsg Mouse +type MouseMotionMsg mouse + +var _ MouseMsg = MouseMotionMsg{} + +// Button implements MouseMsg. +func (e MouseMotionMsg) Button() MouseButton { + return mouse(e).Button() +} + +// Mod implements MouseMsg. +func (e MouseMotionMsg) Mod() KeyMod { + return mouse(e).Mod() +} + +// X implements MouseMsg. +func (e MouseMotionMsg) X() int { + return mouse(e).X() +} + +// Y implements MouseMsg. +func (e MouseMotionMsg) Y() int { + return mouse(e).Y() +} // String returns a string representation of the mouse motion message. func (e MouseMotionMsg) String() string { - m := Mouse(e) - if m.Button != 0 { + m := mouse(e) + if m.button != 0 { return m.String() + "+motion" } return m.String() + "motion" @@ -142,11 +272,11 @@ func parseSGRMouseEvent(csi *ansi.CsiSequence) Msg { x-- y-- - m := Mouse{X: x, Y: y, Button: btn, Mod: mod} + m := mouse{x: x, y: y, button: btn, mod: mod} // Wheel buttons don't have release events // Motion can be reported as a release event in some terminals (Windows Terminal) - if isWheel(m.Button) { + if isWheel(m.button) { return MouseWheelMsg(m) } else if !isMotion && release { return MouseReleaseMsg(m) @@ -181,8 +311,8 @@ func parseX10MouseEvent(buf []byte) Msg { x := int(v[1]) - x10MouseByteOffset - 1 y := int(v[2]) - x10MouseByteOffset - 1 - m := Mouse{X: x, Y: y, Button: btn, Mod: mod} - if isWheel(m.Button) { + m := mouse{x: x, y: y, button: btn, mod: mod} + if isWheel(m.button) { return MouseWheelMsg(m) } else if isMotion { return MouseMotionMsg(m) diff --git a/mouse_deprecated.go b/mouse_deprecated.go deleted file mode 100644 index c0f363ef94..0000000000 --- a/mouse_deprecated.go +++ /dev/null @@ -1,165 +0,0 @@ -package tea - -// MouseMsg contains information about a mouse event and are sent to a programs -// update function when mouse activity occurs. Note that the mouse must first -// be enabled in order for the mouse events to be received. -// -// TODO(v2): Add a MouseMsg interface that incorporates all the mouse message -// types. -// -// Deprecated: in favor of MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, and -// MouseMotionMsg. -type MouseMsg struct { - X int - Y int - Shift bool - Alt bool - Ctrl bool - Action MouseAction - Button MouseButton - Type MouseEventType -} - -// MouseEvent represents a mouse event. -// -// Deprecated: Use Mouse. -type MouseEvent = MouseMsg - -// IsWheel returns true if the mouse event is a wheel event. -func (m MouseMsg) IsWheel() bool { - return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown || - m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight -} - -// String returns a string representation of a mouse event. -func (m MouseMsg) String() (s string) { - if m.Ctrl { - s += "ctrl+" - } - if m.Alt { - s += "alt+" - } - if m.Shift { - s += "shift+" - } - - if m.Button == MouseButtonNone { //nolint:nestif - if m.Action == MouseActionMotion || m.Action == MouseActionRelease { - s += mouseMsgActions[m.Action] - } else { - s += "unknown" - } - } else if m.IsWheel() { - s += mouseMsgButtons[m.Button] - } else { - btn := mouseMsgButtons[m.Button] - if btn != "" { - s += btn - } - act := mouseMsgActions[m.Action] - if act != "" { - s += " " + act - } - } - - return s -} - -// MouseAction represents the action that occurred during a mouse event. -// -// Deprecated: Use MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, and -// MouseMotionMsg. -type MouseAction int - -// Mouse event actions. -// -// Deprecated in favor of MouseClickMsg, MouseReleaseMsg, MouseWheelMsg, and -// MouseMotionMsg. -const ( - MouseActionPress MouseAction = iota - MouseActionRelease - MouseActionMotion -) - -var mouseMsgActions = map[MouseAction]string{ - MouseActionPress: "press", - MouseActionRelease: "release", - MouseActionMotion: "motion", -} - -// Mouse event buttons -// -// This is based on X11 mouse button codes. -// -// 1 = left button -// 2 = middle button (pressing the scroll wheel) -// 3 = right button -// 4 = turn scroll wheel up -// 5 = turn scroll wheel down -// 6 = push scroll wheel left -// 7 = push scroll wheel right -// 8 = 4th button (aka browser backward button) -// 9 = 5th button (aka browser forward button) -// 10 -// 11 -// -// Other buttons are not supported. -// -// Deprecated: Use MouseNone, MouseLeft, etc. -const ( - MouseButtonNone = MouseNone - MouseButtonLeft = MouseLeft - MouseButtonMiddle = MouseMiddle - MouseButtonRight = MouseRight - MouseButtonWheelUp = MouseWheelUp - MouseButtonWheelDown = MouseWheelDown - MouseButtonWheelLeft = MouseWheelLeft - MouseButtonWheelRight = MouseWheelRight - MouseButtonBackward = MouseBackward - MouseButtonForward = MouseForward - MouseButton10 = MouseExtra1 - MouseButton11 = MouseExtra2 -) - -// Deprecated: Use mouseButtons. -var mouseMsgButtons = map[MouseButton]string{ - MouseButtonNone: "none", - MouseButtonLeft: "left", - MouseButtonMiddle: "middle", - MouseButtonRight: "right", - MouseButtonWheelUp: "wheel up", - MouseButtonWheelDown: "wheel down", - MouseButtonWheelLeft: "wheel left", - MouseButtonWheelRight: "wheel right", - MouseButtonBackward: "backward", - MouseButtonForward: "forward", - MouseButton10: "button 10", - MouseButton11: "button 11", -} - -// MouseEventType indicates the type of mouse event occurring. -// -// Deprecated: Use MouseButton. -type MouseEventType = MouseButton - -// Mouse event types. -// -// Deprecated in favor of MouseReleaseMsg and MouseMotionMsg. -const ( - MouseUnknown = MouseNone - - MouseRelease MouseEventType = -iota // mouse button release (X10 only) - MouseMotion -) - -// toMouseMsg converts a mouse event to a mouse message. -func toMouseMsg(m Mouse) MouseMsg { - return MouseMsg{ - X: m.X, - Y: m.Y, - Shift: m.Mod.Contains(ModShift), - Alt: m.Mod.Contains(ModAlt), - Ctrl: m.Mod.Contains(ModCtrl), - Button: m.Button, - } -} diff --git a/mouse_test.go b/mouse_test.go index 69b2730d1f..190dcd6f1f 100644 --- a/mouse_test.go +++ b/mouse_test.go @@ -16,96 +16,96 @@ func TestMouseEvent_String(t *testing.T) { }{ { name: "unknown", - event: MouseClickMsg{Button: MouseButton(0xff)}, + event: MouseClickMsg{button: MouseButton(0xff)}, expected: "unknown", }, { name: "left", - event: MouseClickMsg{Button: MouseLeft}, + event: MouseClickMsg{button: MouseLeft}, expected: "left", }, { name: "right", - event: MouseClickMsg{Button: MouseRight}, + event: MouseClickMsg{button: MouseRight}, expected: "right", }, { name: "middle", - event: MouseClickMsg{Button: MouseMiddle}, + event: MouseClickMsg{button: MouseMiddle}, expected: "middle", }, { name: "release", - event: MouseReleaseMsg{Button: MouseNone}, + event: MouseReleaseMsg{button: MouseNone}, expected: "", }, { name: "wheelup", - event: MouseWheelMsg{Button: MouseWheelUp}, + event: MouseWheelMsg{button: MouseWheelUp}, expected: "wheelup", }, { name: "wheeldown", - event: MouseWheelMsg{Button: MouseWheelDown}, + event: MouseWheelMsg{button: MouseWheelDown}, expected: "wheeldown", }, { name: "wheelleft", - event: MouseWheelMsg{Button: MouseWheelLeft}, + event: MouseWheelMsg{button: MouseWheelLeft}, expected: "wheelleft", }, { name: "wheelright", - event: MouseWheelMsg{Button: MouseWheelRight}, + event: MouseWheelMsg{button: MouseWheelRight}, expected: "wheelright", }, { name: "motion", - event: MouseMotionMsg{Button: MouseNone}, + event: MouseMotionMsg{button: MouseNone}, expected: "motion", }, { name: "shift+left", - event: MouseReleaseMsg{Button: MouseLeft, Mod: ModShift}, + event: MouseReleaseMsg{button: MouseLeft, mod: ModShift}, expected: "shift+left", }, { - name: "shift+left", event: MouseClickMsg{Button: MouseLeft, Mod: ModShift}, + name: "shift+left", event: MouseClickMsg{button: MouseLeft, mod: ModShift}, expected: "shift+left", }, { name: "ctrl+shift+left", - event: MouseClickMsg{Button: MouseLeft, Mod: ModCtrl | ModShift}, + event: MouseClickMsg{button: MouseLeft, mod: ModCtrl | ModShift}, expected: "ctrl+shift+left", }, { name: "alt+left", - event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt}, + event: MouseClickMsg{button: MouseLeft, mod: ModAlt}, expected: "alt+left", }, { name: "ctrl+left", - event: MouseClickMsg{Button: MouseLeft, Mod: ModCtrl}, + event: MouseClickMsg{button: MouseLeft, mod: ModCtrl}, expected: "ctrl+left", }, { name: "ctrl+alt+left", - event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt | ModCtrl}, + event: MouseClickMsg{button: MouseLeft, mod: ModAlt | ModCtrl}, expected: "ctrl+alt+left", }, { name: "ctrl+alt+shift+left", - event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt | ModCtrl | ModShift}, + event: MouseClickMsg{button: MouseLeft, mod: ModAlt | ModCtrl | ModShift}, expected: "ctrl+alt+shift+left", }, { name: "ignore coordinates", - event: MouseClickMsg{X: 100, Y: 200, Button: MouseLeft}, + event: MouseClickMsg{x: 100, y: 200, button: MouseLeft}, expected: "left", }, { name: "broken type", - event: MouseClickMsg{Button: MouseButton(120)}, + event: MouseClickMsg{button: MouseButton(120)}, expected: "unknown", }, } @@ -147,145 +147,145 @@ func TestParseX10MouseDownEvent(t *testing.T) { { name: "zero position", buf: encode(0b0000_0000, 0, 0), - expected: MouseClickMsg{X: 0, Y: 0, Button: MouseLeft}, + expected: MouseClickMsg{x: 0, y: 0, button: MouseLeft}, }, { name: "max position", buf: encode(0b0000_0000, 222, 222), // Because 255 (max int8) - 32 - 1. - expected: MouseClickMsg{X: 222, Y: 222, Button: MouseLeft}, + expected: MouseClickMsg{x: 222, y: 222, button: MouseLeft}, }, // Simple. { name: "left", buf: encode(0b0000_0000, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "left in motion", buf: encode(0b0010_0000, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "middle", buf: encode(0b0000_0001, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseMiddle}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseMiddle}, }, { name: "middle in motion", buf: encode(0b0010_0001, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseMiddle}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseMiddle}, }, { name: "right", buf: encode(0b0000_0010, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseRight}, }, { name: "right in motion", buf: encode(0b0010_0010, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseRight}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseRight}, }, { name: "motion", buf: encode(0b0010_0011, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseNone}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseNone}, }, { name: "wheel up", buf: encode(0b0100_0000, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, }, { name: "wheel down", buf: encode(0b0100_0001, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelDown}, }, { name: "wheel left", buf: encode(0b0100_0010, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelLeft}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelLeft}, }, { name: "wheel right", buf: encode(0b0100_0011, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelRight}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelRight}, }, { name: "release", buf: encode(0b0000_0011, 32, 16), - expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseNone}, + expected: MouseReleaseMsg{x: 32, y: 16, button: MouseNone}, }, { name: "backward", buf: encode(0b1000_0000, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseBackward}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseBackward}, }, { name: "forward", buf: encode(0b1000_0001, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseForward}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseForward}, }, { name: "button 10", buf: encode(0b1000_0010, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseExtra1}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseExtra1}, }, { name: "button 11", buf: encode(0b1000_0011, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseExtra2}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseExtra2}, }, // Combinations. { name: "alt+right", buf: encode(0b0000_1010, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, }, { name: "ctrl+right", buf: encode(0b0001_0010, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, }, { name: "left in motion", buf: encode(0b0010_0000, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "alt+right in motion", buf: encode(0b0010_1010, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, + expected: MouseMotionMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, }, { name: "ctrl+right in motion", buf: encode(0b0011_0010, 32, 16), - expected: MouseMotionMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, + expected: MouseMotionMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, }, { name: "ctrl+alt+right", buf: encode(0b0001_1010, 32, 16), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseRight}, }, { name: "ctrl+wheel up", buf: encode(0b0101_0000, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelUp}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModCtrl, button: MouseWheelUp}, }, { name: "alt+wheel down", buf: encode(0b0100_1001, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt, button: MouseWheelDown}, }, { name: "ctrl+alt+wheel down", buf: encode(0b0101_1001, 32, 16), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseWheelDown}, }, // Overflow position. { name: "overflow position", buf: encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1. - expected: MouseMotionMsg{X: -6, Y: -33, Button: MouseLeft}, + expected: MouseMotionMsg{x: -6, y: -33, button: MouseLeft}, }, } @@ -326,134 +326,134 @@ func TestParseSGRMouseEvent(t *testing.T) { { name: "zero position", buf: encode(0, 0, 0, false), - expected: MouseClickMsg{X: 0, Y: 0, Button: MouseLeft}, + expected: MouseClickMsg{x: 0, y: 0, button: MouseLeft}, }, { name: "225 position", buf: encode(0, 225, 225, false), - expected: MouseClickMsg{X: 225, Y: 225, Button: MouseLeft}, + expected: MouseClickMsg{x: 225, y: 225, button: MouseLeft}, }, // Simple. { name: "left", buf: encode(0, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "left in motion", buf: encode(32, 32, 16, false), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "left", buf: encode(0, 32, 16, true), - expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseLeft}, + expected: MouseReleaseMsg{x: 32, y: 16, button: MouseLeft}, }, { name: "middle", buf: encode(1, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseMiddle}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseMiddle}, }, { name: "middle in motion", buf: encode(33, 32, 16, false), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseMiddle}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseMiddle}, }, { name: "middle", buf: encode(1, 32, 16, true), - expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseMiddle}, + expected: MouseReleaseMsg{x: 32, y: 16, button: MouseMiddle}, }, { name: "right", buf: encode(2, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseRight}, }, { name: "right", buf: encode(2, 32, 16, true), - expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseRight}, + expected: MouseReleaseMsg{x: 32, y: 16, button: MouseRight}, }, { name: "motion", buf: encode(35, 32, 16, false), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseNone}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseNone}, }, { name: "wheel up", buf: encode(64, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, }, { name: "wheel down", buf: encode(65, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelDown}, }, { name: "wheel left", buf: encode(66, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelLeft}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelLeft}, }, { name: "wheel right", buf: encode(67, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelRight}, + expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelRight}, }, { name: "backward", buf: encode(128, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseBackward}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseBackward}, }, { name: "backward in motion", buf: encode(160, 32, 16, false), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseBackward}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseBackward}, }, { name: "forward", buf: encode(129, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Button: MouseForward}, + expected: MouseClickMsg{x: 32, y: 16, button: MouseForward}, }, { name: "forward in motion", buf: encode(161, 32, 16, false), - expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseForward}, + expected: MouseMotionMsg{x: 32, y: 16, button: MouseForward}, }, // Combinations. { name: "alt+right", buf: encode(10, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, }, { name: "ctrl+right", buf: encode(18, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, }, { name: "ctrl+alt+right", buf: encode(26, 32, 16, false), - expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight}, + expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseRight}, }, { name: "alt+wheel", buf: encode(73, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt, button: MouseWheelDown}, }, { name: "ctrl+wheel", buf: encode(81, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModCtrl, button: MouseWheelDown}, }, { name: "ctrl+alt+wheel", buf: encode(89, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseWheelDown}, }, { name: "ctrl+alt+shift+wheel", buf: encode(93, 32, 16, false), - expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModShift | ModCtrl, Button: MouseWheelDown}, + expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModShift | ModCtrl, button: MouseWheelDown}, }, } diff --git a/parse.go b/parse.go index 786d5fba71..c3d482deb9 100644 --- a/parse.go +++ b/parse.go @@ -108,7 +108,7 @@ func parseSequence(buf []byte) (n int, msg Msg) { case ansi.ESC: if len(buf) == 1 { // Escape key - return 1, KeyPressMsg{Type: KeyEscape} + return 1, KeyPressMsg{typ: KeyEscape} } switch b := buf[1]; b { @@ -125,13 +125,13 @@ func parseSequence(buf []byte) (n int, msg Msg) { default: n, e := parseSequence(buf[1:]) if k, ok := e.(KeyPressMsg); ok { - k.Mod |= ModAlt + k.mod |= ModAlt return n + 1, k } // Not a key sequence, nor an alt modified key sequence. In that // case, just report a single escape key. - return 1, KeyPressMsg{Type: KeyEscape} + return 1, KeyPressMsg{typ: KeyEscape} } case ansi.SS3: return parseSs3(buf) @@ -150,7 +150,7 @@ func parseSequence(buf []byte) (n int, msg Msg) { // C1 control code // UTF-8 never starts with a C1 control code // Encode these as Ctrl+Alt+ - return 1, KeyPressMsg{Runes: []rune{rune(b) - 0x40}, Mod: ModCtrl | ModAlt} + return 1, KeyPressMsg{runes: []rune{rune(b) - 0x40}, mod: ModCtrl | ModAlt} } return parseUtf8(buf) } @@ -159,7 +159,7 @@ func parseSequence(buf []byte) (n int, msg Msg) { func parseCsi(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+[ key - return 2, KeyPressMsg{Runes: []rune{rune(b[1])}, Mod: ModAlt} + return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} } var csi ansi.CsiSequence @@ -223,7 +223,7 @@ func parseCsi(b []byte) (int, Msg) { if b[i-1] == '$' { n, ev := parseCsi(append(b[:i-1], '~')) if k, ok := ev.(KeyPressMsg); ok { - k.Mod |= ModShift + k.mod |= ModShift return n, k } } @@ -280,7 +280,7 @@ func parseCsi(b []byte) (int, Msg) { // // For a non ambiguous cursor position report, use // [ansi.RequestExtendedCursorPosition] (DECXCPR) instead. - return i, multiMsg{KeyPressMsg{Type: KeyF3, Mod: KeyMod(csi.Param(1) - 1)}, m} + return i, multiMsg{KeyPressMsg{typ: KeyF3, mod: KeyMod(csi.Param(1) - 1)}, m} } return i, m @@ -296,23 +296,23 @@ func parseCsi(b []byte) (int, Msg) { var k KeyPressMsg switch cmd { case 'a', 'b', 'c', 'd': - k = KeyPressMsg{Type: KeyUp + KeyType(cmd-'a'), Mod: ModShift} + k = KeyPressMsg{typ: KeyUp + KeyType(cmd-'a'), mod: ModShift} case 'A', 'B', 'C', 'D': - k = KeyPressMsg{Type: KeyUp + KeyType(cmd-'A')} + k = KeyPressMsg{typ: KeyUp + KeyType(cmd-'A')} case 'E': - k = KeyPressMsg{Type: KeyBegin} + k = KeyPressMsg{typ: KeyBegin} case 'F': - k = KeyPressMsg{Type: KeyEnd} + k = KeyPressMsg{typ: KeyEnd} case 'H': - k = KeyPressMsg{Type: KeyHome} + k = KeyPressMsg{typ: KeyHome} case 'P', 'Q', 'R', 'S': - k = KeyPressMsg{Type: KeyF1 + KeyType(cmd-'P')} + k = KeyPressMsg{typ: KeyF1 + KeyType(cmd-'P')} case 'Z': - k = KeyPressMsg{Type: KeyTab, Mod: ModShift} + k = KeyPressMsg{typ: KeyTab, mod: ModShift} } if paramsLen > 1 && csi.Param(0) == 1 && csi.Param(1) != -1 { // CSI 1 ; A - k.Mod |= KeyMod(csi.Param(1) - 1) + k.mod |= KeyMod(csi.Param(1) - 1) } return i, k case 'M': @@ -392,51 +392,51 @@ func parseCsi(b []byte) (int, Msg) { switch param { case 1: if flags&_FlagFind != 0 { - k = KeyPressMsg{Type: KeyFind} + k = KeyPressMsg{typ: KeyFind} } else { - k = KeyPressMsg{Type: KeyHome} + k = KeyPressMsg{typ: KeyHome} } case 2: - k = KeyPressMsg{Type: KeyInsert} + k = KeyPressMsg{typ: KeyInsert} case 3: - k = KeyPressMsg{Type: KeyDelete} + k = KeyPressMsg{typ: KeyDelete} case 4: if flags&_FlagSelect != 0 { - k = KeyPressMsg{Type: KeySelect} + k = KeyPressMsg{typ: KeySelect} } else { - k = KeyPressMsg{Type: KeyEnd} + k = KeyPressMsg{typ: KeyEnd} } case 5: - k = KeyPressMsg{Type: KeyPgUp} + k = KeyPressMsg{typ: KeyPgUp} case 6: - k = KeyPressMsg{Type: KeyPgDown} + k = KeyPressMsg{typ: KeyPgDown} case 7: - k = KeyPressMsg{Type: KeyHome} + k = KeyPressMsg{typ: KeyHome} case 8: - k = KeyPressMsg{Type: KeyEnd} + k = KeyPressMsg{typ: KeyEnd} case 11, 12, 13, 14, 15: - k = KeyPressMsg{Type: KeyF1 + KeyType(param-11)} + k = KeyPressMsg{typ: KeyF1 + KeyType(param-11)} case 17, 18, 19, 20, 21: - k = KeyPressMsg{Type: KeyF6 + KeyType(param-17)} + k = KeyPressMsg{typ: KeyF6 + KeyType(param-17)} case 23, 24, 25, 26: - k = KeyPressMsg{Type: KeyF11 + KeyType(param-23)} + k = KeyPressMsg{typ: KeyF11 + KeyType(param-23)} case 28, 29: - k = KeyPressMsg{Type: KeyF15 + KeyType(param-28)} + k = KeyPressMsg{typ: KeyF15 + KeyType(param-28)} case 31, 32, 33, 34: - k = KeyPressMsg{Type: KeyF17 + KeyType(param-31)} + k = KeyPressMsg{typ: KeyF17 + KeyType(param-31)} } // modifiers if paramsLen > 1 && csi.Param(1) != -1 { - k.Mod |= KeyMod(csi.Param(1) - 1) + k.mod |= KeyMod(csi.Param(1) - 1) } // Handle URxvt weird keys switch cmd { case '^': - k.Mod |= ModCtrl + k.mod |= ModCtrl case '@': - k.Mod |= ModCtrl | ModShift + k.mod |= ModCtrl | ModShift } return i, k @@ -450,7 +450,7 @@ func parseCsi(b []byte) (int, Msg) { func parseSs3(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+O key - return 2, KeyPressMsg{Runes: []rune{rune(b[1])}, Mod: ModAlt} + return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} } var i int @@ -482,30 +482,30 @@ func parseSs3(b []byte) (int, Msg) { var k KeyPressMsg switch gl { case 'a', 'b', 'c', 'd': - k = KeyPressMsg{Type: KeyUp + KeyType(gl-'a'), Mod: ModCtrl} + k = KeyPressMsg{typ: KeyUp + KeyType(gl-'a'), mod: ModCtrl} case 'A', 'B', 'C', 'D': - k = KeyPressMsg{Type: KeyUp + KeyType(gl-'A')} + k = KeyPressMsg{typ: KeyUp + KeyType(gl-'A')} case 'E': - k = KeyPressMsg{Type: KeyBegin} + k = KeyPressMsg{typ: KeyBegin} case 'F': - k = KeyPressMsg{Type: KeyEnd} + k = KeyPressMsg{typ: KeyEnd} case 'H': - k = KeyPressMsg{Type: KeyHome} + k = KeyPressMsg{typ: KeyHome} case 'P', 'Q', 'R', 'S': - k = KeyPressMsg{Type: KeyF1 + KeyType(gl-'P')} + k = KeyPressMsg{typ: KeyF1 + KeyType(gl-'P')} case 'M': - k = KeyPressMsg{Type: KeyKpEnter} + k = KeyPressMsg{typ: KeyKpEnter} case 'X': - k = KeyPressMsg{Type: KeyKpEqual} + k = KeyPressMsg{typ: KeyKpEqual} case 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y': - k = KeyPressMsg{Type: KeyKpMultiply + KeyType(gl-'j')} + k = KeyPressMsg{typ: KeyKpMultiply + KeyType(gl-'j')} default: return i, UnknownMsg(b[:i]) } // Handle weird SS3 Func if mod > 0 { - k.Mod |= KeyMod(mod - 1) + k.mod |= KeyMod(mod - 1) } return i, k @@ -514,7 +514,7 @@ func parseSs3(b []byte) (int, Msg) { func parseOsc(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+] key - return 2, KeyPressMsg{Runes: []rune{rune(b[1])}, Mod: ModAlt} + return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} } var i int @@ -639,7 +639,7 @@ func parseStTerminated(intro8, intro7 byte) func([]byte) (int, Msg) { func parseDcs(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+P key - return 2, KeyPressMsg{Runes: []rune{rune(b[1])}, Mod: ModAlt} + return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} } var params [16]int @@ -751,7 +751,7 @@ func parseDcs(b []byte) (int, Msg) { func parseApc(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+_ key - return 2, KeyPressMsg{Runes: []rune{rune(b[1])}, Mod: ModAlt} + return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} } // APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f) @@ -769,7 +769,7 @@ func parseUtf8(b []byte) (int, Msg) { return 1, parseControl(c) } else if c > ansi.US && c < ansi.DEL { // ASCII printable characters - return 1, KeyPressMsg{Runes: []rune{rune(c)}} + return 1, KeyPressMsg{runes: []rune{rune(c)}} } if r, _ := utf8.DecodeRune(b); r == utf8.RuneError { @@ -777,46 +777,46 @@ func parseUtf8(b []byte) (int, Msg) { } cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) - return len(cluster), KeyPressMsg{Runes: []rune(string(cluster))} + return len(cluster), KeyPressMsg{runes: []rune(string(cluster))} } func parseControl(b byte) Msg { switch b { case ansi.NUL: if flags&_FlagCtrlAt != 0 { - return KeyPressMsg{Runes: []rune{'@'}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{'@'}, mod: ModCtrl} } - return KeyPressMsg{Runes: []rune{' '}, Type: KeySpace, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl} case ansi.BS: - return KeyPressMsg{Runes: []rune{'h'}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{'h'}, mod: ModCtrl} case ansi.HT: if flags&_FlagCtrlI != 0 { - return KeyPressMsg{Runes: []rune{'i'}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{'i'}, mod: ModCtrl} } - return KeyPressMsg{Type: KeyTab} + return KeyPressMsg{typ: KeyTab} case ansi.CR: if flags&_FlagCtrlM != 0 { - return KeyPressMsg{Runes: []rune{'m'}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{'m'}, mod: ModCtrl} } - return KeyPressMsg{Type: KeyEnter} + return KeyPressMsg{typ: KeyEnter} case ansi.ESC: if flags&_FlagCtrlOpenBracket != 0 { - return KeyPressMsg{Runes: []rune{'['}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{'['}, mod: ModCtrl} } - return KeyPressMsg{Type: KeyEscape} + return KeyPressMsg{typ: KeyEscape} case ansi.DEL: if flags&_FlagBackspace != 0 { - return KeyPressMsg{Type: KeyDelete} + return KeyPressMsg{typ: KeyDelete} } - return KeyPressMsg{Type: KeyBackspace} + return KeyPressMsg{typ: KeyBackspace} case ansi.SP: - return KeyPressMsg{Type: KeySpace, Runes: []rune{' '}} + return KeyPressMsg{typ: KeySpace, runes: []rune{' '}} default: if b >= ansi.SOH && b <= ansi.SUB { // Use lower case letters for control codes - return KeyPressMsg{Runes: []rune{rune(b + 0x60)}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{rune(b + 0x60)}, mod: ModCtrl} } else if b >= ansi.FS && b <= ansi.US { - return KeyPressMsg{Runes: []rune{rune(b + 0x40)}, Mod: ModCtrl} + return KeyPressMsg{runes: []rune{rune(b + 0x40)}, mod: ModCtrl} } return UnknownMsg(b) } diff --git a/parse_test.go b/parse_test.go index 6e8e0b6170..7be9340452 100644 --- a/parse_test.go +++ b/parse_test.go @@ -9,14 +9,14 @@ import ( func TestParseSequence_Events(t *testing.T) { input := []byte("\x1b\x1b[Ztest\x00\x1b]10;rgb:1234/1234/1234\x07\x1b[27;2;27~\x1b[?1049;2$y") want := []Msg{ - KeyPressMsg{Type: KeyTab, Mod: ModShift | ModAlt}, - KeyPressMsg{Type: KeyRunes, Runes: []rune{'t'}}, - KeyPressMsg{Type: KeyRunes, Runes: []rune{'e'}}, - KeyPressMsg{Type: KeyRunes, Runes: []rune{'s'}}, - KeyPressMsg{Type: KeyRunes, Runes: []rune{'t'}}, - KeyPressMsg{Type: KeySpace, Runes: []rune{' '}, Mod: ModCtrl}, + KeyPressMsg{typ: KeyTab, mod: ModShift | ModAlt}, + KeyPressMsg{typ: KeyRunes, runes: []rune{'t'}}, + KeyPressMsg{typ: KeyRunes, runes: []rune{'e'}}, + KeyPressMsg{typ: KeyRunes, runes: []rune{'s'}}, + KeyPressMsg{typ: KeyRunes, runes: []rune{'t'}}, + KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl}, ForegroundColorMsg{color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff}}, - KeyPressMsg{Type: KeyEscape, Mod: ModShift}, + KeyPressMsg{typ: KeyEscape, mod: ModShift}, ReportModeMsg{Mode: 1049, Value: 2}, } for i := 0; len(input) != 0; i++ { diff --git a/table.go b/table.go index 678388cdd9..04e186ce6c 100644 --- a/table.go +++ b/table.go @@ -9,40 +9,40 @@ import ( // buildKeysTable builds a table of key sequences and their corresponding key // events based on the VT100/VT200, XTerm, and Urxvt terminal specs. // TODO: Use flags? -func buildKeysTable(flags int, term string) map[string]Key { - nul := Key{Runes: []rune{' '}, Type: KeySpace, Mod: ModCtrl} // ctrl+@ or ctrl+space +func buildKeysTable(flags int, term string) map[string]key { + nul := key{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl} // ctrl+@ or ctrl+space if flags&_FlagCtrlAt != 0 { - nul = Key{Runes: []rune{'@'}, Mod: ModCtrl} + nul = key{runes: []rune{'@'}, mod: ModCtrl} } - tab := Key{Type: KeyTab} // ctrl+i or tab + tab := key{typ: KeyTab} // ctrl+i or tab if flags&_FlagCtrlI != 0 { - tab = Key{Runes: []rune{'i'}, Mod: ModCtrl} + tab = key{runes: []rune{'i'}, mod: ModCtrl} } - enter := Key{Type: KeyEnter} // ctrl+m or enter + enter := key{typ: KeyEnter} // ctrl+m or enter if flags&_FlagCtrlM != 0 { - enter = Key{Runes: []rune{'m'}, Mod: ModCtrl} + enter = key{runes: []rune{'m'}, mod: ModCtrl} } - esc := Key{Type: KeyEscape} // ctrl+[ or escape + esc := key{typ: KeyEscape} // ctrl+[ or escape if flags&_FlagCtrlOpenBracket != 0 { - esc = Key{Runes: []rune{'['}, Mod: ModCtrl} // ctrl+[ or escape + esc = key{runes: []rune{'['}, mod: ModCtrl} // ctrl+[ or escape } - del := Key{Type: KeyBackspace} + del := key{typ: KeyBackspace} if flags&_FlagBackspace != 0 { - del.Type = KeyDelete + del.typ = KeyDelete } - find := Key{Type: KeyHome} + find := key{typ: KeyHome} if flags&_FlagFind != 0 { - find.Type = KeyFind + find.typ = KeyFind } - sel := Key{Type: KeyEnd} + sel := key{typ: KeyEnd} if flags&_FlagSelect != 0 { - sel.Type = KeySelect + sel.typ = KeySelect } // The following is a table of key sequences and their corresponding key @@ -53,158 +53,158 @@ func buildKeysTable(flags int, term string) map[string]Key { // // XXX: These keys may be overwritten by other options like XTerm or // Terminfo. - table := map[string]Key{ + table := map[string]key{ // C0 control characters string(byte(ansi.NUL)): nul, - string(byte(ansi.SOH)): {Runes: []rune{'a'}, Mod: ModCtrl}, - string(byte(ansi.STX)): {Runes: []rune{'b'}, Mod: ModCtrl}, - string(byte(ansi.ETX)): {Runes: []rune{'c'}, Mod: ModCtrl}, - string(byte(ansi.EOT)): {Runes: []rune{'d'}, Mod: ModCtrl}, - string(byte(ansi.ENQ)): {Runes: []rune{'e'}, Mod: ModCtrl}, - string(byte(ansi.ACK)): {Runes: []rune{'f'}, Mod: ModCtrl}, - string(byte(ansi.BEL)): {Runes: []rune{'g'}, Mod: ModCtrl}, - string(byte(ansi.BS)): {Runes: []rune{'h'}, Mod: ModCtrl}, + string(byte(ansi.SOH)): {runes: []rune{'a'}, mod: ModCtrl}, + string(byte(ansi.STX)): {runes: []rune{'b'}, mod: ModCtrl}, + string(byte(ansi.ETX)): {runes: []rune{'c'}, mod: ModCtrl}, + string(byte(ansi.EOT)): {runes: []rune{'d'}, mod: ModCtrl}, + string(byte(ansi.ENQ)): {runes: []rune{'e'}, mod: ModCtrl}, + string(byte(ansi.ACK)): {runes: []rune{'f'}, mod: ModCtrl}, + string(byte(ansi.BEL)): {runes: []rune{'g'}, mod: ModCtrl}, + string(byte(ansi.BS)): {runes: []rune{'h'}, mod: ModCtrl}, string(byte(ansi.HT)): tab, - string(byte(ansi.LF)): {Runes: []rune{'j'}, Mod: ModCtrl}, - string(byte(ansi.VT)): {Runes: []rune{'k'}, Mod: ModCtrl}, - string(byte(ansi.FF)): {Runes: []rune{'l'}, Mod: ModCtrl}, + string(byte(ansi.LF)): {runes: []rune{'j'}, mod: ModCtrl}, + string(byte(ansi.VT)): {runes: []rune{'k'}, mod: ModCtrl}, + string(byte(ansi.FF)): {runes: []rune{'l'}, mod: ModCtrl}, string(byte(ansi.CR)): enter, - string(byte(ansi.SO)): {Runes: []rune{'n'}, Mod: ModCtrl}, - string(byte(ansi.SI)): {Runes: []rune{'o'}, Mod: ModCtrl}, - string(byte(ansi.DLE)): {Runes: []rune{'p'}, Mod: ModCtrl}, - string(byte(ansi.DC1)): {Runes: []rune{'q'}, Mod: ModCtrl}, - string(byte(ansi.DC2)): {Runes: []rune{'r'}, Mod: ModCtrl}, - string(byte(ansi.DC3)): {Runes: []rune{'s'}, Mod: ModCtrl}, - string(byte(ansi.DC4)): {Runes: []rune{'t'}, Mod: ModCtrl}, - string(byte(ansi.NAK)): {Runes: []rune{'u'}, Mod: ModCtrl}, - string(byte(ansi.SYN)): {Runes: []rune{'v'}, Mod: ModCtrl}, - string(byte(ansi.ETB)): {Runes: []rune{'w'}, Mod: ModCtrl}, - string(byte(ansi.CAN)): {Runes: []rune{'x'}, Mod: ModCtrl}, - string(byte(ansi.EM)): {Runes: []rune{'y'}, Mod: ModCtrl}, - string(byte(ansi.SUB)): {Runes: []rune{'z'}, Mod: ModCtrl}, + string(byte(ansi.SO)): {runes: []rune{'n'}, mod: ModCtrl}, + string(byte(ansi.SI)): {runes: []rune{'o'}, mod: ModCtrl}, + string(byte(ansi.DLE)): {runes: []rune{'p'}, mod: ModCtrl}, + string(byte(ansi.DC1)): {runes: []rune{'q'}, mod: ModCtrl}, + string(byte(ansi.DC2)): {runes: []rune{'r'}, mod: ModCtrl}, + string(byte(ansi.DC3)): {runes: []rune{'s'}, mod: ModCtrl}, + string(byte(ansi.DC4)): {runes: []rune{'t'}, mod: ModCtrl}, + string(byte(ansi.NAK)): {runes: []rune{'u'}, mod: ModCtrl}, + string(byte(ansi.SYN)): {runes: []rune{'v'}, mod: ModCtrl}, + string(byte(ansi.ETB)): {runes: []rune{'w'}, mod: ModCtrl}, + string(byte(ansi.CAN)): {runes: []rune{'x'}, mod: ModCtrl}, + string(byte(ansi.EM)): {runes: []rune{'y'}, mod: ModCtrl}, + string(byte(ansi.SUB)): {runes: []rune{'z'}, mod: ModCtrl}, string(byte(ansi.ESC)): esc, - string(byte(ansi.FS)): {Runes: []rune{'\\'}, Mod: ModCtrl}, - string(byte(ansi.GS)): {Runes: []rune{']'}, Mod: ModCtrl}, - string(byte(ansi.RS)): {Runes: []rune{'^'}, Mod: ModCtrl}, - string(byte(ansi.US)): {Runes: []rune{'_'}, Mod: ModCtrl}, + string(byte(ansi.FS)): {runes: []rune{'\\'}, mod: ModCtrl}, + string(byte(ansi.GS)): {runes: []rune{']'}, mod: ModCtrl}, + string(byte(ansi.RS)): {runes: []rune{'^'}, mod: ModCtrl}, + string(byte(ansi.US)): {runes: []rune{'_'}, mod: ModCtrl}, // Special keys in G0 - string(byte(ansi.SP)): {Type: KeySpace, Runes: []rune{' '}}, + string(byte(ansi.SP)): {typ: KeySpace, runes: []rune{' '}}, string(byte(ansi.DEL)): del, // Special keys - "\x1b[Z": {Type: KeyTab, Mod: ModShift}, + "\x1b[Z": {typ: KeyTab, mod: ModShift}, "\x1b[1~": find, - "\x1b[2~": {Type: KeyInsert}, - "\x1b[3~": {Type: KeyDelete}, + "\x1b[2~": {typ: KeyInsert}, + "\x1b[3~": {typ: KeyDelete}, "\x1b[4~": sel, - "\x1b[5~": {Type: KeyPgUp}, - "\x1b[6~": {Type: KeyPgDown}, - "\x1b[7~": {Type: KeyHome}, - "\x1b[8~": {Type: KeyEnd}, + "\x1b[5~": {typ: KeyPgUp}, + "\x1b[6~": {typ: KeyPgDown}, + "\x1b[7~": {typ: KeyHome}, + "\x1b[8~": {typ: KeyEnd}, // Normal mode - "\x1b[A": {Type: KeyUp}, - "\x1b[B": {Type: KeyDown}, - "\x1b[C": {Type: KeyRight}, - "\x1b[D": {Type: KeyLeft}, - "\x1b[E": {Type: KeyBegin}, - "\x1b[F": {Type: KeyEnd}, - "\x1b[H": {Type: KeyHome}, - "\x1b[P": {Type: KeyF1}, - "\x1b[Q": {Type: KeyF2}, - "\x1b[R": {Type: KeyF3}, - "\x1b[S": {Type: KeyF4}, + "\x1b[A": {typ: KeyUp}, + "\x1b[B": {typ: KeyDown}, + "\x1b[C": {typ: KeyRight}, + "\x1b[D": {typ: KeyLeft}, + "\x1b[E": {typ: KeyBegin}, + "\x1b[F": {typ: KeyEnd}, + "\x1b[H": {typ: KeyHome}, + "\x1b[P": {typ: KeyF1}, + "\x1b[Q": {typ: KeyF2}, + "\x1b[R": {typ: KeyF3}, + "\x1b[S": {typ: KeyF4}, // Application Cursor Key Mode (DECCKM) - "\x1bOA": {Type: KeyUp}, - "\x1bOB": {Type: KeyDown}, - "\x1bOC": {Type: KeyRight}, - "\x1bOD": {Type: KeyLeft}, - "\x1bOE": {Type: KeyBegin}, - "\x1bOF": {Type: KeyEnd}, - "\x1bOH": {Type: KeyHome}, - "\x1bOP": {Type: KeyF1}, - "\x1bOQ": {Type: KeyF2}, - "\x1bOR": {Type: KeyF3}, - "\x1bOS": {Type: KeyF4}, + "\x1bOA": {typ: KeyUp}, + "\x1bOB": {typ: KeyDown}, + "\x1bOC": {typ: KeyRight}, + "\x1bOD": {typ: KeyLeft}, + "\x1bOE": {typ: KeyBegin}, + "\x1bOF": {typ: KeyEnd}, + "\x1bOH": {typ: KeyHome}, + "\x1bOP": {typ: KeyF1}, + "\x1bOQ": {typ: KeyF2}, + "\x1bOR": {typ: KeyF3}, + "\x1bOS": {typ: KeyF4}, // Keypad Application Mode (DECKPAM) - "\x1bOM": {Type: KeyKpEnter}, - "\x1bOX": {Type: KeyKpEqual}, - "\x1bOj": {Type: KeyKpMultiply}, - "\x1bOk": {Type: KeyKpPlus}, - "\x1bOl": {Type: KeyKpComma}, - "\x1bOm": {Type: KeyKpMinus}, - "\x1bOn": {Type: KeyKpDecimal}, - "\x1bOo": {Type: KeyKpDivide}, - "\x1bOp": {Type: KeyKp0}, - "\x1bOq": {Type: KeyKp1}, - "\x1bOr": {Type: KeyKp2}, - "\x1bOs": {Type: KeyKp3}, - "\x1bOt": {Type: KeyKp4}, - "\x1bOu": {Type: KeyKp5}, - "\x1bOv": {Type: KeyKp6}, - "\x1bOw": {Type: KeyKp7}, - "\x1bOx": {Type: KeyKp8}, - "\x1bOy": {Type: KeyKp9}, + "\x1bOM": {typ: KeyKpEnter}, + "\x1bOX": {typ: KeyKpEqual}, + "\x1bOj": {typ: KeyKpMultiply}, + "\x1bOk": {typ: KeyKpPlus}, + "\x1bOl": {typ: KeyKpComma}, + "\x1bOm": {typ: KeyKpMinus}, + "\x1bOn": {typ: KeyKpDecimal}, + "\x1bOo": {typ: KeyKpDivide}, + "\x1bOp": {typ: KeyKp0}, + "\x1bOq": {typ: KeyKp1}, + "\x1bOr": {typ: KeyKp2}, + "\x1bOs": {typ: KeyKp3}, + "\x1bOt": {typ: KeyKp4}, + "\x1bOu": {typ: KeyKp5}, + "\x1bOv": {typ: KeyKp6}, + "\x1bOw": {typ: KeyKp7}, + "\x1bOx": {typ: KeyKp8}, + "\x1bOy": {typ: KeyKp9}, // Function keys - "\x1b[11~": {Type: KeyF1}, - "\x1b[12~": {Type: KeyF2}, - "\x1b[13~": {Type: KeyF3}, - "\x1b[14~": {Type: KeyF4}, - "\x1b[15~": {Type: KeyF5}, - "\x1b[17~": {Type: KeyF6}, - "\x1b[18~": {Type: KeyF7}, - "\x1b[19~": {Type: KeyF8}, - "\x1b[20~": {Type: KeyF9}, - "\x1b[21~": {Type: KeyF10}, - "\x1b[23~": {Type: KeyF11}, - "\x1b[24~": {Type: KeyF12}, - "\x1b[25~": {Type: KeyF13}, - "\x1b[26~": {Type: KeyF14}, - "\x1b[28~": {Type: KeyF15}, - "\x1b[29~": {Type: KeyF16}, - "\x1b[31~": {Type: KeyF17}, - "\x1b[32~": {Type: KeyF18}, - "\x1b[33~": {Type: KeyF19}, - "\x1b[34~": {Type: KeyF20}, + "\x1b[11~": {typ: KeyF1}, + "\x1b[12~": {typ: KeyF2}, + "\x1b[13~": {typ: KeyF3}, + "\x1b[14~": {typ: KeyF4}, + "\x1b[15~": {typ: KeyF5}, + "\x1b[17~": {typ: KeyF6}, + "\x1b[18~": {typ: KeyF7}, + "\x1b[19~": {typ: KeyF8}, + "\x1b[20~": {typ: KeyF9}, + "\x1b[21~": {typ: KeyF10}, + "\x1b[23~": {typ: KeyF11}, + "\x1b[24~": {typ: KeyF12}, + "\x1b[25~": {typ: KeyF13}, + "\x1b[26~": {typ: KeyF14}, + "\x1b[28~": {typ: KeyF15}, + "\x1b[29~": {typ: KeyF16}, + "\x1b[31~": {typ: KeyF17}, + "\x1b[32~": {typ: KeyF18}, + "\x1b[33~": {typ: KeyF19}, + "\x1b[34~": {typ: KeyF20}, } // CSI ~ sequence keys - csiTildeKeys := map[string]Key{ - "1": find, "2": {Type: KeyInsert}, - "3": {Type: KeyDelete}, "4": sel, - "5": {Type: KeyPgUp}, "6": {Type: KeyPgDown}, - "7": {Type: KeyHome}, "8": {Type: KeyEnd}, + csiTildeKeys := map[string]key{ + "1": find, "2": {typ: KeyInsert}, + "3": {typ: KeyDelete}, "4": sel, + "5": {typ: KeyPgUp}, "6": {typ: KeyPgDown}, + "7": {typ: KeyHome}, "8": {typ: KeyEnd}, // There are no 9 and 10 keys - "11": {Type: KeyF1}, "12": {Type: KeyF2}, - "13": {Type: KeyF3}, "14": {Type: KeyF4}, - "15": {Type: KeyF5}, "17": {Type: KeyF6}, - "18": {Type: KeyF7}, "19": {Type: KeyF8}, - "20": {Type: KeyF9}, "21": {Type: KeyF10}, - "23": {Type: KeyF11}, "24": {Type: KeyF12}, - "25": {Type: KeyF13}, "26": {Type: KeyF14}, - "28": {Type: KeyF15}, "29": {Type: KeyF16}, - "31": {Type: KeyF17}, "32": {Type: KeyF18}, - "33": {Type: KeyF19}, "34": {Type: KeyF20}, + "11": {typ: KeyF1}, "12": {typ: KeyF2}, + "13": {typ: KeyF3}, "14": {typ: KeyF4}, + "15": {typ: KeyF5}, "17": {typ: KeyF6}, + "18": {typ: KeyF7}, "19": {typ: KeyF8}, + "20": {typ: KeyF9}, "21": {typ: KeyF10}, + "23": {typ: KeyF11}, "24": {typ: KeyF12}, + "25": {typ: KeyF13}, "26": {typ: KeyF14}, + "28": {typ: KeyF15}, "29": {typ: KeyF16}, + "31": {typ: KeyF17}, "32": {typ: KeyF18}, + "33": {typ: KeyF19}, "34": {typ: KeyF20}, } // URxvt keys // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes - table["\x1b[a"] = Key{Type: KeyUp, Mod: ModShift} - table["\x1b[b"] = Key{Type: KeyDown, Mod: ModShift} - table["\x1b[c"] = Key{Type: KeyRight, Mod: ModShift} - table["\x1b[d"] = Key{Type: KeyLeft, Mod: ModShift} - table["\x1bOa"] = Key{Type: KeyUp, Mod: ModCtrl} - table["\x1bOb"] = Key{Type: KeyDown, Mod: ModCtrl} - table["\x1bOc"] = Key{Type: KeyRight, Mod: ModCtrl} - table["\x1bOd"] = Key{Type: KeyLeft, Mod: ModCtrl} + table["\x1b[a"] = key{typ: KeyUp, mod: ModShift} + table["\x1b[b"] = key{typ: KeyDown, mod: ModShift} + table["\x1b[c"] = key{typ: KeyRight, mod: ModShift} + table["\x1b[d"] = key{typ: KeyLeft, mod: ModShift} + table["\x1bOa"] = key{typ: KeyUp, mod: ModCtrl} + table["\x1bOb"] = key{typ: KeyDown, mod: ModCtrl} + table["\x1bOc"] = key{typ: KeyRight, mod: ModCtrl} + table["\x1bOd"] = key{typ: KeyLeft, mod: ModCtrl} // TODO: invistigate if shift-ctrl arrow keys collide with DECCKM keys i.e. // "\x1bOA", "\x1bOB", "\x1bOC", "\x1bOD" @@ -213,13 +213,13 @@ func buildKeysTable(flags int, term string) map[string]Key { key := v // Normal (no modifier) already defined part of VT100/VT200 // Shift modifier - key.Mod = ModShift + key.mod = ModShift table["\x1b["+k+"$"] = key // Ctrl modifier - key.Mod = ModCtrl + key.mod = ModCtrl table["\x1b["+k+"^"] = key // Shift-Ctrl modifier - key.Mod = ModShift | ModCtrl + key.mod = ModShift | ModCtrl table["\x1b["+k+"@"] = key } @@ -232,54 +232,54 @@ func buildKeysTable(flags int, term string) map[string]Key { // different escapes like XTerm, or switch to a better terminal ¯\_(ツ)_/¯ // // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes - table["\x1b[23$"] = Key{Type: KeyF11, Mod: ModShift} - table["\x1b[24$"] = Key{Type: KeyF12, Mod: ModShift} - table["\x1b[25$"] = Key{Type: KeyF13, Mod: ModShift} - table["\x1b[26$"] = Key{Type: KeyF14, Mod: ModShift} - table["\x1b[28$"] = Key{Type: KeyF15, Mod: ModShift} - table["\x1b[29$"] = Key{Type: KeyF16, Mod: ModShift} - table["\x1b[31$"] = Key{Type: KeyF17, Mod: ModShift} - table["\x1b[32$"] = Key{Type: KeyF18, Mod: ModShift} - table["\x1b[33$"] = Key{Type: KeyF19, Mod: ModShift} - table["\x1b[34$"] = Key{Type: KeyF20, Mod: ModShift} - table["\x1b[11^"] = Key{Type: KeyF1, Mod: ModCtrl} - table["\x1b[12^"] = Key{Type: KeyF2, Mod: ModCtrl} - table["\x1b[13^"] = Key{Type: KeyF3, Mod: ModCtrl} - table["\x1b[14^"] = Key{Type: KeyF4, Mod: ModCtrl} - table["\x1b[15^"] = Key{Type: KeyF5, Mod: ModCtrl} - table["\x1b[17^"] = Key{Type: KeyF6, Mod: ModCtrl} - table["\x1b[18^"] = Key{Type: KeyF7, Mod: ModCtrl} - table["\x1b[19^"] = Key{Type: KeyF8, Mod: ModCtrl} - table["\x1b[20^"] = Key{Type: KeyF9, Mod: ModCtrl} - table["\x1b[21^"] = Key{Type: KeyF10, Mod: ModCtrl} - table["\x1b[23^"] = Key{Type: KeyF11, Mod: ModCtrl} - table["\x1b[24^"] = Key{Type: KeyF12, Mod: ModCtrl} - table["\x1b[25^"] = Key{Type: KeyF13, Mod: ModCtrl} - table["\x1b[26^"] = Key{Type: KeyF14, Mod: ModCtrl} - table["\x1b[28^"] = Key{Type: KeyF15, Mod: ModCtrl} - table["\x1b[29^"] = Key{Type: KeyF16, Mod: ModCtrl} - table["\x1b[31^"] = Key{Type: KeyF17, Mod: ModCtrl} - table["\x1b[32^"] = Key{Type: KeyF18, Mod: ModCtrl} - table["\x1b[33^"] = Key{Type: KeyF19, Mod: ModCtrl} - table["\x1b[34^"] = Key{Type: KeyF20, Mod: ModCtrl} - table["\x1b[23@"] = Key{Type: KeyF11, Mod: ModShift | ModCtrl} - table["\x1b[24@"] = Key{Type: KeyF12, Mod: ModShift | ModCtrl} - table["\x1b[25@"] = Key{Type: KeyF13, Mod: ModShift | ModCtrl} - table["\x1b[26@"] = Key{Type: KeyF14, Mod: ModShift | ModCtrl} - table["\x1b[28@"] = Key{Type: KeyF15, Mod: ModShift | ModCtrl} - table["\x1b[29@"] = Key{Type: KeyF16, Mod: ModShift | ModCtrl} - table["\x1b[31@"] = Key{Type: KeyF17, Mod: ModShift | ModCtrl} - table["\x1b[32@"] = Key{Type: KeyF18, Mod: ModShift | ModCtrl} - table["\x1b[33@"] = Key{Type: KeyF19, Mod: ModShift | ModCtrl} - table["\x1b[34@"] = Key{Type: KeyF20, Mod: ModShift | ModCtrl} + table["\x1b[23$"] = key{typ: KeyF11, mod: ModShift} + table["\x1b[24$"] = key{typ: KeyF12, mod: ModShift} + table["\x1b[25$"] = key{typ: KeyF13, mod: ModShift} + table["\x1b[26$"] = key{typ: KeyF14, mod: ModShift} + table["\x1b[28$"] = key{typ: KeyF15, mod: ModShift} + table["\x1b[29$"] = key{typ: KeyF16, mod: ModShift} + table["\x1b[31$"] = key{typ: KeyF17, mod: ModShift} + table["\x1b[32$"] = key{typ: KeyF18, mod: ModShift} + table["\x1b[33$"] = key{typ: KeyF19, mod: ModShift} + table["\x1b[34$"] = key{typ: KeyF20, mod: ModShift} + table["\x1b[11^"] = key{typ: KeyF1, mod: ModCtrl} + table["\x1b[12^"] = key{typ: KeyF2, mod: ModCtrl} + table["\x1b[13^"] = key{typ: KeyF3, mod: ModCtrl} + table["\x1b[14^"] = key{typ: KeyF4, mod: ModCtrl} + table["\x1b[15^"] = key{typ: KeyF5, mod: ModCtrl} + table["\x1b[17^"] = key{typ: KeyF6, mod: ModCtrl} + table["\x1b[18^"] = key{typ: KeyF7, mod: ModCtrl} + table["\x1b[19^"] = key{typ: KeyF8, mod: ModCtrl} + table["\x1b[20^"] = key{typ: KeyF9, mod: ModCtrl} + table["\x1b[21^"] = key{typ: KeyF10, mod: ModCtrl} + table["\x1b[23^"] = key{typ: KeyF11, mod: ModCtrl} + table["\x1b[24^"] = key{typ: KeyF12, mod: ModCtrl} + table["\x1b[25^"] = key{typ: KeyF13, mod: ModCtrl} + table["\x1b[26^"] = key{typ: KeyF14, mod: ModCtrl} + table["\x1b[28^"] = key{typ: KeyF15, mod: ModCtrl} + table["\x1b[29^"] = key{typ: KeyF16, mod: ModCtrl} + table["\x1b[31^"] = key{typ: KeyF17, mod: ModCtrl} + table["\x1b[32^"] = key{typ: KeyF18, mod: ModCtrl} + table["\x1b[33^"] = key{typ: KeyF19, mod: ModCtrl} + table["\x1b[34^"] = key{typ: KeyF20, mod: ModCtrl} + table["\x1b[23@"] = key{typ: KeyF11, mod: ModShift | ModCtrl} + table["\x1b[24@"] = key{typ: KeyF12, mod: ModShift | ModCtrl} + table["\x1b[25@"] = key{typ: KeyF13, mod: ModShift | ModCtrl} + table["\x1b[26@"] = key{typ: KeyF14, mod: ModShift | ModCtrl} + table["\x1b[28@"] = key{typ: KeyF15, mod: ModShift | ModCtrl} + table["\x1b[29@"] = key{typ: KeyF16, mod: ModShift | ModCtrl} + table["\x1b[31@"] = key{typ: KeyF17, mod: ModShift | ModCtrl} + table["\x1b[32@"] = key{typ: KeyF18, mod: ModShift | ModCtrl} + table["\x1b[33@"] = key{typ: KeyF19, mod: ModShift | ModCtrl} + table["\x1b[34@"] = key{typ: KeyF20, mod: ModShift | ModCtrl} // Register Alt + combinations // XXX: this must come after URxvt but before XTerm keys to register URxvt // keys with alt modifier - tmap := map[string]Key{} + tmap := map[string]key{} for seq, key := range table { key := key - key.Mod |= ModAlt + key.mod |= ModAlt tmap["\x1b"+seq] = key } for seq, key := range tmap { @@ -308,38 +308,38 @@ func buildKeysTable(flags int, term string) map[string]Key { } // SS3 keypad function keys - ss3FuncKeys := map[string]Key{ + ss3FuncKeys := map[string]key{ // These are defined in XTerm // Taken from Foot keymap.h and XTerm modifyOtherKeys // https://codeberg.org/dnkl/foot/src/branch/master/keymap.h - "M": {Type: KeyKpEnter}, "X": {Type: KeyKpEqual}, - "j": {Type: KeyKpMultiply}, "k": {Type: KeyKpPlus}, - "l": {Type: KeyKpComma}, "m": {Type: KeyKpMinus}, - "n": {Type: KeyKpDecimal}, "o": {Type: KeyKpDivide}, - "p": {Type: KeyKp0}, "q": {Type: KeyKp1}, - "r": {Type: KeyKp2}, "s": {Type: KeyKp3}, - "t": {Type: KeyKp4}, "u": {Type: KeyKp5}, - "v": {Type: KeyKp6}, "w": {Type: KeyKp7}, - "x": {Type: KeyKp8}, "y": {Type: KeyKp9}, + "M": {typ: KeyKpEnter}, "X": {typ: KeyKpEqual}, + "j": {typ: KeyKpMultiply}, "k": {typ: KeyKpPlus}, + "l": {typ: KeyKpComma}, "m": {typ: KeyKpMinus}, + "n": {typ: KeyKpDecimal}, "o": {typ: KeyKpDivide}, + "p": {typ: KeyKp0}, "q": {typ: KeyKp1}, + "r": {typ: KeyKp2}, "s": {typ: KeyKp3}, + "t": {typ: KeyKp4}, "u": {typ: KeyKp5}, + "v": {typ: KeyKp6}, "w": {typ: KeyKp7}, + "x": {typ: KeyKp8}, "y": {typ: KeyKp9}, } // XTerm keys - csiFuncKeys := map[string]Key{ - "A": {Type: KeyUp}, "B": {Type: KeyDown}, - "C": {Type: KeyRight}, "D": {Type: KeyLeft}, - "E": {Type: KeyBegin}, "F": {Type: KeyEnd}, - "H": {Type: KeyHome}, "P": {Type: KeyF1}, - "Q": {Type: KeyF2}, "R": {Type: KeyF3}, - "S": {Type: KeyF4}, + csiFuncKeys := map[string]key{ + "A": {typ: KeyUp}, "B": {typ: KeyDown}, + "C": {typ: KeyRight}, "D": {typ: KeyLeft}, + "E": {typ: KeyBegin}, "F": {typ: KeyEnd}, + "H": {typ: KeyHome}, "P": {typ: KeyF1}, + "Q": {typ: KeyF2}, "R": {typ: KeyF3}, + "S": {typ: KeyF4}, } // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys - modifyOtherKeys := map[int]Key{ - ansi.BS: {Type: KeyBackspace}, - ansi.HT: {Type: KeyTab}, - ansi.CR: {Type: KeyEnter}, - ansi.ESC: {Type: KeyEscape}, - ansi.DEL: {Type: KeyBackspace}, + modifyOtherKeys := map[int]key{ + ansi.BS: {typ: KeyBackspace}, + ansi.HT: {typ: KeyTab}, + ansi.CR: {typ: KeyEnter}, + ansi.ESC: {typ: KeyEscape}, + ansi.DEL: {typ: KeyBackspace}, } for _, m := range modifiers { @@ -351,21 +351,21 @@ func buildKeysTable(flags int, term string) map[string]Key { // Functions always have a leading 1 param seq := "\x1b[1;" + xtermMod + k key := v - key.Mod = m + key.mod = m table[seq] = key } // SS3 for k, v := range ss3FuncKeys { seq := "\x1bO" + xtermMod + k key := v - key.Mod = m + key.mod = m table[seq] = key } // CSI ; ~ for k, v := range csiTildeKeys { seq := "\x1b[" + k + ";" + xtermMod + "~" key := v - key.Mod = m + key.mod = m table[seq] = key } // CSI 27 ; ; ~ @@ -373,7 +373,7 @@ func buildKeysTable(flags int, term string) map[string]Key { code := strconv.Itoa(k) seq := "\x1b[27;" + xtermMod + ";" + code + "~" key := v - key.Mod = m + key.mod = m table[seq] = key } } diff --git a/terminfo.go b/terminfo.go index e4be0ddf3e..32a33d5bd1 100644 --- a/terminfo.go +++ b/terminfo.go @@ -6,8 +6,8 @@ import ( "github.com/xo/terminfo" ) -func buildTerminfoKeys(flags int, term string) map[string]Key { - table := make(map[string]Key) +func buildTerminfoKeys(flags int, term string) map[string]key { + table := make(map[string]key) ti, _ := terminfo.Load(term) if ti == nil { return table @@ -54,93 +54,93 @@ func buildTerminfoKeys(flags int, term string) map[string]Key { // // See https://man7.org/linux/man-pages/man5/terminfo.5.html // See https://github.com/mirror/ncurses/blob/master/include/Caps-ncurses -func defaultTerminfoKeys(flags int) map[string]Key { - keys := map[string]Key{ - "kcuu1": {Type: KeyUp}, - "kUP": {Type: KeyUp, Mod: ModShift}, - "kUP3": {Type: KeyUp, Mod: ModAlt}, - "kUP4": {Type: KeyUp, Mod: ModShift | ModAlt}, - "kUP5": {Type: KeyUp, Mod: ModCtrl}, - "kUP6": {Type: KeyUp, Mod: ModShift | ModCtrl}, - "kUP7": {Type: KeyUp, Mod: ModAlt | ModCtrl}, - "kUP8": {Type: KeyUp, Mod: ModShift | ModAlt | ModCtrl}, - "kcud1": {Type: KeyDown}, - "kDN": {Type: KeyDown, Mod: ModShift}, - "kDN3": {Type: KeyDown, Mod: ModAlt}, - "kDN4": {Type: KeyDown, Mod: ModShift | ModAlt}, - "kDN5": {Type: KeyDown, Mod: ModCtrl}, - "kDN7": {Type: KeyDown, Mod: ModAlt | ModCtrl}, - "kDN6": {Type: KeyDown, Mod: ModShift | ModCtrl}, - "kDN8": {Type: KeyDown, Mod: ModShift | ModAlt | ModCtrl}, - "kcub1": {Type: KeyLeft}, - "kLFT": {Type: KeyLeft, Mod: ModShift}, - "kLFT3": {Type: KeyLeft, Mod: ModAlt}, - "kLFT4": {Type: KeyLeft, Mod: ModShift | ModAlt}, - "kLFT5": {Type: KeyLeft, Mod: ModCtrl}, - "kLFT6": {Type: KeyLeft, Mod: ModShift | ModCtrl}, - "kLFT7": {Type: KeyLeft, Mod: ModAlt | ModCtrl}, - "kLFT8": {Type: KeyLeft, Mod: ModShift | ModAlt | ModCtrl}, - "kcuf1": {Type: KeyRight}, - "kRIT": {Type: KeyRight, Mod: ModShift}, - "kRIT3": {Type: KeyRight, Mod: ModAlt}, - "kRIT4": {Type: KeyRight, Mod: ModShift | ModAlt}, - "kRIT5": {Type: KeyRight, Mod: ModCtrl}, - "kRIT6": {Type: KeyRight, Mod: ModShift | ModCtrl}, - "kRIT7": {Type: KeyRight, Mod: ModAlt | ModCtrl}, - "kRIT8": {Type: KeyRight, Mod: ModShift | ModAlt | ModCtrl}, - "kich1": {Type: KeyInsert}, - "kIC": {Type: KeyInsert, Mod: ModShift}, - "kIC3": {Type: KeyInsert, Mod: ModAlt}, - "kIC4": {Type: KeyInsert, Mod: ModShift | ModAlt}, - "kIC5": {Type: KeyInsert, Mod: ModCtrl}, - "kIC6": {Type: KeyInsert, Mod: ModShift | ModCtrl}, - "kIC7": {Type: KeyInsert, Mod: ModAlt | ModCtrl}, - "kIC8": {Type: KeyInsert, Mod: ModShift | ModAlt | ModCtrl}, - "kdch1": {Type: KeyDelete}, - "kDC": {Type: KeyDelete, Mod: ModShift}, - "kDC3": {Type: KeyDelete, Mod: ModAlt}, - "kDC4": {Type: KeyDelete, Mod: ModShift | ModAlt}, - "kDC5": {Type: KeyDelete, Mod: ModCtrl}, - "kDC6": {Type: KeyDelete, Mod: ModShift | ModCtrl}, - "kDC7": {Type: KeyDelete, Mod: ModAlt | ModCtrl}, - "kDC8": {Type: KeyDelete, Mod: ModShift | ModAlt | ModCtrl}, - "khome": {Type: KeyHome}, - "kHOM": {Type: KeyHome, Mod: ModShift}, - "kHOM3": {Type: KeyHome, Mod: ModAlt}, - "kHOM4": {Type: KeyHome, Mod: ModShift | ModAlt}, - "kHOM5": {Type: KeyHome, Mod: ModCtrl}, - "kHOM6": {Type: KeyHome, Mod: ModShift | ModCtrl}, - "kHOM7": {Type: KeyHome, Mod: ModAlt | ModCtrl}, - "kHOM8": {Type: KeyHome, Mod: ModShift | ModAlt | ModCtrl}, - "kend": {Type: KeyEnd}, - "kEND": {Type: KeyEnd, Mod: ModShift}, - "kEND3": {Type: KeyEnd, Mod: ModAlt}, - "kEND4": {Type: KeyEnd, Mod: ModShift | ModAlt}, - "kEND5": {Type: KeyEnd, Mod: ModCtrl}, - "kEND6": {Type: KeyEnd, Mod: ModShift | ModCtrl}, - "kEND7": {Type: KeyEnd, Mod: ModAlt | ModCtrl}, - "kEND8": {Type: KeyEnd, Mod: ModShift | ModAlt | ModCtrl}, - "kpp": {Type: KeyPgUp}, - "kprv": {Type: KeyPgUp}, - "kPRV": {Type: KeyPgUp, Mod: ModShift}, - "kPRV3": {Type: KeyPgUp, Mod: ModAlt}, - "kPRV4": {Type: KeyPgUp, Mod: ModShift | ModAlt}, - "kPRV5": {Type: KeyPgUp, Mod: ModCtrl}, - "kPRV6": {Type: KeyPgUp, Mod: ModShift | ModCtrl}, - "kPRV7": {Type: KeyPgUp, Mod: ModAlt | ModCtrl}, - "kPRV8": {Type: KeyPgUp, Mod: ModShift | ModAlt | ModCtrl}, - "knp": {Type: KeyPgDown}, - "knxt": {Type: KeyPgDown}, - "kNXT": {Type: KeyPgDown, Mod: ModShift}, - "kNXT3": {Type: KeyPgDown, Mod: ModAlt}, - "kNXT4": {Type: KeyPgDown, Mod: ModShift | ModAlt}, - "kNXT5": {Type: KeyPgDown, Mod: ModCtrl}, - "kNXT6": {Type: KeyPgDown, Mod: ModShift | ModCtrl}, - "kNXT7": {Type: KeyPgDown, Mod: ModAlt | ModCtrl}, - "kNXT8": {Type: KeyPgDown, Mod: ModShift | ModAlt | ModCtrl}, +func defaultTerminfoKeys(flags int) map[string]key { + keys := map[string]key{ + "kcuu1": {typ: KeyUp}, + "kUP": {typ: KeyUp, mod: ModShift}, + "kUP3": {typ: KeyUp, mod: ModAlt}, + "kUP4": {typ: KeyUp, mod: ModShift | ModAlt}, + "kUP5": {typ: KeyUp, mod: ModCtrl}, + "kUP6": {typ: KeyUp, mod: ModShift | ModCtrl}, + "kUP7": {typ: KeyUp, mod: ModAlt | ModCtrl}, + "kUP8": {typ: KeyUp, mod: ModShift | ModAlt | ModCtrl}, + "kcud1": {typ: KeyDown}, + "kDN": {typ: KeyDown, mod: ModShift}, + "kDN3": {typ: KeyDown, mod: ModAlt}, + "kDN4": {typ: KeyDown, mod: ModShift | ModAlt}, + "kDN5": {typ: KeyDown, mod: ModCtrl}, + "kDN7": {typ: KeyDown, mod: ModAlt | ModCtrl}, + "kDN6": {typ: KeyDown, mod: ModShift | ModCtrl}, + "kDN8": {typ: KeyDown, mod: ModShift | ModAlt | ModCtrl}, + "kcub1": {typ: KeyLeft}, + "kLFT": {typ: KeyLeft, mod: ModShift}, + "kLFT3": {typ: KeyLeft, mod: ModAlt}, + "kLFT4": {typ: KeyLeft, mod: ModShift | ModAlt}, + "kLFT5": {typ: KeyLeft, mod: ModCtrl}, + "kLFT6": {typ: KeyLeft, mod: ModShift | ModCtrl}, + "kLFT7": {typ: KeyLeft, mod: ModAlt | ModCtrl}, + "kLFT8": {typ: KeyLeft, mod: ModShift | ModAlt | ModCtrl}, + "kcuf1": {typ: KeyRight}, + "kRIT": {typ: KeyRight, mod: ModShift}, + "kRIT3": {typ: KeyRight, mod: ModAlt}, + "kRIT4": {typ: KeyRight, mod: ModShift | ModAlt}, + "kRIT5": {typ: KeyRight, mod: ModCtrl}, + "kRIT6": {typ: KeyRight, mod: ModShift | ModCtrl}, + "kRIT7": {typ: KeyRight, mod: ModAlt | ModCtrl}, + "kRIT8": {typ: KeyRight, mod: ModShift | ModAlt | ModCtrl}, + "kich1": {typ: KeyInsert}, + "kIC": {typ: KeyInsert, mod: ModShift}, + "kIC3": {typ: KeyInsert, mod: ModAlt}, + "kIC4": {typ: KeyInsert, mod: ModShift | ModAlt}, + "kIC5": {typ: KeyInsert, mod: ModCtrl}, + "kIC6": {typ: KeyInsert, mod: ModShift | ModCtrl}, + "kIC7": {typ: KeyInsert, mod: ModAlt | ModCtrl}, + "kIC8": {typ: KeyInsert, mod: ModShift | ModAlt | ModCtrl}, + "kdch1": {typ: KeyDelete}, + "kDC": {typ: KeyDelete, mod: ModShift}, + "kDC3": {typ: KeyDelete, mod: ModAlt}, + "kDC4": {typ: KeyDelete, mod: ModShift | ModAlt}, + "kDC5": {typ: KeyDelete, mod: ModCtrl}, + "kDC6": {typ: KeyDelete, mod: ModShift | ModCtrl}, + "kDC7": {typ: KeyDelete, mod: ModAlt | ModCtrl}, + "kDC8": {typ: KeyDelete, mod: ModShift | ModAlt | ModCtrl}, + "khome": {typ: KeyHome}, + "kHOM": {typ: KeyHome, mod: ModShift}, + "kHOM3": {typ: KeyHome, mod: ModAlt}, + "kHOM4": {typ: KeyHome, mod: ModShift | ModAlt}, + "kHOM5": {typ: KeyHome, mod: ModCtrl}, + "kHOM6": {typ: KeyHome, mod: ModShift | ModCtrl}, + "kHOM7": {typ: KeyHome, mod: ModAlt | ModCtrl}, + "kHOM8": {typ: KeyHome, mod: ModShift | ModAlt | ModCtrl}, + "kend": {typ: KeyEnd}, + "kEND": {typ: KeyEnd, mod: ModShift}, + "kEND3": {typ: KeyEnd, mod: ModAlt}, + "kEND4": {typ: KeyEnd, mod: ModShift | ModAlt}, + "kEND5": {typ: KeyEnd, mod: ModCtrl}, + "kEND6": {typ: KeyEnd, mod: ModShift | ModCtrl}, + "kEND7": {typ: KeyEnd, mod: ModAlt | ModCtrl}, + "kEND8": {typ: KeyEnd, mod: ModShift | ModAlt | ModCtrl}, + "kpp": {typ: KeyPgUp}, + "kprv": {typ: KeyPgUp}, + "kPRV": {typ: KeyPgUp, mod: ModShift}, + "kPRV3": {typ: KeyPgUp, mod: ModAlt}, + "kPRV4": {typ: KeyPgUp, mod: ModShift | ModAlt}, + "kPRV5": {typ: KeyPgUp, mod: ModCtrl}, + "kPRV6": {typ: KeyPgUp, mod: ModShift | ModCtrl}, + "kPRV7": {typ: KeyPgUp, mod: ModAlt | ModCtrl}, + "kPRV8": {typ: KeyPgUp, mod: ModShift | ModAlt | ModCtrl}, + "knp": {typ: KeyPgDown}, + "knxt": {typ: KeyPgDown}, + "kNXT": {typ: KeyPgDown, mod: ModShift}, + "kNXT3": {typ: KeyPgDown, mod: ModAlt}, + "kNXT4": {typ: KeyPgDown, mod: ModShift | ModAlt}, + "kNXT5": {typ: KeyPgDown, mod: ModCtrl}, + "kNXT6": {typ: KeyPgDown, mod: ModShift | ModCtrl}, + "kNXT7": {typ: KeyPgDown, mod: ModAlt | ModCtrl}, + "kNXT8": {typ: KeyPgDown, mod: ModShift | ModAlt | ModCtrl}, - "kbs": {Type: KeyBackspace}, - "kcbt": {Type: KeyTab, Mod: ModShift}, + "kbs": {typ: KeyBackspace}, + "kcbt": {typ: KeyTab, mod: ModShift}, // Function keys // This only includes the first 12 function keys. The rest are treated @@ -152,125 +152,125 @@ func defaultTerminfoKeys(flags int) map[string]Key { // See https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyFunctionKeys // See https://invisible-island.net/xterm/terminfo.html - "kf1": {Type: KeyF1}, - "kf2": {Type: KeyF2}, - "kf3": {Type: KeyF3}, - "kf4": {Type: KeyF4}, - "kf5": {Type: KeyF5}, - "kf6": {Type: KeyF6}, - "kf7": {Type: KeyF7}, - "kf8": {Type: KeyF8}, - "kf9": {Type: KeyF9}, - "kf10": {Type: KeyF10}, - "kf11": {Type: KeyF11}, - "kf12": {Type: KeyF12}, - "kf13": {Type: KeyF1, Mod: ModShift}, - "kf14": {Type: KeyF2, Mod: ModShift}, - "kf15": {Type: KeyF3, Mod: ModShift}, - "kf16": {Type: KeyF4, Mod: ModShift}, - "kf17": {Type: KeyF5, Mod: ModShift}, - "kf18": {Type: KeyF6, Mod: ModShift}, - "kf19": {Type: KeyF7, Mod: ModShift}, - "kf20": {Type: KeyF8, Mod: ModShift}, - "kf21": {Type: KeyF9, Mod: ModShift}, - "kf22": {Type: KeyF10, Mod: ModShift}, - "kf23": {Type: KeyF11, Mod: ModShift}, - "kf24": {Type: KeyF12, Mod: ModShift}, - "kf25": {Type: KeyF1, Mod: ModCtrl}, - "kf26": {Type: KeyF2, Mod: ModCtrl}, - "kf27": {Type: KeyF3, Mod: ModCtrl}, - "kf28": {Type: KeyF4, Mod: ModCtrl}, - "kf29": {Type: KeyF5, Mod: ModCtrl}, - "kf30": {Type: KeyF6, Mod: ModCtrl}, - "kf31": {Type: KeyF7, Mod: ModCtrl}, - "kf32": {Type: KeyF8, Mod: ModCtrl}, - "kf33": {Type: KeyF9, Mod: ModCtrl}, - "kf34": {Type: KeyF10, Mod: ModCtrl}, - "kf35": {Type: KeyF11, Mod: ModCtrl}, - "kf36": {Type: KeyF12, Mod: ModCtrl}, - "kf37": {Type: KeyF1, Mod: ModShift | ModCtrl}, - "kf38": {Type: KeyF2, Mod: ModShift | ModCtrl}, - "kf39": {Type: KeyF3, Mod: ModShift | ModCtrl}, - "kf40": {Type: KeyF4, Mod: ModShift | ModCtrl}, - "kf41": {Type: KeyF5, Mod: ModShift | ModCtrl}, - "kf42": {Type: KeyF6, Mod: ModShift | ModCtrl}, - "kf43": {Type: KeyF7, Mod: ModShift | ModCtrl}, - "kf44": {Type: KeyF8, Mod: ModShift | ModCtrl}, - "kf45": {Type: KeyF9, Mod: ModShift | ModCtrl}, - "kf46": {Type: KeyF10, Mod: ModShift | ModCtrl}, - "kf47": {Type: KeyF11, Mod: ModShift | ModCtrl}, - "kf48": {Type: KeyF12, Mod: ModShift | ModCtrl}, - "kf49": {Type: KeyF1, Mod: ModAlt}, - "kf50": {Type: KeyF2, Mod: ModAlt}, - "kf51": {Type: KeyF3, Mod: ModAlt}, - "kf52": {Type: KeyF4, Mod: ModAlt}, - "kf53": {Type: KeyF5, Mod: ModAlt}, - "kf54": {Type: KeyF6, Mod: ModAlt}, - "kf55": {Type: KeyF7, Mod: ModAlt}, - "kf56": {Type: KeyF8, Mod: ModAlt}, - "kf57": {Type: KeyF9, Mod: ModAlt}, - "kf58": {Type: KeyF10, Mod: ModAlt}, - "kf59": {Type: KeyF11, Mod: ModAlt}, - "kf60": {Type: KeyF12, Mod: ModAlt}, - "kf61": {Type: KeyF1, Mod: ModShift | ModAlt}, - "kf62": {Type: KeyF2, Mod: ModShift | ModAlt}, - "kf63": {Type: KeyF3, Mod: ModShift | ModAlt}, + "kf1": {typ: KeyF1}, + "kf2": {typ: KeyF2}, + "kf3": {typ: KeyF3}, + "kf4": {typ: KeyF4}, + "kf5": {typ: KeyF5}, + "kf6": {typ: KeyF6}, + "kf7": {typ: KeyF7}, + "kf8": {typ: KeyF8}, + "kf9": {typ: KeyF9}, + "kf10": {typ: KeyF10}, + "kf11": {typ: KeyF11}, + "kf12": {typ: KeyF12}, + "kf13": {typ: KeyF1, mod: ModShift}, + "kf14": {typ: KeyF2, mod: ModShift}, + "kf15": {typ: KeyF3, mod: ModShift}, + "kf16": {typ: KeyF4, mod: ModShift}, + "kf17": {typ: KeyF5, mod: ModShift}, + "kf18": {typ: KeyF6, mod: ModShift}, + "kf19": {typ: KeyF7, mod: ModShift}, + "kf20": {typ: KeyF8, mod: ModShift}, + "kf21": {typ: KeyF9, mod: ModShift}, + "kf22": {typ: KeyF10, mod: ModShift}, + "kf23": {typ: KeyF11, mod: ModShift}, + "kf24": {typ: KeyF12, mod: ModShift}, + "kf25": {typ: KeyF1, mod: ModCtrl}, + "kf26": {typ: KeyF2, mod: ModCtrl}, + "kf27": {typ: KeyF3, mod: ModCtrl}, + "kf28": {typ: KeyF4, mod: ModCtrl}, + "kf29": {typ: KeyF5, mod: ModCtrl}, + "kf30": {typ: KeyF6, mod: ModCtrl}, + "kf31": {typ: KeyF7, mod: ModCtrl}, + "kf32": {typ: KeyF8, mod: ModCtrl}, + "kf33": {typ: KeyF9, mod: ModCtrl}, + "kf34": {typ: KeyF10, mod: ModCtrl}, + "kf35": {typ: KeyF11, mod: ModCtrl}, + "kf36": {typ: KeyF12, mod: ModCtrl}, + "kf37": {typ: KeyF1, mod: ModShift | ModCtrl}, + "kf38": {typ: KeyF2, mod: ModShift | ModCtrl}, + "kf39": {typ: KeyF3, mod: ModShift | ModCtrl}, + "kf40": {typ: KeyF4, mod: ModShift | ModCtrl}, + "kf41": {typ: KeyF5, mod: ModShift | ModCtrl}, + "kf42": {typ: KeyF6, mod: ModShift | ModCtrl}, + "kf43": {typ: KeyF7, mod: ModShift | ModCtrl}, + "kf44": {typ: KeyF8, mod: ModShift | ModCtrl}, + "kf45": {typ: KeyF9, mod: ModShift | ModCtrl}, + "kf46": {typ: KeyF10, mod: ModShift | ModCtrl}, + "kf47": {typ: KeyF11, mod: ModShift | ModCtrl}, + "kf48": {typ: KeyF12, mod: ModShift | ModCtrl}, + "kf49": {typ: KeyF1, mod: ModAlt}, + "kf50": {typ: KeyF2, mod: ModAlt}, + "kf51": {typ: KeyF3, mod: ModAlt}, + "kf52": {typ: KeyF4, mod: ModAlt}, + "kf53": {typ: KeyF5, mod: ModAlt}, + "kf54": {typ: KeyF6, mod: ModAlt}, + "kf55": {typ: KeyF7, mod: ModAlt}, + "kf56": {typ: KeyF8, mod: ModAlt}, + "kf57": {typ: KeyF9, mod: ModAlt}, + "kf58": {typ: KeyF10, mod: ModAlt}, + "kf59": {typ: KeyF11, mod: ModAlt}, + "kf60": {typ: KeyF12, mod: ModAlt}, + "kf61": {typ: KeyF1, mod: ModShift | ModAlt}, + "kf62": {typ: KeyF2, mod: ModShift | ModAlt}, + "kf63": {typ: KeyF3, mod: ModShift | ModAlt}, } // Preserve F keys from F13 to F63 instead of using them for F-keys // modifiers. if flags&_FlagFKeys != 0 { - keys["kf13"] = Key{Type: KeyF13} - keys["kf14"] = Key{Type: KeyF14} - keys["kf15"] = Key{Type: KeyF15} - keys["kf16"] = Key{Type: KeyF16} - keys["kf17"] = Key{Type: KeyF17} - keys["kf18"] = Key{Type: KeyF18} - keys["kf19"] = Key{Type: KeyF19} - keys["kf20"] = Key{Type: KeyF20} - keys["kf21"] = Key{Type: KeyF21} - keys["kf22"] = Key{Type: KeyF22} - keys["kf23"] = Key{Type: KeyF23} - keys["kf24"] = Key{Type: KeyF24} - keys["kf25"] = Key{Type: KeyF25} - keys["kf26"] = Key{Type: KeyF26} - keys["kf27"] = Key{Type: KeyF27} - keys["kf28"] = Key{Type: KeyF28} - keys["kf29"] = Key{Type: KeyF29} - keys["kf30"] = Key{Type: KeyF30} - keys["kf31"] = Key{Type: KeyF31} - keys["kf32"] = Key{Type: KeyF32} - keys["kf33"] = Key{Type: KeyF33} - keys["kf34"] = Key{Type: KeyF34} - keys["kf35"] = Key{Type: KeyF35} - keys["kf36"] = Key{Type: KeyF36} - keys["kf37"] = Key{Type: KeyF37} - keys["kf38"] = Key{Type: KeyF38} - keys["kf39"] = Key{Type: KeyF39} - keys["kf40"] = Key{Type: KeyF40} - keys["kf41"] = Key{Type: KeyF41} - keys["kf42"] = Key{Type: KeyF42} - keys["kf43"] = Key{Type: KeyF43} - keys["kf44"] = Key{Type: KeyF44} - keys["kf45"] = Key{Type: KeyF45} - keys["kf46"] = Key{Type: KeyF46} - keys["kf47"] = Key{Type: KeyF47} - keys["kf48"] = Key{Type: KeyF48} - keys["kf49"] = Key{Type: KeyF49} - keys["kf50"] = Key{Type: KeyF50} - keys["kf51"] = Key{Type: KeyF51} - keys["kf52"] = Key{Type: KeyF52} - keys["kf53"] = Key{Type: KeyF53} - keys["kf54"] = Key{Type: KeyF54} - keys["kf55"] = Key{Type: KeyF55} - keys["kf56"] = Key{Type: KeyF56} - keys["kf57"] = Key{Type: KeyF57} - keys["kf58"] = Key{Type: KeyF58} - keys["kf59"] = Key{Type: KeyF59} - keys["kf60"] = Key{Type: KeyF60} - keys["kf61"] = Key{Type: KeyF61} - keys["kf62"] = Key{Type: KeyF62} - keys["kf63"] = Key{Type: KeyF63} + keys["kf13"] = key{typ: KeyF13} + keys["kf14"] = key{typ: KeyF14} + keys["kf15"] = key{typ: KeyF15} + keys["kf16"] = key{typ: KeyF16} + keys["kf17"] = key{typ: KeyF17} + keys["kf18"] = key{typ: KeyF18} + keys["kf19"] = key{typ: KeyF19} + keys["kf20"] = key{typ: KeyF20} + keys["kf21"] = key{typ: KeyF21} + keys["kf22"] = key{typ: KeyF22} + keys["kf23"] = key{typ: KeyF23} + keys["kf24"] = key{typ: KeyF24} + keys["kf25"] = key{typ: KeyF25} + keys["kf26"] = key{typ: KeyF26} + keys["kf27"] = key{typ: KeyF27} + keys["kf28"] = key{typ: KeyF28} + keys["kf29"] = key{typ: KeyF29} + keys["kf30"] = key{typ: KeyF30} + keys["kf31"] = key{typ: KeyF31} + keys["kf32"] = key{typ: KeyF32} + keys["kf33"] = key{typ: KeyF33} + keys["kf34"] = key{typ: KeyF34} + keys["kf35"] = key{typ: KeyF35} + keys["kf36"] = key{typ: KeyF36} + keys["kf37"] = key{typ: KeyF37} + keys["kf38"] = key{typ: KeyF38} + keys["kf39"] = key{typ: KeyF39} + keys["kf40"] = key{typ: KeyF40} + keys["kf41"] = key{typ: KeyF41} + keys["kf42"] = key{typ: KeyF42} + keys["kf43"] = key{typ: KeyF43} + keys["kf44"] = key{typ: KeyF44} + keys["kf45"] = key{typ: KeyF45} + keys["kf46"] = key{typ: KeyF46} + keys["kf47"] = key{typ: KeyF47} + keys["kf48"] = key{typ: KeyF48} + keys["kf49"] = key{typ: KeyF49} + keys["kf50"] = key{typ: KeyF50} + keys["kf51"] = key{typ: KeyF51} + keys["kf52"] = key{typ: KeyF52} + keys["kf53"] = key{typ: KeyF53} + keys["kf54"] = key{typ: KeyF54} + keys["kf55"] = key{typ: KeyF55} + keys["kf56"] = key{typ: KeyF56} + keys["kf57"] = key{typ: KeyF57} + keys["kf58"] = key{typ: KeyF58} + keys["kf59"] = key{typ: KeyF59} + keys["kf60"] = key{typ: KeyF60} + keys["kf61"] = key{typ: KeyF61} + keys["kf62"] = key{typ: KeyF62} + keys["kf63"] = key{typ: KeyF63} } return keys diff --git a/win32input.go b/win32input.go index 7b9f2c0dd6..092f93a889 100644 --- a/win32input.go +++ b/win32input.go @@ -31,7 +31,7 @@ func disableWindowsInputMode() Msg { //nolint:unused } func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) Msg { - var key Key + var key key isCtrl := cks&(_LEFT_CTRL_PRESSED|_RIGHT_CTRL_PRESSED) != 0 switch vkc { case _VK_SHIFT: @@ -39,51 +39,51 @@ func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uin return nil case _VK_MENU: if cks&_LEFT_ALT_PRESSED != 0 { - key.Type = KeyLeftAlt + key.typ = KeyLeftAlt } else if cks&_RIGHT_ALT_PRESSED != 0 { - key.Type = KeyRightAlt + key.typ = KeyRightAlt } else if !keyDown { return nil } case _VK_CONTROL: if cks&_LEFT_CTRL_PRESSED != 0 { - key.Type = KeyLeftCtrl + key.typ = KeyLeftCtrl } else if cks&_RIGHT_CTRL_PRESSED != 0 { - key.Type = KeyRightCtrl + key.typ = KeyRightCtrl } else if !keyDown { return nil } case _VK_CAPITAL: - key.Type = KeyCapsLock + key.typ = KeyCapsLock default: var ok bool key, ok = vkKeyEvent[vkc] if !ok { if isCtrl { - key.Runes = []rune{vkCtrlRune(key, r, vkc)} + key.runes = []rune{vkCtrlRune(key, r, vkc)} } else { - key.Runes = []rune{r} + key.runes = []rune{r} } } } if isCtrl { - key.Mod |= ModCtrl + key.mod |= ModCtrl } if cks&(_LEFT_ALT_PRESSED|_RIGHT_ALT_PRESSED) != 0 { - key.Mod |= ModAlt + key.mod |= ModAlt } if cks&_SHIFT_PRESSED != 0 { - key.Mod |= ModShift + key.mod |= ModShift } if cks&_CAPSLOCK_ON != 0 { - key.Mod |= ModCapsLock + key.mod |= ModCapsLock } if cks&_NUMLOCK_ON != 0 { - key.Mod |= ModNumLock + key.mod |= ModNumLock } if cks&_SCROLLLOCK_ON != 0 { - key.Mod |= ModScrollLock + key.mod |= ModScrollLock } // Use the unshifted key @@ -99,7 +99,7 @@ func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uin } var e Msg = KeyPressMsg(key) - key.IsRepeat = repeatCount > 1 + key.isRepeat = repeatCount > 1 if !keyDown { e = KeyReleaseMsg(key) } @@ -116,80 +116,80 @@ func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uin return multiMsg(kevents) } -var vkKeyEvent = map[uint16]Key{ - _VK_RETURN: {Type: KeyEnter}, - _VK_BACK: {Type: KeyBackspace}, - _VK_TAB: {Type: KeyTab}, - _VK_ESCAPE: {Type: KeyEscape}, - _VK_SPACE: {Type: KeySpace, Runes: []rune{' '}}, - _VK_UP: {Type: KeyUp}, - _VK_DOWN: {Type: KeyDown}, - _VK_RIGHT: {Type: KeyRight}, - _VK_LEFT: {Type: KeyLeft}, - _VK_HOME: {Type: KeyHome}, - _VK_END: {Type: KeyEnd}, - _VK_PRIOR: {Type: KeyPgUp}, - _VK_NEXT: {Type: KeyPgDown}, - _VK_DELETE: {Type: KeyDelete}, - _VK_SELECT: {Type: KeySelect}, - _VK_SNAPSHOT: {Type: KeyPrintScreen}, - _VK_INSERT: {Type: KeyInsert}, - _VK_LWIN: {Type: KeyLeftSuper}, - _VK_RWIN: {Type: KeyRightSuper}, - _VK_APPS: {Type: KeyMenu}, - _VK_NUMPAD0: {Type: KeyKp0}, - _VK_NUMPAD1: {Type: KeyKp1}, - _VK_NUMPAD2: {Type: KeyKp2}, - _VK_NUMPAD3: {Type: KeyKp3}, - _VK_NUMPAD4: {Type: KeyKp4}, - _VK_NUMPAD5: {Type: KeyKp5}, - _VK_NUMPAD6: {Type: KeyKp6}, - _VK_NUMPAD7: {Type: KeyKp7}, - _VK_NUMPAD8: {Type: KeyKp8}, - _VK_NUMPAD9: {Type: KeyKp9}, - _VK_MULTIPLY: {Type: KeyKpMultiply}, - _VK_ADD: {Type: KeyKpPlus}, - _VK_SEPARATOR: {Type: KeyKpComma}, - _VK_SUBTRACT: {Type: KeyKpMinus}, - _VK_DECIMAL: {Type: KeyKpDecimal}, - _VK_DIVIDE: {Type: KeyKpDivide}, - _VK_F1: {Type: KeyF1}, - _VK_F2: {Type: KeyF2}, - _VK_F3: {Type: KeyF3}, - _VK_F4: {Type: KeyF4}, - _VK_F5: {Type: KeyF5}, - _VK_F6: {Type: KeyF6}, - _VK_F7: {Type: KeyF7}, - _VK_F8: {Type: KeyF8}, - _VK_F9: {Type: KeyF9}, - _VK_F10: {Type: KeyF10}, - _VK_F11: {Type: KeyF11}, - _VK_F12: {Type: KeyF12}, - _VK_F13: {Type: KeyF13}, - _VK_F14: {Type: KeyF14}, - _VK_F15: {Type: KeyF15}, - _VK_F16: {Type: KeyF16}, - _VK_F17: {Type: KeyF17}, - _VK_F18: {Type: KeyF18}, - _VK_F19: {Type: KeyF19}, - _VK_F20: {Type: KeyF20}, - _VK_F21: {Type: KeyF21}, - _VK_F22: {Type: KeyF22}, - _VK_F23: {Type: KeyF23}, - _VK_F24: {Type: KeyF24}, - _VK_NUMLOCK: {Type: KeyNumLock}, - _VK_SCROLL: {Type: KeyScrollLock}, - _VK_LSHIFT: {Type: KeyLeftShift}, - _VK_RSHIFT: {Type: KeyRightShift}, - _VK_LCONTROL: {Type: KeyLeftCtrl}, - _VK_RCONTROL: {Type: KeyRightCtrl}, - _VK_LMENU: {Type: KeyLeftAlt}, - _VK_RMENU: {Type: KeyRightAlt}, - _VK_OEM_4: {Runes: []rune{'['}}, +var vkKeyEvent = map[uint16]key{ + _VK_RETURN: {typ: KeyEnter}, + _VK_BACK: {typ: KeyBackspace}, + _VK_TAB: {typ: KeyTab}, + _VK_ESCAPE: {typ: KeyEscape}, + _VK_SPACE: {typ: KeySpace, runes: []rune{' '}}, + _VK_UP: {typ: KeyUp}, + _VK_DOWN: {typ: KeyDown}, + _VK_RIGHT: {typ: KeyRight}, + _VK_LEFT: {typ: KeyLeft}, + _VK_HOME: {typ: KeyHome}, + _VK_END: {typ: KeyEnd}, + _VK_PRIOR: {typ: KeyPgUp}, + _VK_NEXT: {typ: KeyPgDown}, + _VK_DELETE: {typ: KeyDelete}, + _VK_SELECT: {typ: KeySelect}, + _VK_SNAPSHOT: {typ: KeyPrintScreen}, + _VK_INSERT: {typ: KeyInsert}, + _VK_LWIN: {typ: KeyLeftSuper}, + _VK_RWIN: {typ: KeyRightSuper}, + _VK_APPS: {typ: KeyMenu}, + _VK_NUMPAD0: {typ: KeyKp0}, + _VK_NUMPAD1: {typ: KeyKp1}, + _VK_NUMPAD2: {typ: KeyKp2}, + _VK_NUMPAD3: {typ: KeyKp3}, + _VK_NUMPAD4: {typ: KeyKp4}, + _VK_NUMPAD5: {typ: KeyKp5}, + _VK_NUMPAD6: {typ: KeyKp6}, + _VK_NUMPAD7: {typ: KeyKp7}, + _VK_NUMPAD8: {typ: KeyKp8}, + _VK_NUMPAD9: {typ: KeyKp9}, + _VK_MULTIPLY: {typ: KeyKpMultiply}, + _VK_ADD: {typ: KeyKpPlus}, + _VK_SEPARATOR: {typ: KeyKpComma}, + _VK_SUBTRACT: {typ: KeyKpMinus}, + _VK_DECIMAL: {typ: KeyKpDecimal}, + _VK_DIVIDE: {typ: KeyKpDivide}, + _VK_F1: {typ: KeyF1}, + _VK_F2: {typ: KeyF2}, + _VK_F3: {typ: KeyF3}, + _VK_F4: {typ: KeyF4}, + _VK_F5: {typ: KeyF5}, + _VK_F6: {typ: KeyF6}, + _VK_F7: {typ: KeyF7}, + _VK_F8: {typ: KeyF8}, + _VK_F9: {typ: KeyF9}, + _VK_F10: {typ: KeyF10}, + _VK_F11: {typ: KeyF11}, + _VK_F12: {typ: KeyF12}, + _VK_F13: {typ: KeyF13}, + _VK_F14: {typ: KeyF14}, + _VK_F15: {typ: KeyF15}, + _VK_F16: {typ: KeyF16}, + _VK_F17: {typ: KeyF17}, + _VK_F18: {typ: KeyF18}, + _VK_F19: {typ: KeyF19}, + _VK_F20: {typ: KeyF20}, + _VK_F21: {typ: KeyF21}, + _VK_F22: {typ: KeyF22}, + _VK_F23: {typ: KeyF23}, + _VK_F24: {typ: KeyF24}, + _VK_NUMLOCK: {typ: KeyNumLock}, + _VK_SCROLL: {typ: KeyScrollLock}, + _VK_LSHIFT: {typ: KeyLeftShift}, + _VK_RSHIFT: {typ: KeyRightShift}, + _VK_LCONTROL: {typ: KeyLeftCtrl}, + _VK_RCONTROL: {typ: KeyRightCtrl}, + _VK_LMENU: {typ: KeyLeftAlt}, + _VK_RMENU: {typ: KeyRightAlt}, + _VK_OEM_4: {runes: []rune{'['}}, // TODO: add more keys } -func vkCtrlRune(k Key, r rune, kc uint16) rune { +func vkCtrlRune(k key, r rune, kc uint16) rune { switch r { case 0x01: return 'a' @@ -257,7 +257,7 @@ func vkCtrlRune(k Key, r rune, kc uint16) rune { } // https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes - if len(k.Runes) == 0 && + if len(k.runes) == 0 && (kc >= 0x30 && kc <= 0x39) || (kc >= 0x41 && kc <= 0x5a) { return rune(kc) diff --git a/xterm.go b/xterm.go index d5110dfb9c..7055fbe18b 100644 --- a/xterm.go +++ b/xterm.go @@ -34,21 +34,21 @@ func parseXTermModifyOtherKeys(csi *ansi.CsiSequence) Msg { switch r { case ansi.BS: - return KeyPressMsg{Mod: mod, Type: KeyBackspace} + return KeyPressMsg{mod: mod, typ: KeyBackspace} case ansi.HT: - return KeyPressMsg{Mod: mod, Type: KeyTab} + return KeyPressMsg{mod: mod, typ: KeyTab} case ansi.CR: - return KeyPressMsg{Mod: mod, Type: KeyEnter} + return KeyPressMsg{mod: mod, typ: KeyEnter} case ansi.ESC: - return KeyPressMsg{Mod: mod, Type: KeyEscape} + return KeyPressMsg{mod: mod, typ: KeyEscape} case ansi.DEL: - return KeyPressMsg{Mod: mod, Type: KeyBackspace} + return KeyPressMsg{mod: mod, typ: KeyBackspace} } // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys return KeyPressMsg{ - Mod: mod, - Runes: []rune{r}, + mod: mod, + runes: []rune{r}, } } From 16f706ac13a18de9ef561c95cc0bb8b802bdea13 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 27 Aug 2024 11:15:19 -0400 Subject: [PATCH 3/7] refactor: expose key codes and define key/mouse interfaces --- driver.go | 2 +- driver_windows.go | 8 +- key.go | 396 +++++++++++++------------------------------ key_test.go | 188 ++++++++++----------- kitty.go | 88 ++++++---- mouse.go | 203 +++++++---------------- mouse_test.go | 146 ++++++++-------- parse.go | 142 +++++++++------- parse_test.go | 16 +- table.go | 415 +++++++++++++++++++++++----------------------- tea_test.go | 2 +- terminfo.go | 404 ++++++++++++++++++++++---------------------- win32input.go | 180 ++++++++++---------- xterm.go | 18 +- 14 files changed, 985 insertions(+), 1223 deletions(-) diff --git a/driver.go b/driver.go index 1b3457d4ee..2e2070c09d 100644 --- a/driver.go +++ b/driver.go @@ -13,7 +13,7 @@ import ( // buffer. type driver struct { rd cancelreader.CancelReader - table map[string]key // table is a lookup table for key sequences. + table map[string]Key // table is a lookup table for key sequences. term string // term is the terminal name $TERM. diff --git a/driver_windows.go b/driver_windows.go index fb50a88bb0..b9e750cef5 100644 --- a/driver_windows.go +++ b/driver_windows.go @@ -64,7 +64,7 @@ loop: for i, e := range events { switch e := e.(type) { case KeyPressMsg: - switch e.Rune() { + switch e.Code { case ansi.ESC, ansi.CSI, ansi.OSC, ansi.DCS, ansi.APC: // start of a sequence if start == -1 { @@ -85,7 +85,7 @@ loop: for i := start; i <= end; i++ { switch e := events[i].(type) { case KeyPressMsg: - seq = append(seq, byte(e.Rune())) + seq = append(seq, byte(e.Code)) } } @@ -127,7 +127,7 @@ func parseConInputEvent(event xwindows.InputRecord, buttonState *uint32, windowS // (e.g. function keys, arrows, etc.) // Otherwise, try to translate it to a rune based on the active keyboard // layout. - if len(key.Runes) == 0 { + if len(key.Text) == 0 { return event } @@ -163,7 +163,7 @@ func parseConInputEvent(event xwindows.InputRecord, buttonState *uint32, windowS return event } - key.baseRune = runes[0] + key.baseCode = runes[0] if kevent.KeyDown { return KeyPressMsg(key) } diff --git a/key.go b/key.go index 5b07fd5142..0a77612b45 100644 --- a/key.go +++ b/key.go @@ -1,48 +1,30 @@ package tea -// KeyType indicates whether the key is a special key or runes. Special -// keys are things like KeyEnter, KeyBackspace, and so on. Runes keys are just -// regular characters like 'a', '你', 'ض', '🦄', and so on. -// -// k := Key{Type: KeyRunes, Runes: []rune{'A'}, Mod: ModShift} -// if k.Type == KeyRunes { -// -// fmt.Println(k.Runes) -// // Output: A -// -// fmt.Println(k.String()) -// // Output: shift+a -// -// } -type KeyType int - -// Special key symbols. -const ( - // KeyRunes indicates that the key represents rune(s), like 'a', 'b', 'c', - // and so on. - KeyRunes KeyType = iota - - // Special names in C0 +import ( + "fmt" + "strings" + "unicode" - KeyBackspace - KeyTab - KeyEnter - KeyEscape + "github.com/charmbracelet/x/ansi" +) - // Special names in G0 +const ( + extended = unicode.MaxRune + 1 +) - KeySpace - KeyDelete +// Special key symbols. +const ( // Special keys - KeyUp + KeyUp rune = extended + iota KeyDown KeyRight KeyLeft KeyBegin KeyFind KeyInsert + KeyDelete KeySelect KeyPgUp KeyPgDown @@ -190,240 +172,107 @@ const ( KeyRightMeta KeyIsoLevel3Shift KeyIsoLevel5Shift -) -// KeyMsg represents an interface that all key messages must implement. -// KeyMsg contains information about a key or release. Keys are always sent to the -// program's update function. 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 msg := msg.(type) { -// case KeyMsg: // catch all key messages (presses and releases) -// fmt.Println(msg.String()) -// case KeyPressMsg: -// switch msg.String() { -// case "enter": -// fmt.Println("you pressed enter!") -// case "a": -// fmt.Println("you pressed a!") -// } -// } -// -// // Switch on the key type (more foolproof) -// switch msg := msg.(type) { -// case KeyReleaseMsg: -// switch msg.Type() { -// case KeyEnter: -// fmt.Println("you pressed enter!") -// case KeyRunes: -// switch string(msg.Runes()) { -// case "a": -// fmt.Println("you pressed a!") -// } -// } -// } -// -// Note that in the case of [KeyRunes], `key.Runes()` will always contain at -// least one character, so you can always safely call `key.Runes()[0]`. -// Instead, use `key.Rune()` to get the first key rune received. Though, in -// certain input method editors (most notably Chinese IMEs) can input multiple -// runes at once. -type KeyMsg interface { - // String returns a friendly string representation for a key. It's safe (and - // encouraged) for use in key comparison. - // - // For example: - // k := Key{Type: KeyEnter} - // fmt.Println(k) // Output: enter - // k = Key{Type: KeyRunes, Runes: []rune{'A'}, Mod: ModShift} - // fmt.Println(k) // Output: shift+a - // k = Key{Type: KeySpace, Runes: []rune{' '}, Mod: ModCtrl|ModShift} - // fmt.Println(k) // Output: ctrl+shift+space - String() string - - // Type returns the key type. A key type is either a special key or - // KeyRunes. Special keys are things like KeyEnter, KeyBackspace, and so - // on. - Type() KeyType - - // Rune returns the first rune in the Runes field. If the Runes field is - // empty, it returns 0. - Rune() rune - - // Runes returns the runes of the key. If the key is a special key, this - // will return nil. Use [Rune()] if you only care about the first rune. - Runes() []rune - - // Mod returns the key modifiers. Modifiers are things like ModCtrl, - // ModAlt, ModShift, and so on. - Mod() KeyMod - - // IsRepeat indicates whether the key is being held down and sending events - // repeatedly. - IsRepeat() bool -} - -// 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. -type key struct { - // runes contains the actual characters received. This usually has a length - // of 1. Use [Rune()] to get the first key rune received. If the user - // presses shift+a, the runes will be `[]rune{'A'}`. - runes []rune - - // typ is a special key, like enter, tab, backspace, and so on. - typ KeyType - - // altRune is the actual, unshifted key pressed by the user. For example, - // if the user presses shift+a, or caps lock is on, the altRune will be - // 'a'. - // - // In the case of non-latin keyboards, like Arabic, altRune is the - // unshifted key on the keyboard. - // - // This is only available with the Kitty Keyboard Protocol or the Windows - // Console API. - altRune rune - - // baseRune is the key pressed according to the standard PC-101 key layout. - // On internaltional keyboards, this is the key that would be pressed if - // the keyboard was set to US layout. - // - // For example, if the user presses 'q' on a French AZERTY keyboard, the - // baseRune will be 'q'. - // - // This is only available with the Kitty Keyboard Protocol or the Windows - // Console API. - baseRune rune + // Special names in C0 - // mod is a modifier key, like ctrl, alt, and so on. - mod KeyMod + KeyBackspace = rune(ansi.DEL) + KeyTab = rune(ansi.HT) + KeyEnter = rune(ansi.CR) + KeyReturn = KeyEnter + KeyEscape = rune(ansi.ESC) + KeyEsc = KeyEscape - // 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 -} - -// Mod represents a key modifier. Modifiers are things like ModCtrl, ModAlt, -// ModShift, and so on. -func (k key) Mod() KeyMod { - return k.mod -} + // Special names in G0 -// IsRepeat indicates whether the key is being held down and sending events -// repeatedly. -func (k key) IsRepeat() bool { - return k.isRepeat -} + KeySpace = rune(ansi.SP) +) // KeyPressMsg represents a key press message. -type KeyPressMsg key +type KeyPressMsg Key -var _ KeyMsg = KeyPressMsg{} - -// String implements fmt.Stringer and is quite useful for matching key -// events. For details, on what this returns see [key.String]. +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. func (k KeyPressMsg) String() string { - return key(k).String() -} - -// Rune returns the first rune in the Runes field. If the Runes field is empty, -// it returns 0. -func (k KeyPressMsg) Rune() rune { - return key(k).Rune() -} - -// Runes returns the runes of the key. If the key is a special key, this will -// return nil. -func (k KeyPressMsg) Runes() []rune { - return key(k).Runes() + return Key(k).String() } -// Type returns the key type. A key type is either a special key or KeyRunes. -// Special keys are things like KeyEnter, KeyBackspace, and so on. -func (k KeyPressMsg) Type() KeyType { - return key(k).Type() -} - -// Mod returns the key modifiers. Modifiers are things like ModCtrl, ModAlt, -// ModShift, and so on. -func (k KeyPressMsg) Mod() KeyMod { - return key(k).Mod() -} - -// IsRepeat indicates whether the key is being held down and sending events -// repeatedly. -func (k KeyPressMsg) IsRepeat() bool { - return key(k).IsRepeat() +// Key returns the underlying key event. This is a syntactic sugar for casting +// the key event to a [Key]. +func (k KeyPressMsg) Key() Key { + return Key(k) } // KeyReleaseMsg represents a key release message. -type KeyReleaseMsg key - -var _ KeyMsg = KeyReleaseMsg{} +type KeyReleaseMsg Key -// String implements fmt.Stringer and is quite useful for matching complex key -// events. For details, on what this returns see [key.String]. +// String implements [fmt.Stringer] and is quite useful for matching key +// events. For details, on what this returns see [Key.String]. func (k KeyReleaseMsg) String() string { - return key(k).String() + return Key(k).String() } -// Rune returns the first rune in the Runes field. If the Runes field is empty, -// it returns 0. -func (k KeyReleaseMsg) Rune() rune { - return key(k).Rune() +// Key returns the underlying key event. This is a convenience method and +// syntactic sugar to satisfy the [KeyMsg] interface, and cast the key event to +// [Key]. +func (k KeyReleaseMsg) Key() Key { + return Key(k) } -// Runes returns the runes of the key. If the key is a special key, this will -// return nil. -func (k KeyReleaseMsg) Runes() []rune { - return key(k).Runes() -} +// KeyMsg represents a key event. This can be either a key press or a key +// release event. +type KeyMsg interface { + fmt.Stringer -// Type returns the key type. A key type is either a special key or KeyRunes. -// Special keys are things like KeyEnter, KeyBackspace, and so on. -func (k KeyReleaseMsg) Type() KeyType { - return key(k).Type() + // Key returns the underlying key event. + Key() Key } -// Mod returns the key modifiers. Modifiers are things like ModCtrl, ModAlt, -// ModShift, and so on. -func (k KeyReleaseMsg) Mod() KeyMod { - return key(k).Mod() -} +// 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. +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 -// IsRepeat indicates whether the key is being held down and sending events -// repeatedly. -func (k KeyReleaseMsg) IsRepeat() bool { - return key(k).IsRepeat() -} + // Mod represents modifier keys, like [ModCtrl], [ModAlt], and so on. + Mod KeyMod -// Runes returns the runes of the key. If the key is a special key, this will -// return nil. -func (k key) Runes() []rune { - return k.runes -} + // Code represents the key pressed. This is usually a special key like + // [KeyTab], [KeyEnter], [KeyF1], or a printable character like 'a'. + Code rune -// Rune returns the first rune in the Runes field. If the Runes field is empty, -// it returns 0. -func (k key) Rune() rune { - if len(k.runes) == 0 { - return 0 - } - return k.runes[0] -} + // 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 -// Type returns the key type. A key type is either a special key or KeyRunes. -// Special keys are things like KeyEnter, KeyBackspace, and so on. -func (k key) Type() KeyType { - return k.typ + // 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 used to convert a key to a string. +// String implements [fmt.Stringer] and is used to convert a key to a string. // 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. @@ -438,64 +287,49 @@ func (k key) Type() KeyType { // // For example, you'll always see "ctrl+shift+alt+a" and never // "shift+ctrl+alt+a". -func (k key) String() string { - var s string - if k.mod.Contains(ModCtrl) && k.typ != KeyLeftCtrl && k.typ != KeyRightCtrl { - s += "ctrl+" +func (k Key) String() 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.typ != KeyLeftAlt && k.typ != KeyRightAlt { - s += "alt+" + if k.Mod.Contains(ModAlt) && k.Code != KeyLeftAlt && k.Code != KeyRightAlt { + sb.WriteString("alt+") } - if k.mod.Contains(ModShift) && k.typ != KeyLeftShift && k.typ != KeyRightShift { - s += "shift+" + if k.Mod.Contains(ModShift) && k.Code != KeyLeftShift && k.Code != KeyRightShift { + sb.WriteString("shift+") } - if k.mod.Contains(ModMeta) && k.typ != KeyLeftMeta && k.typ != KeyRightMeta { - s += "meta+" + if k.Mod.Contains(ModMeta) && k.Code != KeyLeftMeta && k.Code != KeyRightMeta { + sb.WriteString("meta+") } - if k.mod.Contains(ModHyper) && k.typ != KeyLeftHyper && k.typ != KeyRightHyper { - s += "hyper+" + if k.Mod.Contains(ModHyper) && k.Code != KeyLeftHyper && k.Code != KeyRightHyper { + sb.WriteString("hyper+") } - if k.mod.Contains(ModSuper) && k.typ != KeyLeftSuper && k.typ != KeyRightSuper { - s += "super+" + if k.Mod.Contains(ModSuper) && k.Code != KeyLeftSuper && k.Code != KeyRightSuper { + sb.WriteString("super+") } - runeStr := func(r rune) string { - // Space is the only invisible printable character. - if r == ' ' { - return "space" + 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 } - return string(r) - } - if k.baseRune != 0 { - // If a baseRune is present, use it to represent a key using the standard - // PC-101 key layout. - s += runeStr(k.baseRune) - } else if k.altRune != 0 { - // Otherwise, use the AltRune aka the non-shifted one if present. - s += runeStr(k.altRune) - } else if len(k.runes) > 0 { - // Else, just print the rune. - if len(k.runes) > 1 { - s += string(k.runes) + + if code == ' ' { + // Space is the only invisible printable character. + sb.WriteString("space") } else { - s += runeStr(k.Rune()) + sb.WriteRune(code) } - } else { - s += k.typ.String() } - return s -} -// String returns the string representation of the key type. -func (k KeyType) String() string { - if s, ok := keyTypeString[k]; ok { - return s - } - return "" + return sb.String() } -var keyTypeString = map[KeyType]string{ - KeyRunes: "runes", +var keyTypeString = map[rune]string{ KeyEnter: "enter", KeyTab: "tab", KeyBackspace: "backspace", diff --git a/key_test.go b/key_test.go index b7824a0d99..a0aea7dc90 100644 --- a/key_test.go +++ b/key_test.go @@ -24,36 +24,22 @@ var sequences = buildKeysTable(_FlagTerminfo, "dumb") func TestKeyString(t *testing.T) { t.Run("alt+space", func(t *testing.T) { - k := KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModAlt} + k := KeyPressMsg{Code: KeySpace, Text: " ", Mod: ModAlt} if got := k.String(); got != "alt+space" { t.Fatalf(`expected a "alt+space ", got %q`, got) } }) t.Run("runes", func(t *testing.T) { - k := KeyPressMsg{runes: []rune{'a'}} + k := KeyPressMsg{Code: 'a', Text: "a"} if got := k.String(); got != "a" { t.Fatalf(`expected an "a", got %q`, got) } }) t.Run("invalid", func(t *testing.T) { - k := KeyPressMsg{typ: 99999} - if got := k.String(); got != "" { - t.Fatalf(`expected a "unknown", got %q`, got) - } - }) -} - -func TestKeyTypeString(t *testing.T) { - t.Run("space", func(t *testing.T) { - if got := KeySpace.String(); got != "space" { - t.Fatalf(`expected a "space", got %q`, got) - } - }) - - t.Run("invalid", func(t *testing.T) { - if got := KeyType(99999).String(); got != "" { + k := KeyPressMsg{Code: 99999} + if got := k.String(); got != "𘚟" { t.Fatalf(`expected a "unknown", got %q`, got) } }) @@ -78,7 +64,7 @@ func buildBaseSeqTests() []seqTest { // position report having the same sequence. See [parseCsi] for more // information. if f3CurPosRegexp.MatchString(seq) { - st.msgs = []Msg{k, CursorPositionMsg{Row: 1, Column: int(key.mod) + 1}} + st.msgs = []Msg{k, CursorPositionMsg{Row: 1, Column: int(key.Mod) + 1}} } td = append(td, st) } @@ -96,14 +82,14 @@ func buildBaseSeqTests() []seqTest { seqTest{ []byte{' '}, []Msg{ - KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, + KeyPressMsg{Code: KeySpace, Text: " "}, }, }, // An escape character with the alt modifier. seqTest{ []byte{'\x1b', ' '}, []Msg{ - KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModAlt}, + KeyPressMsg{Code: KeySpace, Mod: ModAlt}, }, }, ) @@ -116,101 +102,101 @@ func TestParseSequence(t *testing.T) { // Xterm modifyOtherKeys CSI 27 ; ; ~ seqTest{ []byte("\x1b[27;3;20320~"), - []Msg{KeyPressMsg{runes: []rune{'你'}, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: '你', Mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;65~"), - []Msg{KeyPressMsg{runes: []rune{'A'}, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: 'A', Mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;8~"), - []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyBackspace, Mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;27~"), - []Msg{KeyPressMsg{typ: KeyEscape, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyEscape, Mod: ModAlt}}, }, seqTest{ []byte("\x1b[27;3;127~"), - []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyBackspace, Mod: ModAlt}}, }, // Kitty keyboard / CSI u (fixterms) seqTest{ []byte("\x1b[1B"), - []Msg{KeyPressMsg{typ: KeyDown}}, + []Msg{KeyPressMsg{Code: KeyDown}}, }, seqTest{ []byte("\x1b[1;B"), - []Msg{KeyPressMsg{typ: KeyDown}}, + []Msg{KeyPressMsg{Code: KeyDown}}, }, seqTest{ []byte("\x1b[1;4B"), - []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyDown}}, + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Code: KeyDown}}, }, seqTest{ []byte("\x1b[8~"), - []Msg{KeyPressMsg{typ: KeyEnd}}, + []Msg{KeyPressMsg{Code: KeyEnd}}, }, seqTest{ []byte("\x1b[8;~"), - []Msg{KeyPressMsg{typ: KeyEnd}}, + []Msg{KeyPressMsg{Code: KeyEnd}}, }, seqTest{ []byte("\x1b[8;10~"), - []Msg{KeyPressMsg{mod: ModShift | ModMeta, typ: KeyEnd}}, + []Msg{KeyPressMsg{Mod: ModShift | ModMeta, Code: KeyEnd}}, }, seqTest{ []byte("\x1b[27;4u"), - []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyEscape}}, + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Code: KeyEscape}}, }, seqTest{ []byte("\x1b[127;4u"), - []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyBackspace}}, + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Code: KeyBackspace}}, }, seqTest{ []byte("\x1b[57358;4u"), - []Msg{KeyPressMsg{mod: ModShift | ModAlt, typ: KeyCapsLock}}, + []Msg{KeyPressMsg{Mod: ModShift | ModAlt, Code: KeyCapsLock}}, }, seqTest{ []byte("\x1b[9;2u"), - []Msg{KeyPressMsg{mod: ModShift, typ: KeyTab}}, + []Msg{KeyPressMsg{Mod: ModShift, Code: KeyTab}}, }, seqTest{ []byte("\x1b[195;u"), - []Msg{KeyPressMsg{runes: []rune{'Ã'}, typ: KeyRunes}}, + []Msg{KeyPressMsg{Text: "Ã", Code: 'Ã'}}, }, seqTest{ []byte("\x1b[20320;2u"), - []Msg{KeyPressMsg{runes: []rune{'你'}, mod: ModShift, typ: KeyRunes}}, + []Msg{KeyPressMsg{Text: "你", Mod: ModShift, Code: '你'}}, }, seqTest{ []byte("\x1b[195;:1u"), - []Msg{KeyPressMsg{runes: []rune{'Ã'}, typ: KeyRunes}}, + []Msg{KeyPressMsg{Text: "Ã", Code: 'Ã'}}, }, seqTest{ []byte("\x1b[195;2:3u"), - []Msg{KeyReleaseMsg{runes: []rune{'Ã'}, mod: ModShift}}, + []Msg{KeyReleaseMsg{Code: 'Ã', Text: "Ã", Mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:2u"), - []Msg{KeyPressMsg{runes: []rune{'Ã'}, isRepeat: true, mod: ModShift}}, + []Msg{KeyPressMsg{Code: 'Ã', Text: "Ã", IsRepeat: true, Mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:1u"), - []Msg{KeyPressMsg{runes: []rune{'Ã'}, mod: ModShift}}, + []Msg{KeyPressMsg{Code: 'Ã', Text: "Ã", Mod: ModShift}}, }, seqTest{ []byte("\x1b[195;2:3u"), - []Msg{KeyReleaseMsg{runes: []rune{'Ã'}, mod: ModShift}}, + []Msg{KeyReleaseMsg{Code: 'Ã', Text: "Ã", Mod: ModShift}}, }, seqTest{ []byte("\x1b[97;2;65u"), - []Msg{KeyPressMsg{runes: []rune{'A'}, mod: ModShift, altRune: 'a'}}, + []Msg{KeyPressMsg{Code: 'a', Text: "A", Mod: ModShift}}, }, seqTest{ []byte("\x1b[97;;229u"), - []Msg{KeyPressMsg{runes: []rune{'å'}, altRune: 'a'}}, + []Msg{KeyPressMsg{Code: 'a', Text: "å"}}, }, // focus/blur @@ -230,86 +216,86 @@ func TestParseSequence(t *testing.T) { seqTest{ []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, []Msg{ - MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, + MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, }, }, // SGR Mouse event. seqTest{ []byte("\x1b[<0;33;17M"), []Msg{ - MouseClickMsg{x: 32, y: 16, button: MouseLeft}, + MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, }, }, // Runes. seqTest{ []byte{'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Text: "a"}, }, }, seqTest{ []byte{'\x1b', 'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, + KeyPressMsg{Code: 'a', Mod: ModAlt}, }, }, seqTest{ []byte{'a', 'a', 'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, - KeyPressMsg{runes: []rune{'a'}}, - KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Text: "a"}, + KeyPressMsg{Code: 'a', Text: "a"}, + KeyPressMsg{Code: 'a', Text: "a"}, }, }, // Multi-byte rune. seqTest{ []byte("☃"), []Msg{ - KeyPressMsg{runes: []rune{'☃'}}, + KeyPressMsg{Code: '☃', Text: "☃"}, }, }, seqTest{ []byte("\x1b☃"), []Msg{ - KeyPressMsg{runes: []rune{'☃'}, mod: ModAlt}, + KeyPressMsg{Code: '☃', Mod: ModAlt}, }, }, - // Standalone control chacters. + // Standalone control characters. seqTest{ []byte{'\x1b'}, []Msg{ - KeyPressMsg{typ: KeyEscape}, + KeyPressMsg{Code: KeyEscape}, }, }, seqTest{ []byte{ansi.SOH}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, + KeyPressMsg{Code: 'a', Mod: ModCtrl}, }, }, seqTest{ []byte{'\x1b', ansi.SOH}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl | ModAlt}, + KeyPressMsg{Code: 'a', Mod: ModCtrl | ModAlt}, }, }, seqTest{ []byte{ansi.NUL}, []Msg{ - KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl}, + KeyPressMsg{Code: KeySpace, Mod: ModCtrl}, }, }, seqTest{ []byte{'\x1b', ansi.NUL}, []Msg{ - KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl | ModAlt}, + KeyPressMsg{Code: KeySpace, Mod: ModCtrl | ModAlt}, }, }, // C1 control characters. seqTest{ []byte{'\x80'}, []Msg{ - KeyPressMsg{runes: []rune{0x80 - '@'}, mod: ModCtrl | ModAlt}, + KeyPressMsg{Code: rune(0x80 - '@'), Mod: ModCtrl | ModAlt}, }, }, ) @@ -340,7 +326,7 @@ func TestParseSequence(t *testing.T) { buf = buf[width:] } if !reflect.DeepEqual(tc.msgs, events) { - t.Errorf("\nexpected event:\n %#v\ngot:\n %#v", tc.msgs, events) + t.Errorf("\nexpected event for %q:\n %#v\ngot:\n %#v", tc.seq, tc.msgs, events) } }) } @@ -349,7 +335,7 @@ func TestParseSequence(t *testing.T) { func TestReadLongInput(t *testing.T) { expect := make([]Msg, 1000) for i := 0; i < 1000; i++ { - expect[i] = KeyPressMsg{runes: []rune{'a'}} + expect[i] = KeyPressMsg{Code: 'a', Text: "a"} } input := strings.Repeat("a", 1000) drv, err := newDriver(strings.NewReader(input), "dumb", 0) @@ -385,77 +371,77 @@ func TestReadInput(t *testing.T) { "a", []byte{'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Text: "a"}, }, }, { "space", []byte{' '}, []Msg{ - KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, + KeyPressMsg{Code: KeySpace, Text: " "}, }, }, { "a alt+a", []byte{'a', '\x1b', 'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, - KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, + KeyPressMsg{Code: 'a', Text: "a"}, + KeyPressMsg{Code: 'a', Mod: ModAlt}, }, }, { "a alt+a a", []byte{'a', '\x1b', 'a', 'a'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, - KeyPressMsg{runes: []rune{'a'}, mod: ModAlt}, - KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Text: "a"}, + KeyPressMsg{Code: 'a', Mod: ModAlt}, + KeyPressMsg{Code: 'a', Text: "a"}, }, }, { "ctrl+a", []byte{byte(ansi.SOH)}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, + KeyPressMsg{Code: 'a', Mod: ModCtrl}, }, }, { "ctrl+a ctrl+b", []byte{byte(ansi.SOH), byte(ansi.STX)}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl}, - KeyPressMsg{runes: []rune{'b'}, mod: ModCtrl}, + KeyPressMsg{Code: 'a', Mod: ModCtrl}, + KeyPressMsg{Code: 'b', Mod: ModCtrl}, }, }, { "alt+a", []byte{byte(0x1b), 'a'}, []Msg{ - KeyPressMsg{mod: ModAlt, runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Mod: ModAlt}, }, }, { "a b c d", []byte{'a', 'b', 'c', 'd'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, - KeyPressMsg{runes: []rune{'b'}}, - KeyPressMsg{runes: []rune{'c'}}, - KeyPressMsg{runes: []rune{'d'}}, + KeyPressMsg{Code: 'a', Text: "a"}, + KeyPressMsg{Code: 'b', Text: "b"}, + KeyPressMsg{Code: 'c', Text: "c"}, + KeyPressMsg{Code: 'd', Text: "d"}, }, }, { "up", []byte("\x1b[A"), []Msg{ - KeyPressMsg{typ: KeyUp}, + KeyPressMsg{Code: KeyUp}, }, }, { "wheel up", []byte{'\x1b', '[', 'M', byte(32) + 0b0100_0000, byte(65), byte(49)}, []Msg{ - MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, + MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, }, }, { @@ -465,41 +451,41 @@ func TestReadInput(t *testing.T) { '\x1b', '[', 'M', byte(32) + 0b0000_0011, byte(64 + 33), byte(32 + 33), }, []Msg{ - MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, - MouseReleaseMsg{x: 64, y: 32, button: MouseNone}, + MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, + MouseReleaseMsg{X: 64, Y: 32, Button: MouseNone}, }, }, { "shift+tab", []byte{'\x1b', '[', 'Z'}, []Msg{ - KeyPressMsg{typ: KeyTab, mod: ModShift}, + KeyPressMsg{Code: KeyTab, Mod: ModShift}, }, }, { "enter", []byte{'\r'}, - []Msg{KeyPressMsg{typ: KeyEnter}}, + []Msg{KeyPressMsg{Code: KeyEnter}}, }, { "alt+enter", []byte{'\x1b', '\r'}, []Msg{ - KeyPressMsg{typ: KeyEnter, mod: ModAlt}, + KeyPressMsg{Code: KeyEnter, Mod: ModAlt}, }, }, { "insert", []byte{'\x1b', '[', '2', '~'}, []Msg{ - KeyPressMsg{typ: KeyInsert}, + KeyPressMsg{Code: KeyInsert}, }, }, { "ctrl+alt+a", []byte{'\x1b', byte(ansi.SOH)}, []Msg{ - KeyPressMsg{runes: []rune{'a'}, mod: ModCtrl | ModAlt}, + KeyPressMsg{Code: 'a', Mod: ModCtrl | ModAlt}, }, }, { @@ -511,52 +497,52 @@ func TestReadInput(t *testing.T) { { "up", []byte{'\x1b', 'O', 'A'}, - []Msg{KeyPressMsg{typ: KeyUp}}, + []Msg{KeyPressMsg{Code: KeyUp}}, }, { "down", []byte{'\x1b', 'O', 'B'}, - []Msg{KeyPressMsg{typ: KeyDown}}, + []Msg{KeyPressMsg{Code: KeyDown}}, }, { "right", []byte{'\x1b', 'O', 'C'}, - []Msg{KeyPressMsg{typ: KeyRight}}, + []Msg{KeyPressMsg{Code: KeyRight}}, }, { "left", []byte{'\x1b', 'O', 'D'}, - []Msg{KeyPressMsg{typ: KeyLeft}}, + []Msg{KeyPressMsg{Code: KeyLeft}}, }, { "alt+enter", []byte{'\x1b', '\x0d'}, - []Msg{KeyPressMsg{typ: KeyEnter, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyEnter, Mod: ModAlt}}, }, { "alt+backspace", []byte{'\x1b', '\x7f'}, - []Msg{KeyPressMsg{typ: KeyBackspace, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyBackspace, Mod: ModAlt}}, }, { "ctrl+space", []byte{'\x00'}, - []Msg{KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl}}, + []Msg{KeyPressMsg{Code: KeySpace, Mod: ModCtrl}}, }, { "ctrl+alt+space", []byte{'\x1b', '\x00'}, - []Msg{KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl | ModAlt}}, + []Msg{KeyPressMsg{Code: KeySpace, Mod: ModCtrl | ModAlt}}, }, { "esc", []byte{'\x1b'}, - []Msg{KeyPressMsg{typ: KeyEscape}}, + []Msg{KeyPressMsg{Code: KeyEscape}}, }, { "alt+esc", []byte{'\x1b', '\x1b'}, - []Msg{KeyPressMsg{typ: KeyEscape, mod: ModAlt}}, + []Msg{KeyPressMsg{Code: KeyEscape, Mod: ModAlt}}, }, { "a b o", @@ -570,7 +556,7 @@ func TestReadInput(t *testing.T) { PasteStartMsg{}, PasteMsg("a b"), PasteEndMsg{}, - KeyPressMsg{runes: []rune{'o'}}, + KeyPressMsg{Code: 'o', Text: "o"}, }, }, { @@ -597,10 +583,10 @@ func TestReadInput(t *testing.T) { "a ?0xfe? b", []byte{'a', '\xfe', ' ', 'b'}, []Msg{ - KeyPressMsg{runes: []rune{'a'}}, + KeyPressMsg{Code: 'a', Text: "a"}, UnknownMsg(rune(0xfe)), - KeyPressMsg{typ: KeySpace, runes: []rune{' '}}, - KeyPressMsg{runes: []rune{'b'}}, + KeyPressMsg{Code: KeySpace, Text: " "}, + KeyPressMsg{Code: 'b', Text: "b"}, }, }, } diff --git a/kitty.go b/kitty.go index 5522a708ae..41b738cdeb 100644 --- a/kitty.go +++ b/kitty.go @@ -49,7 +49,7 @@ func kittyKeyboard() Msg { //nolint:unused type _KittyKeyboardMsg int // Kitty Clipboard Control Sequences -var kittyKeyMap = map[int]KeyType{ +var kittyKeyMap = map[int]rune{ ansi.BS: KeyBackspace, ansi.HT: KeyTab, ansi.CR: KeyEnter, @@ -224,72 +224,88 @@ func fromKittyMod(mod int) KeyMod { // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { var isRelease bool - key := key{} + key := Key{} if params := csi.Subparams(0); len(params) > 0 { code := params[0] if sym, ok := kittyKeyMap[code]; ok { - key.typ = sym + key.Code = sym } else { r := rune(code) if !utf8.ValidRune(r) { r = utf8.RuneError } - key.typ = KeyRunes - key.runes = []rune{r} + key.Code = r + } - // alternate key reporting - switch len(params) { - case 3: - // shifted key + base key - if b := rune(params[2]); 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.baseRune = b - } - fallthrough - case 2: - // shifted key - if s := rune(params[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.altRune = key.Rune() - key.runes = []rune{s} - } + // alternate key reporting + switch len(params) { + case 3: + // shifted key + base key + if b := rune(params[2]); 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 2: + // shifted key + if s := rune(params[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 } } } + if params := csi.Subparams(1); len(params) > 0 { mod := params[0] if mod > 1 { - key.mod = fromKittyMod(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 = "" + } } if len(params) > 1 { switch params[1] { case 2: - key.isRepeat = true + key.IsRepeat = true case 3: isRelease = true } } } + if params := csi.Subparams(2); len(params) > 0 { - r := rune(params[0]) - if unicode.IsPrint(r) { - key.altRune = key.Rune() - key.runes = []rune{r} + for _, code := range params { + if code != 0 { + key.Text += string(rune(code)) + } } } + + isShifted := key.Mod <= ModShift && key.Code != KeyLeftShift && key.Code != KeyRightShift + if len(key.Text) == 0 && isShifted { + if key.ShiftedCode != 0 { + key.Text = string(key.ShiftedCode) + } else { + key.Text = string(key.Code) + } + } + if isRelease { return KeyReleaseMsg(key) } + return KeyPressMsg(key) } diff --git a/mouse.go b/mouse.go index 6946224937..58949b8cfc 100644 --- a/mouse.go +++ b/mouse.go @@ -1,6 +1,8 @@ package tea import ( + "fmt" + "github.com/charmbracelet/x/ansi" ) @@ -54,48 +56,35 @@ var mouseButtons = map[MouseButton]string{ MouseExtra2: "button11", } -// mouse represents a mouse message. -type mouse struct { - x, y int - button MouseButton - mod KeyMod -} - -var _ MouseMsg = mouse{} - -// Button implements MouseMsg. -func (m mouse) Button() MouseButton { - return m.button -} - -// Mod implements MouseMsg. -func (m mouse) Mod() KeyMod { - return m.mod -} +// MouseMsg represents a mouse message. This is a generic mouse message that +// can represent any kind of mouse event. +type MouseMsg interface { + fmt.Stringer -// X implements MouseMsg. -func (m mouse) X() int { - return m.x + // Mouse returns the underlying mouse event. + Mouse() Mouse } -// Y implements MouseMsg. -func (m mouse) Y() int { - return m.y +// Mouse represents a Mouse message. +type Mouse struct { + X, Y int + Button MouseButton + Mod KeyMod } // String returns a string representation of the mouse message. -func (m mouse) String() (s string) { - if m.mod.Contains(ModCtrl) { +func (m Mouse) String() (s string) { + if m.Mod.Contains(ModCtrl) { s += "ctrl+" } - if m.mod.Contains(ModAlt) { + if m.Mod.Contains(ModAlt) { s += "alt+" } - if m.mod.Contains(ModShift) { + if m.Mod.Contains(ModShift) { s += "shift+" } - str, ok := mouseButtons[m.button] + str, ok := mouseButtons[m.Button] if !ok { s += "unknown" } else if str != "none" { // motion events don't have a button @@ -105,150 +94,70 @@ func (m mouse) String() (s string) { return s } -// MouseMsg contains information about a mouse event and are sent to a programs -// update function when mouse activity occurs. Note that the mouse must first -// be enabled in order for the mouse events to be received. -type MouseMsg interface { - // String returns a string representation of the mouse event. - String() string - - // X returns the x-coordinate of the mouse event. - X() int - - // Y returns the y-coordinate of the mouse event. - Y() int - - // Button returns the button that was pressed during the mouse event. - Button() MouseButton - - // Mod returns any modifier keys that were pressed during the mouse event. - Mod() KeyMod -} - // MouseClickMsg represents a mouse button click message. -type MouseClickMsg mouse - -var _ MouseMsg = MouseClickMsg{} - -// Button implements MouseMsg. -func (e MouseClickMsg) Button() MouseButton { - return mouse(e).Button() -} - -// Mod implements MouseMsg. -func (e MouseClickMsg) Mod() KeyMod { - return mouse(e).Mod() -} - -// X implements MouseMsg. -func (e MouseClickMsg) X() int { - return mouse(e).X() -} - -// Y implements MouseMsg. -func (e MouseClickMsg) Y() int { - return mouse(e).Y() -} +type MouseClickMsg Mouse // String returns a string representation of the mouse click message. func (e MouseClickMsg) String() string { - return mouse(e).String() + return Mouse(e).String() } -var _ MouseMsg = MouseReleaseMsg{} - -// MouseReleaseMsg represents a mouse button release message. -type MouseReleaseMsg mouse - -// Button implements MouseMsg. -func (e MouseReleaseMsg) Button() MouseButton { - return mouse(e).Button() +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseClickMsg) Mouse() Mouse { + return Mouse(e) } -// Mod implements MouseMsg. -func (e MouseReleaseMsg) Mod() KeyMod { - return mouse(e).Mod() -} - -// X implements MouseMsg. -func (e MouseReleaseMsg) X() int { - return mouse(e).X() -} - -// Y implements MouseMsg. -func (e MouseReleaseMsg) Y() int { - return mouse(e).Y() -} +// MouseReleaseMsg represents a mouse button release message. +type MouseReleaseMsg Mouse // String returns a string representation of the mouse release message. func (e MouseReleaseMsg) String() string { - return mouse(e).String() -} - -var _ MouseMsg = MouseWheelMsg{} - -// MouseWheelMsg represents a mouse wheel message event. -type MouseWheelMsg mouse - -// Button implements MouseMsg. -func (e MouseWheelMsg) Button() MouseButton { - return mouse(e).Button() -} - -// Mod implements MouseMsg. -func (e MouseWheelMsg) Mod() KeyMod { - return mouse(e).Mod() + return Mouse(e).String() } -// X implements MouseMsg. -func (e MouseWheelMsg) X() int { - return mouse(e).X() +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseReleaseMsg) Mouse() Mouse { + return Mouse(e) } -// Y implements MouseMsg. -func (e MouseWheelMsg) Y() int { - return mouse(e).Y() -} +// MouseWheelMsg represents a mouse wheel message event. +type MouseWheelMsg Mouse // String returns a string representation of the mouse wheel message. func (e MouseWheelMsg) String() string { - return mouse(e).String() -} - -// MouseMotionMsg represents a mouse motion message. -type MouseMotionMsg mouse - -var _ MouseMsg = MouseMotionMsg{} - -// Button implements MouseMsg. -func (e MouseMotionMsg) Button() MouseButton { - return mouse(e).Button() + return Mouse(e).String() } -// Mod implements MouseMsg. -func (e MouseMotionMsg) Mod() KeyMod { - return mouse(e).Mod() +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseWheelMsg) Mouse() Mouse { + return Mouse(e) } -// X implements MouseMsg. -func (e MouseMotionMsg) X() int { - return mouse(e).X() -} - -// Y implements MouseMsg. -func (e MouseMotionMsg) Y() int { - return mouse(e).Y() -} +// MouseMotionMsg represents a mouse motion message. +type MouseMotionMsg Mouse // String returns a string representation of the mouse motion message. func (e MouseMotionMsg) String() string { - m := mouse(e) - if m.button != 0 { + m := Mouse(e) + if m.Button != 0 { return m.String() + "+motion" } return m.String() + "motion" } +// Mouse returns the underlying mouse event. This is a convenience method and +// syntactic sugar to satisfy the [MouseMsg] interface, and cast the mouse +// event to [Mouse]. +func (e MouseMotionMsg) Mouse() Mouse { + return Mouse(e) +} + // Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events // look like: // @@ -272,11 +181,11 @@ func parseSGRMouseEvent(csi *ansi.CsiSequence) Msg { x-- y-- - m := mouse{x: x, y: y, button: btn, mod: mod} + m := Mouse{X: x, Y: y, Button: btn, Mod: mod} // Wheel buttons don't have release events // Motion can be reported as a release event in some terminals (Windows Terminal) - if isWheel(m.button) { + if isWheel(m.Button) { return MouseWheelMsg(m) } else if !isMotion && release { return MouseReleaseMsg(m) @@ -311,8 +220,8 @@ func parseX10MouseEvent(buf []byte) Msg { x := int(v[1]) - x10MouseByteOffset - 1 y := int(v[2]) - x10MouseByteOffset - 1 - m := mouse{x: x, y: y, button: btn, mod: mod} - if isWheel(m.button) { + m := Mouse{X: x, Y: y, Button: btn, Mod: mod} + if isWheel(m.Button) { return MouseWheelMsg(m) } else if isMotion { return MouseMotionMsg(m) diff --git a/mouse_test.go b/mouse_test.go index 190dcd6f1f..69b2730d1f 100644 --- a/mouse_test.go +++ b/mouse_test.go @@ -16,96 +16,96 @@ func TestMouseEvent_String(t *testing.T) { }{ { name: "unknown", - event: MouseClickMsg{button: MouseButton(0xff)}, + event: MouseClickMsg{Button: MouseButton(0xff)}, expected: "unknown", }, { name: "left", - event: MouseClickMsg{button: MouseLeft}, + event: MouseClickMsg{Button: MouseLeft}, expected: "left", }, { name: "right", - event: MouseClickMsg{button: MouseRight}, + event: MouseClickMsg{Button: MouseRight}, expected: "right", }, { name: "middle", - event: MouseClickMsg{button: MouseMiddle}, + event: MouseClickMsg{Button: MouseMiddle}, expected: "middle", }, { name: "release", - event: MouseReleaseMsg{button: MouseNone}, + event: MouseReleaseMsg{Button: MouseNone}, expected: "", }, { name: "wheelup", - event: MouseWheelMsg{button: MouseWheelUp}, + event: MouseWheelMsg{Button: MouseWheelUp}, expected: "wheelup", }, { name: "wheeldown", - event: MouseWheelMsg{button: MouseWheelDown}, + event: MouseWheelMsg{Button: MouseWheelDown}, expected: "wheeldown", }, { name: "wheelleft", - event: MouseWheelMsg{button: MouseWheelLeft}, + event: MouseWheelMsg{Button: MouseWheelLeft}, expected: "wheelleft", }, { name: "wheelright", - event: MouseWheelMsg{button: MouseWheelRight}, + event: MouseWheelMsg{Button: MouseWheelRight}, expected: "wheelright", }, { name: "motion", - event: MouseMotionMsg{button: MouseNone}, + event: MouseMotionMsg{Button: MouseNone}, expected: "motion", }, { name: "shift+left", - event: MouseReleaseMsg{button: MouseLeft, mod: ModShift}, + event: MouseReleaseMsg{Button: MouseLeft, Mod: ModShift}, expected: "shift+left", }, { - name: "shift+left", event: MouseClickMsg{button: MouseLeft, mod: ModShift}, + name: "shift+left", event: MouseClickMsg{Button: MouseLeft, Mod: ModShift}, expected: "shift+left", }, { name: "ctrl+shift+left", - event: MouseClickMsg{button: MouseLeft, mod: ModCtrl | ModShift}, + event: MouseClickMsg{Button: MouseLeft, Mod: ModCtrl | ModShift}, expected: "ctrl+shift+left", }, { name: "alt+left", - event: MouseClickMsg{button: MouseLeft, mod: ModAlt}, + event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt}, expected: "alt+left", }, { name: "ctrl+left", - event: MouseClickMsg{button: MouseLeft, mod: ModCtrl}, + event: MouseClickMsg{Button: MouseLeft, Mod: ModCtrl}, expected: "ctrl+left", }, { name: "ctrl+alt+left", - event: MouseClickMsg{button: MouseLeft, mod: ModAlt | ModCtrl}, + event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt | ModCtrl}, expected: "ctrl+alt+left", }, { name: "ctrl+alt+shift+left", - event: MouseClickMsg{button: MouseLeft, mod: ModAlt | ModCtrl | ModShift}, + event: MouseClickMsg{Button: MouseLeft, Mod: ModAlt | ModCtrl | ModShift}, expected: "ctrl+alt+shift+left", }, { name: "ignore coordinates", - event: MouseClickMsg{x: 100, y: 200, button: MouseLeft}, + event: MouseClickMsg{X: 100, Y: 200, Button: MouseLeft}, expected: "left", }, { name: "broken type", - event: MouseClickMsg{button: MouseButton(120)}, + event: MouseClickMsg{Button: MouseButton(120)}, expected: "unknown", }, } @@ -147,145 +147,145 @@ func TestParseX10MouseDownEvent(t *testing.T) { { name: "zero position", buf: encode(0b0000_0000, 0, 0), - expected: MouseClickMsg{x: 0, y: 0, button: MouseLeft}, + expected: MouseClickMsg{X: 0, Y: 0, Button: MouseLeft}, }, { name: "max position", buf: encode(0b0000_0000, 222, 222), // Because 255 (max int8) - 32 - 1. - expected: MouseClickMsg{x: 222, y: 222, button: MouseLeft}, + expected: MouseClickMsg{X: 222, Y: 222, Button: MouseLeft}, }, // Simple. { name: "left", buf: encode(0b0000_0000, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "left in motion", buf: encode(0b0010_0000, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "middle", buf: encode(0b0000_0001, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseMiddle}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseMiddle}, }, { name: "middle in motion", buf: encode(0b0010_0001, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseMiddle}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseMiddle}, }, { name: "right", buf: encode(0b0000_0010, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseRight}, }, { name: "right in motion", buf: encode(0b0010_0010, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseRight}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseRight}, }, { name: "motion", buf: encode(0b0010_0011, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseNone}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseNone}, }, { name: "wheel up", buf: encode(0b0100_0000, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, }, { name: "wheel down", buf: encode(0b0100_0001, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelDown}, }, { name: "wheel left", buf: encode(0b0100_0010, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelLeft}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelLeft}, }, { name: "wheel right", buf: encode(0b0100_0011, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelRight}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelRight}, }, { name: "release", buf: encode(0b0000_0011, 32, 16), - expected: MouseReleaseMsg{x: 32, y: 16, button: MouseNone}, + expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseNone}, }, { name: "backward", buf: encode(0b1000_0000, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseBackward}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseBackward}, }, { name: "forward", buf: encode(0b1000_0001, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseForward}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseForward}, }, { name: "button 10", buf: encode(0b1000_0010, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseExtra1}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseExtra1}, }, { name: "button 11", buf: encode(0b1000_0011, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, button: MouseExtra2}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseExtra2}, }, // Combinations. { name: "alt+right", buf: encode(0b0000_1010, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, }, { name: "ctrl+right", buf: encode(0b0001_0010, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, }, { name: "left in motion", buf: encode(0b0010_0000, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "alt+right in motion", buf: encode(0b0010_1010, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, + expected: MouseMotionMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, }, { name: "ctrl+right in motion", buf: encode(0b0011_0010, 32, 16), - expected: MouseMotionMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, + expected: MouseMotionMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, }, { name: "ctrl+alt+right", buf: encode(0b0001_1010, 32, 16), - expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight}, }, { name: "ctrl+wheel up", buf: encode(0b0101_0000, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModCtrl, button: MouseWheelUp}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelUp}, }, { name: "alt+wheel down", buf: encode(0b0100_1001, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown}, }, { name: "ctrl+alt+wheel down", buf: encode(0b0101_1001, 32, 16), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown}, }, // Overflow position. { name: "overflow position", buf: encode(0b0010_0000, 250, 223), // Because 255 (max int8) - 32 - 1. - expected: MouseMotionMsg{x: -6, y: -33, button: MouseLeft}, + expected: MouseMotionMsg{X: -6, Y: -33, Button: MouseLeft}, }, } @@ -326,134 +326,134 @@ func TestParseSGRMouseEvent(t *testing.T) { { name: "zero position", buf: encode(0, 0, 0, false), - expected: MouseClickMsg{x: 0, y: 0, button: MouseLeft}, + expected: MouseClickMsg{X: 0, Y: 0, Button: MouseLeft}, }, { name: "225 position", buf: encode(0, 225, 225, false), - expected: MouseClickMsg{x: 225, y: 225, button: MouseLeft}, + expected: MouseClickMsg{X: 225, Y: 225, Button: MouseLeft}, }, // Simple. { name: "left", buf: encode(0, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "left in motion", buf: encode(32, 32, 16, false), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "left", buf: encode(0, 32, 16, true), - expected: MouseReleaseMsg{x: 32, y: 16, button: MouseLeft}, + expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseLeft}, }, { name: "middle", buf: encode(1, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, button: MouseMiddle}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseMiddle}, }, { name: "middle in motion", buf: encode(33, 32, 16, false), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseMiddle}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseMiddle}, }, { name: "middle", buf: encode(1, 32, 16, true), - expected: MouseReleaseMsg{x: 32, y: 16, button: MouseMiddle}, + expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseMiddle}, }, { name: "right", buf: encode(2, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseRight}, }, { name: "right", buf: encode(2, 32, 16, true), - expected: MouseReleaseMsg{x: 32, y: 16, button: MouseRight}, + expected: MouseReleaseMsg{X: 32, Y: 16, Button: MouseRight}, }, { name: "motion", buf: encode(35, 32, 16, false), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseNone}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseNone}, }, { name: "wheel up", buf: encode(64, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelUp}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelUp}, }, { name: "wheel down", buf: encode(65, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelDown}, }, { name: "wheel left", buf: encode(66, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelLeft}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelLeft}, }, { name: "wheel right", buf: encode(67, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, button: MouseWheelRight}, + expected: MouseWheelMsg{X: 32, Y: 16, Button: MouseWheelRight}, }, { name: "backward", buf: encode(128, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, button: MouseBackward}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseBackward}, }, { name: "backward in motion", buf: encode(160, 32, 16, false), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseBackward}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseBackward}, }, { name: "forward", buf: encode(129, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, button: MouseForward}, + expected: MouseClickMsg{X: 32, Y: 16, Button: MouseForward}, }, { name: "forward in motion", buf: encode(161, 32, 16, false), - expected: MouseMotionMsg{x: 32, y: 16, button: MouseForward}, + expected: MouseMotionMsg{X: 32, Y: 16, Button: MouseForward}, }, // Combinations. { name: "alt+right", buf: encode(10, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseRight}, }, { name: "ctrl+right", buf: encode(18, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, mod: ModCtrl, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseRight}, }, { name: "ctrl+alt+right", buf: encode(26, 32, 16, false), - expected: MouseClickMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseRight}, + expected: MouseClickMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseRight}, }, { name: "alt+wheel", buf: encode(73, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt, Button: MouseWheelDown}, }, { name: "ctrl+wheel", buf: encode(81, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModCtrl, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModCtrl, Button: MouseWheelDown}, }, { name: "ctrl+alt+wheel", buf: encode(89, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModCtrl, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModCtrl, Button: MouseWheelDown}, }, { name: "ctrl+alt+shift+wheel", buf: encode(93, 32, 16, false), - expected: MouseWheelMsg{x: 32, y: 16, mod: ModAlt | ModShift | ModCtrl, button: MouseWheelDown}, + expected: MouseWheelMsg{X: 32, Y: 16, Mod: ModAlt | ModShift | ModCtrl, Button: MouseWheelDown}, }, } diff --git a/parse.go b/parse.go index c3d482deb9..bc5569319c 100644 --- a/parse.go +++ b/parse.go @@ -3,6 +3,7 @@ package tea import ( "encoding/base64" "strings" + "unicode" "unicode/utf8" "github.com/charmbracelet/x/ansi" @@ -108,7 +109,7 @@ func parseSequence(buf []byte) (n int, msg Msg) { case ansi.ESC: if len(buf) == 1 { // Escape key - return 1, KeyPressMsg{typ: KeyEscape} + return 1, KeyPressMsg{Code: KeyEscape} } switch b := buf[1]; b { @@ -125,13 +126,14 @@ func parseSequence(buf []byte) (n int, msg Msg) { default: n, e := parseSequence(buf[1:]) if k, ok := e.(KeyPressMsg); ok { - k.mod |= ModAlt + k.Text = "" + k.Mod |= ModAlt return n + 1, k } // Not a key sequence, nor an alt modified key sequence. In that // case, just report a single escape key. - return 1, KeyPressMsg{typ: KeyEscape} + return 1, KeyPressMsg{Code: KeyEscape} } case ansi.SS3: return parseSs3(buf) @@ -150,7 +152,8 @@ func parseSequence(buf []byte) (n int, msg Msg) { // C1 control code // UTF-8 never starts with a C1 control code // Encode these as Ctrl+Alt+ - return 1, KeyPressMsg{runes: []rune{rune(b) - 0x40}, mod: ModCtrl | ModAlt} + code := rune(b) - 0x40 + return 1, KeyPressMsg{Code: code, Mod: ModCtrl | ModAlt} } return parseUtf8(buf) } @@ -159,7 +162,7 @@ func parseSequence(buf []byte) (n int, msg Msg) { func parseCsi(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+[ key - return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} + return 2, KeyPressMsg{Text: string(rune(b[1])), Mod: ModAlt} } var csi ansi.CsiSequence @@ -223,7 +226,7 @@ func parseCsi(b []byte) (int, Msg) { if b[i-1] == '$' { n, ev := parseCsi(append(b[:i-1], '~')) if k, ok := ev.(KeyPressMsg); ok { - k.mod |= ModShift + k.Mod |= ModShift return n, k } } @@ -280,7 +283,7 @@ func parseCsi(b []byte) (int, Msg) { // // For a non ambiguous cursor position report, use // [ansi.RequestExtendedCursorPosition] (DECXCPR) instead. - return i, multiMsg{KeyPressMsg{typ: KeyF3, mod: KeyMod(csi.Param(1) - 1)}, m} + return i, multiMsg{KeyPressMsg{Code: KeyF3, Mod: KeyMod(csi.Param(1) - 1)}, m} } return i, m @@ -296,23 +299,23 @@ func parseCsi(b []byte) (int, Msg) { var k KeyPressMsg switch cmd { case 'a', 'b', 'c', 'd': - k = KeyPressMsg{typ: KeyUp + KeyType(cmd-'a'), mod: ModShift} + k = KeyPressMsg{Code: KeyUp + rune(cmd-'a'), Mod: ModShift} case 'A', 'B', 'C', 'D': - k = KeyPressMsg{typ: KeyUp + KeyType(cmd-'A')} + k = KeyPressMsg{Code: KeyUp + rune(cmd-'A')} case 'E': - k = KeyPressMsg{typ: KeyBegin} + k = KeyPressMsg{Code: KeyBegin} case 'F': - k = KeyPressMsg{typ: KeyEnd} + k = KeyPressMsg{Code: KeyEnd} case 'H': - k = KeyPressMsg{typ: KeyHome} + k = KeyPressMsg{Code: KeyHome} case 'P', 'Q', 'R', 'S': - k = KeyPressMsg{typ: KeyF1 + KeyType(cmd-'P')} + k = KeyPressMsg{Code: KeyF1 + rune(cmd-'P')} case 'Z': - k = KeyPressMsg{typ: KeyTab, mod: ModShift} + k = KeyPressMsg{Code: KeyTab, Mod: ModShift} } if paramsLen > 1 && csi.Param(0) == 1 && csi.Param(1) != -1 { // CSI 1 ; A - k.mod |= KeyMod(csi.Param(1) - 1) + k.Mod |= KeyMod(csi.Param(1) - 1) } return i, k case 'M': @@ -392,51 +395,51 @@ func parseCsi(b []byte) (int, Msg) { switch param { case 1: if flags&_FlagFind != 0 { - k = KeyPressMsg{typ: KeyFind} + k = KeyPressMsg{Code: KeyFind} } else { - k = KeyPressMsg{typ: KeyHome} + k = KeyPressMsg{Code: KeyHome} } case 2: - k = KeyPressMsg{typ: KeyInsert} + k = KeyPressMsg{Code: KeyInsert} case 3: - k = KeyPressMsg{typ: KeyDelete} + k = KeyPressMsg{Code: KeyDelete} case 4: if flags&_FlagSelect != 0 { - k = KeyPressMsg{typ: KeySelect} + k = KeyPressMsg{Code: KeySelect} } else { - k = KeyPressMsg{typ: KeyEnd} + k = KeyPressMsg{Code: KeyEnd} } case 5: - k = KeyPressMsg{typ: KeyPgUp} + k = KeyPressMsg{Code: KeyPgUp} case 6: - k = KeyPressMsg{typ: KeyPgDown} + k = KeyPressMsg{Code: KeyPgDown} case 7: - k = KeyPressMsg{typ: KeyHome} + k = KeyPressMsg{Code: KeyHome} case 8: - k = KeyPressMsg{typ: KeyEnd} + k = KeyPressMsg{Code: KeyEnd} case 11, 12, 13, 14, 15: - k = KeyPressMsg{typ: KeyF1 + KeyType(param-11)} + k = KeyPressMsg{Code: KeyF1 + rune(param-11)} case 17, 18, 19, 20, 21: - k = KeyPressMsg{typ: KeyF6 + KeyType(param-17)} + k = KeyPressMsg{Code: KeyF6 + rune(param-17)} case 23, 24, 25, 26: - k = KeyPressMsg{typ: KeyF11 + KeyType(param-23)} + k = KeyPressMsg{Code: KeyF11 + rune(param-23)} case 28, 29: - k = KeyPressMsg{typ: KeyF15 + KeyType(param-28)} + k = KeyPressMsg{Code: KeyF15 + rune(param-28)} case 31, 32, 33, 34: - k = KeyPressMsg{typ: KeyF17 + KeyType(param-31)} + k = KeyPressMsg{Code: KeyF17 + rune(param-31)} } // modifiers if paramsLen > 1 && csi.Param(1) != -1 { - k.mod |= KeyMod(csi.Param(1) - 1) + k.Mod |= KeyMod(csi.Param(1) - 1) } // Handle URxvt weird keys switch cmd { case '^': - k.mod |= ModCtrl + k.Mod |= ModCtrl case '@': - k.mod |= ModCtrl | ModShift + k.Mod |= ModCtrl | ModShift } return i, k @@ -450,7 +453,7 @@ func parseCsi(b []byte) (int, Msg) { func parseSs3(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+O key - return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} + return 2, KeyPressMsg{Code: rune(b[1]), Mod: ModAlt} } var i int @@ -482,30 +485,30 @@ func parseSs3(b []byte) (int, Msg) { var k KeyPressMsg switch gl { case 'a', 'b', 'c', 'd': - k = KeyPressMsg{typ: KeyUp + KeyType(gl-'a'), mod: ModCtrl} + k = KeyPressMsg{Code: KeyUp + rune(gl-'a'), Mod: ModCtrl} case 'A', 'B', 'C', 'D': - k = KeyPressMsg{typ: KeyUp + KeyType(gl-'A')} + k = KeyPressMsg{Code: KeyUp + rune(gl-'A')} case 'E': - k = KeyPressMsg{typ: KeyBegin} + k = KeyPressMsg{Code: KeyBegin} case 'F': - k = KeyPressMsg{typ: KeyEnd} + k = KeyPressMsg{Code: KeyEnd} case 'H': - k = KeyPressMsg{typ: KeyHome} + k = KeyPressMsg{Code: KeyHome} case 'P', 'Q', 'R', 'S': - k = KeyPressMsg{typ: KeyF1 + KeyType(gl-'P')} + k = KeyPressMsg{Code: KeyF1 + rune(gl-'P')} case 'M': - k = KeyPressMsg{typ: KeyKpEnter} + k = KeyPressMsg{Code: KeyKpEnter} case 'X': - k = KeyPressMsg{typ: KeyKpEqual} + k = KeyPressMsg{Code: KeyKpEqual} case 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y': - k = KeyPressMsg{typ: KeyKpMultiply + KeyType(gl-'j')} + k = KeyPressMsg{Code: KeyKpMultiply + rune(gl-'j')} default: return i, UnknownMsg(b[:i]) } // Handle weird SS3 Func if mod > 0 { - k.mod |= KeyMod(mod - 1) + k.Mod |= KeyMod(mod - 1) } return i, k @@ -514,7 +517,7 @@ func parseSs3(b []byte) (int, Msg) { func parseOsc(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+] key - return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} + return 2, KeyPressMsg{Code: rune(b[1]), Mod: ModAlt} } var i int @@ -639,7 +642,7 @@ func parseStTerminated(intro8, intro7 byte) func([]byte) (int, Msg) { func parseDcs(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+P key - return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} + return 2, KeyPressMsg{Code: rune(b[1]), Mod: ModAlt} } var params [16]int @@ -751,7 +754,7 @@ func parseDcs(b []byte) (int, Msg) { func parseApc(b []byte) (int, Msg) { if len(b) == 2 && b[0] == ansi.ESC { // short cut if this is an alt+_ key - return 2, KeyPressMsg{runes: []rune{rune(b[1])}, mod: ModAlt} + return 2, KeyPressMsg{Code: rune(b[1]), Mod: ModAlt} } // APC sequences are introduced by APC (0x9f) or ESC _ (0x1b 0x5f) @@ -769,54 +772,65 @@ func parseUtf8(b []byte) (int, Msg) { return 1, parseControl(c) } else if c > ansi.US && c < ansi.DEL { // ASCII printable characters - return 1, KeyPressMsg{runes: []rune{rune(c)}} + code := rune(c) + k := KeyPressMsg{Code: code, Text: string(code)} + if unicode.IsUpper(code) { + k.Code = unicode.ToLower(code) + k.ShiftedCode = code + k.Mod |= ModShift + } + + return 1, k } - if r, _ := utf8.DecodeRune(b); r == utf8.RuneError { + r, _ := utf8.DecodeRune(b) + if r == utf8.RuneError { return 1, UnknownMsg(b[0]) } cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) - return len(cluster), KeyPressMsg{runes: []rune(string(cluster))} + return len(cluster), KeyPressMsg{Code: r, Text: string(cluster)} } func parseControl(b byte) Msg { switch b { case ansi.NUL: if flags&_FlagCtrlAt != 0 { - return KeyPressMsg{runes: []rune{'@'}, mod: ModCtrl} + return KeyPressMsg{Code: '@', Mod: ModCtrl} } - return KeyPressMsg{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl} + return KeyPressMsg{Code: KeySpace, Mod: ModCtrl} case ansi.BS: - return KeyPressMsg{runes: []rune{'h'}, mod: ModCtrl} + return KeyPressMsg{Code: 'h', Mod: ModCtrl} case ansi.HT: if flags&_FlagCtrlI != 0 { - return KeyPressMsg{runes: []rune{'i'}, mod: ModCtrl} + return KeyPressMsg{Code: 'i', Mod: ModCtrl} } - return KeyPressMsg{typ: KeyTab} + return KeyPressMsg{Code: KeyTab} case ansi.CR: if flags&_FlagCtrlM != 0 { - return KeyPressMsg{runes: []rune{'m'}, mod: ModCtrl} + return KeyPressMsg{Code: 'm', Mod: ModCtrl} } - return KeyPressMsg{typ: KeyEnter} + return KeyPressMsg{Code: KeyEnter} case ansi.ESC: if flags&_FlagCtrlOpenBracket != 0 { - return KeyPressMsg{runes: []rune{'['}, mod: ModCtrl} + return KeyPressMsg{Code: '[', Mod: ModCtrl} } - return KeyPressMsg{typ: KeyEscape} + return KeyPressMsg{Code: KeyEscape} case ansi.DEL: if flags&_FlagBackspace != 0 { - return KeyPressMsg{typ: KeyDelete} + return KeyPressMsg{Code: KeyDelete} } - return KeyPressMsg{typ: KeyBackspace} + return KeyPressMsg{Code: KeyBackspace} case ansi.SP: - return KeyPressMsg{typ: KeySpace, runes: []rune{' '}} + return KeyPressMsg{Code: KeySpace, Text: " "} default: if b >= ansi.SOH && b <= ansi.SUB { // Use lower case letters for control codes - return KeyPressMsg{runes: []rune{rune(b + 0x60)}, mod: ModCtrl} + code := rune(b + 0x60) + return KeyPressMsg{Code: code, Mod: ModCtrl} } else if b >= ansi.FS && b <= ansi.US { - return KeyPressMsg{runes: []rune{rune(b + 0x40)}, mod: ModCtrl} + code := rune(b + 0x40) + return KeyPressMsg{Code: code, Mod: ModCtrl} } return UnknownMsg(b) } diff --git a/parse_test.go b/parse_test.go index 7be9340452..e74e5301d7 100644 --- a/parse_test.go +++ b/parse_test.go @@ -9,14 +9,14 @@ import ( func TestParseSequence_Events(t *testing.T) { input := []byte("\x1b\x1b[Ztest\x00\x1b]10;rgb:1234/1234/1234\x07\x1b[27;2;27~\x1b[?1049;2$y") want := []Msg{ - KeyPressMsg{typ: KeyTab, mod: ModShift | ModAlt}, - KeyPressMsg{typ: KeyRunes, runes: []rune{'t'}}, - KeyPressMsg{typ: KeyRunes, runes: []rune{'e'}}, - KeyPressMsg{typ: KeyRunes, runes: []rune{'s'}}, - KeyPressMsg{typ: KeyRunes, runes: []rune{'t'}}, - KeyPressMsg{typ: KeySpace, runes: []rune{' '}, mod: ModCtrl}, + KeyPressMsg{Code: KeyTab, Mod: ModShift | ModAlt}, + KeyPressMsg{Code: 't', Text: "t"}, + KeyPressMsg{Code: 'e', Text: "e"}, + KeyPressMsg{Code: 's', Text: "s"}, + KeyPressMsg{Code: 't', Text: "t"}, + KeyPressMsg{Code: KeySpace, Mod: ModCtrl}, ForegroundColorMsg{color.RGBA{R: 0x12, G: 0x12, B: 0x12, A: 0xff}}, - KeyPressMsg{typ: KeyEscape, mod: ModShift}, + KeyPressMsg{Code: KeyEscape, Mod: ModShift}, ReportModeMsg{Mode: 1049, Value: 2}, } for i := 0; len(input) != 0; i++ { @@ -25,7 +25,7 @@ func TestParseSequence_Events(t *testing.T) { } n, got := parseSequence(input) if !reflect.DeepEqual(got, want[i]) { - t.Errorf("got %v (%T), want %v (%T)", got, got, want[i], want[i]) + t.Errorf("got %#v (%T), want %#v (%T)", got, got, want[i], want[i]) } input = input[n:] } diff --git a/table.go b/table.go index 04e186ce6c..51afed174b 100644 --- a/table.go +++ b/table.go @@ -9,40 +9,40 @@ import ( // buildKeysTable builds a table of key sequences and their corresponding key // events based on the VT100/VT200, XTerm, and Urxvt terminal specs. // TODO: Use flags? -func buildKeysTable(flags int, term string) map[string]key { - nul := key{runes: []rune{' '}, typ: KeySpace, mod: ModCtrl} // ctrl+@ or ctrl+space +func buildKeysTable(flags int, term string) map[string]Key { + nul := Key{Code: KeySpace, Mod: ModCtrl} // ctrl+@ or ctrl+space if flags&_FlagCtrlAt != 0 { - nul = key{runes: []rune{'@'}, mod: ModCtrl} + nul = Key{Code: '@', Mod: ModCtrl} } - tab := key{typ: KeyTab} // ctrl+i or tab + tab := Key{Code: KeyTab} // ctrl+i or tab if flags&_FlagCtrlI != 0 { - tab = key{runes: []rune{'i'}, mod: ModCtrl} + tab = Key{Code: 'i', Mod: ModCtrl} } - enter := key{typ: KeyEnter} // ctrl+m or enter + enter := Key{Code: KeyEnter} // ctrl+m or enter if flags&_FlagCtrlM != 0 { - enter = key{runes: []rune{'m'}, mod: ModCtrl} + enter = Key{Code: 'm', Mod: ModCtrl} } - esc := key{typ: KeyEscape} // ctrl+[ or escape + esc := Key{Code: KeyEscape} // ctrl+[ or escape if flags&_FlagCtrlOpenBracket != 0 { - esc = key{runes: []rune{'['}, mod: ModCtrl} // ctrl+[ or escape + esc = Key{Code: '[', Mod: ModCtrl} // ctrl+[ or escape } - del := key{typ: KeyBackspace} + del := Key{Code: KeyBackspace} if flags&_FlagBackspace != 0 { - del.typ = KeyDelete + del.Code = KeyDelete } - find := key{typ: KeyHome} + find := Key{Code: KeyHome} if flags&_FlagFind != 0 { - find.typ = KeyFind + find.Code = KeyFind } - sel := key{typ: KeyEnd} + sel := Key{Code: KeyEnd} if flags&_FlagSelect != 0 { - sel.typ = KeySelect + sel.Code = KeySelect } // The following is a table of key sequences and their corresponding key @@ -53,158 +53,158 @@ func buildKeysTable(flags int, term string) map[string]key { // // XXX: These keys may be overwritten by other options like XTerm or // Terminfo. - table := map[string]key{ + table := map[string]Key{ // C0 control characters string(byte(ansi.NUL)): nul, - string(byte(ansi.SOH)): {runes: []rune{'a'}, mod: ModCtrl}, - string(byte(ansi.STX)): {runes: []rune{'b'}, mod: ModCtrl}, - string(byte(ansi.ETX)): {runes: []rune{'c'}, mod: ModCtrl}, - string(byte(ansi.EOT)): {runes: []rune{'d'}, mod: ModCtrl}, - string(byte(ansi.ENQ)): {runes: []rune{'e'}, mod: ModCtrl}, - string(byte(ansi.ACK)): {runes: []rune{'f'}, mod: ModCtrl}, - string(byte(ansi.BEL)): {runes: []rune{'g'}, mod: ModCtrl}, - string(byte(ansi.BS)): {runes: []rune{'h'}, mod: ModCtrl}, + string(byte(ansi.SOH)): {Code: 'a', Mod: ModCtrl}, + string(byte(ansi.STX)): {Code: 'b', Mod: ModCtrl}, + string(byte(ansi.ETX)): {Code: 'c', Mod: ModCtrl}, + string(byte(ansi.EOT)): {Code: 'd', Mod: ModCtrl}, + string(byte(ansi.ENQ)): {Code: 'e', Mod: ModCtrl}, + string(byte(ansi.ACK)): {Code: 'f', Mod: ModCtrl}, + string(byte(ansi.BEL)): {Code: 'g', Mod: ModCtrl}, + string(byte(ansi.BS)): {Code: 'h', Mod: ModCtrl}, string(byte(ansi.HT)): tab, - string(byte(ansi.LF)): {runes: []rune{'j'}, mod: ModCtrl}, - string(byte(ansi.VT)): {runes: []rune{'k'}, mod: ModCtrl}, - string(byte(ansi.FF)): {runes: []rune{'l'}, mod: ModCtrl}, + string(byte(ansi.LF)): {Code: 'j', Mod: ModCtrl}, + string(byte(ansi.VT)): {Code: 'k', Mod: ModCtrl}, + string(byte(ansi.FF)): {Code: 'l', Mod: ModCtrl}, string(byte(ansi.CR)): enter, - string(byte(ansi.SO)): {runes: []rune{'n'}, mod: ModCtrl}, - string(byte(ansi.SI)): {runes: []rune{'o'}, mod: ModCtrl}, - string(byte(ansi.DLE)): {runes: []rune{'p'}, mod: ModCtrl}, - string(byte(ansi.DC1)): {runes: []rune{'q'}, mod: ModCtrl}, - string(byte(ansi.DC2)): {runes: []rune{'r'}, mod: ModCtrl}, - string(byte(ansi.DC3)): {runes: []rune{'s'}, mod: ModCtrl}, - string(byte(ansi.DC4)): {runes: []rune{'t'}, mod: ModCtrl}, - string(byte(ansi.NAK)): {runes: []rune{'u'}, mod: ModCtrl}, - string(byte(ansi.SYN)): {runes: []rune{'v'}, mod: ModCtrl}, - string(byte(ansi.ETB)): {runes: []rune{'w'}, mod: ModCtrl}, - string(byte(ansi.CAN)): {runes: []rune{'x'}, mod: ModCtrl}, - string(byte(ansi.EM)): {runes: []rune{'y'}, mod: ModCtrl}, - string(byte(ansi.SUB)): {runes: []rune{'z'}, mod: ModCtrl}, + string(byte(ansi.SO)): {Code: 'n', Mod: ModCtrl}, + string(byte(ansi.SI)): {Code: 'o', Mod: ModCtrl}, + string(byte(ansi.DLE)): {Code: 'p', Mod: ModCtrl}, + string(byte(ansi.DC1)): {Code: 'q', Mod: ModCtrl}, + string(byte(ansi.DC2)): {Code: 'r', Mod: ModCtrl}, + string(byte(ansi.DC3)): {Code: 's', Mod: ModCtrl}, + string(byte(ansi.DC4)): {Code: 't', Mod: ModCtrl}, + string(byte(ansi.NAK)): {Code: 'u', Mod: ModCtrl}, + string(byte(ansi.SYN)): {Code: 'v', Mod: ModCtrl}, + string(byte(ansi.ETB)): {Code: 'w', Mod: ModCtrl}, + string(byte(ansi.CAN)): {Code: 'x', Mod: ModCtrl}, + string(byte(ansi.EM)): {Code: 'y', Mod: ModCtrl}, + string(byte(ansi.SUB)): {Code: 'z', Mod: ModCtrl}, string(byte(ansi.ESC)): esc, - string(byte(ansi.FS)): {runes: []rune{'\\'}, mod: ModCtrl}, - string(byte(ansi.GS)): {runes: []rune{']'}, mod: ModCtrl}, - string(byte(ansi.RS)): {runes: []rune{'^'}, mod: ModCtrl}, - string(byte(ansi.US)): {runes: []rune{'_'}, mod: ModCtrl}, + string(byte(ansi.FS)): {Code: '\\', Mod: ModCtrl}, + string(byte(ansi.GS)): {Code: ']', Mod: ModCtrl}, + string(byte(ansi.RS)): {Code: '^', Mod: ModCtrl}, + string(byte(ansi.US)): {Code: '_', Mod: ModCtrl}, // Special keys in G0 - string(byte(ansi.SP)): {typ: KeySpace, runes: []rune{' '}}, + string(byte(ansi.SP)): {Code: KeySpace, Text: " "}, string(byte(ansi.DEL)): del, // Special keys - "\x1b[Z": {typ: KeyTab, mod: ModShift}, + "\x1b[Z": {Code: KeyTab, Mod: ModShift}, "\x1b[1~": find, - "\x1b[2~": {typ: KeyInsert}, - "\x1b[3~": {typ: KeyDelete}, + "\x1b[2~": {Code: KeyInsert}, + "\x1b[3~": {Code: KeyDelete}, "\x1b[4~": sel, - "\x1b[5~": {typ: KeyPgUp}, - "\x1b[6~": {typ: KeyPgDown}, - "\x1b[7~": {typ: KeyHome}, - "\x1b[8~": {typ: KeyEnd}, + "\x1b[5~": {Code: KeyPgUp}, + "\x1b[6~": {Code: KeyPgDown}, + "\x1b[7~": {Code: KeyHome}, + "\x1b[8~": {Code: KeyEnd}, // Normal mode - "\x1b[A": {typ: KeyUp}, - "\x1b[B": {typ: KeyDown}, - "\x1b[C": {typ: KeyRight}, - "\x1b[D": {typ: KeyLeft}, - "\x1b[E": {typ: KeyBegin}, - "\x1b[F": {typ: KeyEnd}, - "\x1b[H": {typ: KeyHome}, - "\x1b[P": {typ: KeyF1}, - "\x1b[Q": {typ: KeyF2}, - "\x1b[R": {typ: KeyF3}, - "\x1b[S": {typ: KeyF4}, + "\x1b[A": {Code: KeyUp}, + "\x1b[B": {Code: KeyDown}, + "\x1b[C": {Code: KeyRight}, + "\x1b[D": {Code: KeyLeft}, + "\x1b[E": {Code: KeyBegin}, + "\x1b[F": {Code: KeyEnd}, + "\x1b[H": {Code: KeyHome}, + "\x1b[P": {Code: KeyF1}, + "\x1b[Q": {Code: KeyF2}, + "\x1b[R": {Code: KeyF3}, + "\x1b[S": {Code: KeyF4}, // Application Cursor Key Mode (DECCKM) - "\x1bOA": {typ: KeyUp}, - "\x1bOB": {typ: KeyDown}, - "\x1bOC": {typ: KeyRight}, - "\x1bOD": {typ: KeyLeft}, - "\x1bOE": {typ: KeyBegin}, - "\x1bOF": {typ: KeyEnd}, - "\x1bOH": {typ: KeyHome}, - "\x1bOP": {typ: KeyF1}, - "\x1bOQ": {typ: KeyF2}, - "\x1bOR": {typ: KeyF3}, - "\x1bOS": {typ: KeyF4}, + "\x1bOA": {Code: KeyUp}, + "\x1bOB": {Code: KeyDown}, + "\x1bOC": {Code: KeyRight}, + "\x1bOD": {Code: KeyLeft}, + "\x1bOE": {Code: KeyBegin}, + "\x1bOF": {Code: KeyEnd}, + "\x1bOH": {Code: KeyHome}, + "\x1bOP": {Code: KeyF1}, + "\x1bOQ": {Code: KeyF2}, + "\x1bOR": {Code: KeyF3}, + "\x1bOS": {Code: KeyF4}, // Keypad Application Mode (DECKPAM) - "\x1bOM": {typ: KeyKpEnter}, - "\x1bOX": {typ: KeyKpEqual}, - "\x1bOj": {typ: KeyKpMultiply}, - "\x1bOk": {typ: KeyKpPlus}, - "\x1bOl": {typ: KeyKpComma}, - "\x1bOm": {typ: KeyKpMinus}, - "\x1bOn": {typ: KeyKpDecimal}, - "\x1bOo": {typ: KeyKpDivide}, - "\x1bOp": {typ: KeyKp0}, - "\x1bOq": {typ: KeyKp1}, - "\x1bOr": {typ: KeyKp2}, - "\x1bOs": {typ: KeyKp3}, - "\x1bOt": {typ: KeyKp4}, - "\x1bOu": {typ: KeyKp5}, - "\x1bOv": {typ: KeyKp6}, - "\x1bOw": {typ: KeyKp7}, - "\x1bOx": {typ: KeyKp8}, - "\x1bOy": {typ: KeyKp9}, + "\x1bOM": {Code: KeyKpEnter}, + "\x1bOX": {Code: KeyKpEqual}, + "\x1bOj": {Code: KeyKpMultiply}, + "\x1bOk": {Code: KeyKpPlus}, + "\x1bOl": {Code: KeyKpComma}, + "\x1bOm": {Code: KeyKpMinus}, + "\x1bOn": {Code: KeyKpDecimal}, + "\x1bOo": {Code: KeyKpDivide}, + "\x1bOp": {Code: KeyKp0}, + "\x1bOq": {Code: KeyKp1}, + "\x1bOr": {Code: KeyKp2}, + "\x1bOs": {Code: KeyKp3}, + "\x1bOt": {Code: KeyKp4}, + "\x1bOu": {Code: KeyKp5}, + "\x1bOv": {Code: KeyKp6}, + "\x1bOw": {Code: KeyKp7}, + "\x1bOx": {Code: KeyKp8}, + "\x1bOy": {Code: KeyKp9}, // Function keys - "\x1b[11~": {typ: KeyF1}, - "\x1b[12~": {typ: KeyF2}, - "\x1b[13~": {typ: KeyF3}, - "\x1b[14~": {typ: KeyF4}, - "\x1b[15~": {typ: KeyF5}, - "\x1b[17~": {typ: KeyF6}, - "\x1b[18~": {typ: KeyF7}, - "\x1b[19~": {typ: KeyF8}, - "\x1b[20~": {typ: KeyF9}, - "\x1b[21~": {typ: KeyF10}, - "\x1b[23~": {typ: KeyF11}, - "\x1b[24~": {typ: KeyF12}, - "\x1b[25~": {typ: KeyF13}, - "\x1b[26~": {typ: KeyF14}, - "\x1b[28~": {typ: KeyF15}, - "\x1b[29~": {typ: KeyF16}, - "\x1b[31~": {typ: KeyF17}, - "\x1b[32~": {typ: KeyF18}, - "\x1b[33~": {typ: KeyF19}, - "\x1b[34~": {typ: KeyF20}, + "\x1b[11~": {Code: KeyF1}, + "\x1b[12~": {Code: KeyF2}, + "\x1b[13~": {Code: KeyF3}, + "\x1b[14~": {Code: KeyF4}, + "\x1b[15~": {Code: KeyF5}, + "\x1b[17~": {Code: KeyF6}, + "\x1b[18~": {Code: KeyF7}, + "\x1b[19~": {Code: KeyF8}, + "\x1b[20~": {Code: KeyF9}, + "\x1b[21~": {Code: KeyF10}, + "\x1b[23~": {Code: KeyF11}, + "\x1b[24~": {Code: KeyF12}, + "\x1b[25~": {Code: KeyF13}, + "\x1b[26~": {Code: KeyF14}, + "\x1b[28~": {Code: KeyF15}, + "\x1b[29~": {Code: KeyF16}, + "\x1b[31~": {Code: KeyF17}, + "\x1b[32~": {Code: KeyF18}, + "\x1b[33~": {Code: KeyF19}, + "\x1b[34~": {Code: KeyF20}, } // CSI ~ sequence keys - csiTildeKeys := map[string]key{ - "1": find, "2": {typ: KeyInsert}, - "3": {typ: KeyDelete}, "4": sel, - "5": {typ: KeyPgUp}, "6": {typ: KeyPgDown}, - "7": {typ: KeyHome}, "8": {typ: KeyEnd}, + csiTildeKeys := map[string]Key{ + "1": find, "2": {Code: KeyInsert}, + "3": {Code: KeyDelete}, "4": sel, + "5": {Code: KeyPgUp}, "6": {Code: KeyPgDown}, + "7": {Code: KeyHome}, "8": {Code: KeyEnd}, // There are no 9 and 10 keys - "11": {typ: KeyF1}, "12": {typ: KeyF2}, - "13": {typ: KeyF3}, "14": {typ: KeyF4}, - "15": {typ: KeyF5}, "17": {typ: KeyF6}, - "18": {typ: KeyF7}, "19": {typ: KeyF8}, - "20": {typ: KeyF9}, "21": {typ: KeyF10}, - "23": {typ: KeyF11}, "24": {typ: KeyF12}, - "25": {typ: KeyF13}, "26": {typ: KeyF14}, - "28": {typ: KeyF15}, "29": {typ: KeyF16}, - "31": {typ: KeyF17}, "32": {typ: KeyF18}, - "33": {typ: KeyF19}, "34": {typ: KeyF20}, + "11": {Code: KeyF1}, "12": {Code: KeyF2}, + "13": {Code: KeyF3}, "14": {Code: KeyF4}, + "15": {Code: KeyF5}, "17": {Code: KeyF6}, + "18": {Code: KeyF7}, "19": {Code: KeyF8}, + "20": {Code: KeyF9}, "21": {Code: KeyF10}, + "23": {Code: KeyF11}, "24": {Code: KeyF12}, + "25": {Code: KeyF13}, "26": {Code: KeyF14}, + "28": {Code: KeyF15}, "29": {Code: KeyF16}, + "31": {Code: KeyF17}, "32": {Code: KeyF18}, + "33": {Code: KeyF19}, "34": {Code: KeyF20}, } // URxvt keys // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes - table["\x1b[a"] = key{typ: KeyUp, mod: ModShift} - table["\x1b[b"] = key{typ: KeyDown, mod: ModShift} - table["\x1b[c"] = key{typ: KeyRight, mod: ModShift} - table["\x1b[d"] = key{typ: KeyLeft, mod: ModShift} - table["\x1bOa"] = key{typ: KeyUp, mod: ModCtrl} - table["\x1bOb"] = key{typ: KeyDown, mod: ModCtrl} - table["\x1bOc"] = key{typ: KeyRight, mod: ModCtrl} - table["\x1bOd"] = key{typ: KeyLeft, mod: ModCtrl} + table["\x1b[a"] = Key{Code: KeyUp, Mod: ModShift} + table["\x1b[b"] = Key{Code: KeyDown, Mod: ModShift} + table["\x1b[c"] = Key{Code: KeyRight, Mod: ModShift} + table["\x1b[d"] = Key{Code: KeyLeft, Mod: ModShift} + table["\x1bOa"] = Key{Code: KeyUp, Mod: ModCtrl} + table["\x1bOb"] = Key{Code: KeyDown, Mod: ModCtrl} + table["\x1bOc"] = Key{Code: KeyRight, Mod: ModCtrl} + table["\x1bOd"] = Key{Code: KeyLeft, Mod: ModCtrl} // TODO: invistigate if shift-ctrl arrow keys collide with DECCKM keys i.e. // "\x1bOA", "\x1bOB", "\x1bOC", "\x1bOD" @@ -213,13 +213,13 @@ func buildKeysTable(flags int, term string) map[string]key { key := v // Normal (no modifier) already defined part of VT100/VT200 // Shift modifier - key.mod = ModShift + key.Mod = ModShift table["\x1b["+k+"$"] = key // Ctrl modifier - key.mod = ModCtrl + key.Mod = ModCtrl table["\x1b["+k+"^"] = key // Shift-Ctrl modifier - key.mod = ModShift | ModCtrl + key.Mod = ModShift | ModCtrl table["\x1b["+k+"@"] = key } @@ -232,54 +232,55 @@ func buildKeysTable(flags int, term string) map[string]key { // different escapes like XTerm, or switch to a better terminal ¯\_(ツ)_/¯ // // See https://manpages.ubuntu.com/manpages/trusty/man7/urxvt.7.html#key%20codes - table["\x1b[23$"] = key{typ: KeyF11, mod: ModShift} - table["\x1b[24$"] = key{typ: KeyF12, mod: ModShift} - table["\x1b[25$"] = key{typ: KeyF13, mod: ModShift} - table["\x1b[26$"] = key{typ: KeyF14, mod: ModShift} - table["\x1b[28$"] = key{typ: KeyF15, mod: ModShift} - table["\x1b[29$"] = key{typ: KeyF16, mod: ModShift} - table["\x1b[31$"] = key{typ: KeyF17, mod: ModShift} - table["\x1b[32$"] = key{typ: KeyF18, mod: ModShift} - table["\x1b[33$"] = key{typ: KeyF19, mod: ModShift} - table["\x1b[34$"] = key{typ: KeyF20, mod: ModShift} - table["\x1b[11^"] = key{typ: KeyF1, mod: ModCtrl} - table["\x1b[12^"] = key{typ: KeyF2, mod: ModCtrl} - table["\x1b[13^"] = key{typ: KeyF3, mod: ModCtrl} - table["\x1b[14^"] = key{typ: KeyF4, mod: ModCtrl} - table["\x1b[15^"] = key{typ: KeyF5, mod: ModCtrl} - table["\x1b[17^"] = key{typ: KeyF6, mod: ModCtrl} - table["\x1b[18^"] = key{typ: KeyF7, mod: ModCtrl} - table["\x1b[19^"] = key{typ: KeyF8, mod: ModCtrl} - table["\x1b[20^"] = key{typ: KeyF9, mod: ModCtrl} - table["\x1b[21^"] = key{typ: KeyF10, mod: ModCtrl} - table["\x1b[23^"] = key{typ: KeyF11, mod: ModCtrl} - table["\x1b[24^"] = key{typ: KeyF12, mod: ModCtrl} - table["\x1b[25^"] = key{typ: KeyF13, mod: ModCtrl} - table["\x1b[26^"] = key{typ: KeyF14, mod: ModCtrl} - table["\x1b[28^"] = key{typ: KeyF15, mod: ModCtrl} - table["\x1b[29^"] = key{typ: KeyF16, mod: ModCtrl} - table["\x1b[31^"] = key{typ: KeyF17, mod: ModCtrl} - table["\x1b[32^"] = key{typ: KeyF18, mod: ModCtrl} - table["\x1b[33^"] = key{typ: KeyF19, mod: ModCtrl} - table["\x1b[34^"] = key{typ: KeyF20, mod: ModCtrl} - table["\x1b[23@"] = key{typ: KeyF11, mod: ModShift | ModCtrl} - table["\x1b[24@"] = key{typ: KeyF12, mod: ModShift | ModCtrl} - table["\x1b[25@"] = key{typ: KeyF13, mod: ModShift | ModCtrl} - table["\x1b[26@"] = key{typ: KeyF14, mod: ModShift | ModCtrl} - table["\x1b[28@"] = key{typ: KeyF15, mod: ModShift | ModCtrl} - table["\x1b[29@"] = key{typ: KeyF16, mod: ModShift | ModCtrl} - table["\x1b[31@"] = key{typ: KeyF17, mod: ModShift | ModCtrl} - table["\x1b[32@"] = key{typ: KeyF18, mod: ModShift | ModCtrl} - table["\x1b[33@"] = key{typ: KeyF19, mod: ModShift | ModCtrl} - table["\x1b[34@"] = key{typ: KeyF20, mod: ModShift | ModCtrl} + table["\x1b[23$"] = Key{Code: KeyF11, Mod: ModShift} + table["\x1b[24$"] = Key{Code: KeyF12, Mod: ModShift} + table["\x1b[25$"] = Key{Code: KeyF13, Mod: ModShift} + table["\x1b[26$"] = Key{Code: KeyF14, Mod: ModShift} + table["\x1b[28$"] = Key{Code: KeyF15, Mod: ModShift} + table["\x1b[29$"] = Key{Code: KeyF16, Mod: ModShift} + table["\x1b[31$"] = Key{Code: KeyF17, Mod: ModShift} + table["\x1b[32$"] = Key{Code: KeyF18, Mod: ModShift} + table["\x1b[33$"] = Key{Code: KeyF19, Mod: ModShift} + table["\x1b[34$"] = Key{Code: KeyF20, Mod: ModShift} + table["\x1b[11^"] = Key{Code: KeyF1, Mod: ModCtrl} + table["\x1b[12^"] = Key{Code: KeyF2, Mod: ModCtrl} + table["\x1b[13^"] = Key{Code: KeyF3, Mod: ModCtrl} + table["\x1b[14^"] = Key{Code: KeyF4, Mod: ModCtrl} + table["\x1b[15^"] = Key{Code: KeyF5, Mod: ModCtrl} + table["\x1b[17^"] = Key{Code: KeyF6, Mod: ModCtrl} + table["\x1b[18^"] = Key{Code: KeyF7, Mod: ModCtrl} + table["\x1b[19^"] = Key{Code: KeyF8, Mod: ModCtrl} + table["\x1b[20^"] = Key{Code: KeyF9, Mod: ModCtrl} + table["\x1b[21^"] = Key{Code: KeyF10, Mod: ModCtrl} + table["\x1b[23^"] = Key{Code: KeyF11, Mod: ModCtrl} + table["\x1b[24^"] = Key{Code: KeyF12, Mod: ModCtrl} + table["\x1b[25^"] = Key{Code: KeyF13, Mod: ModCtrl} + table["\x1b[26^"] = Key{Code: KeyF14, Mod: ModCtrl} + table["\x1b[28^"] = Key{Code: KeyF15, Mod: ModCtrl} + table["\x1b[29^"] = Key{Code: KeyF16, Mod: ModCtrl} + table["\x1b[31^"] = Key{Code: KeyF17, Mod: ModCtrl} + table["\x1b[32^"] = Key{Code: KeyF18, Mod: ModCtrl} + table["\x1b[33^"] = Key{Code: KeyF19, Mod: ModCtrl} + table["\x1b[34^"] = Key{Code: KeyF20, Mod: ModCtrl} + table["\x1b[23@"] = Key{Code: KeyF11, Mod: ModShift | ModCtrl} + table["\x1b[24@"] = Key{Code: KeyF12, Mod: ModShift | ModCtrl} + table["\x1b[25@"] = Key{Code: KeyF13, Mod: ModShift | ModCtrl} + table["\x1b[26@"] = Key{Code: KeyF14, Mod: ModShift | ModCtrl} + table["\x1b[28@"] = Key{Code: KeyF15, Mod: ModShift | ModCtrl} + table["\x1b[29@"] = Key{Code: KeyF16, Mod: ModShift | ModCtrl} + table["\x1b[31@"] = Key{Code: KeyF17, Mod: ModShift | ModCtrl} + table["\x1b[32@"] = Key{Code: KeyF18, Mod: ModShift | ModCtrl} + table["\x1b[33@"] = Key{Code: KeyF19, Mod: ModShift | ModCtrl} + table["\x1b[34@"] = Key{Code: KeyF20, Mod: ModShift | ModCtrl} // Register Alt + combinations // XXX: this must come after URxvt but before XTerm keys to register URxvt // keys with alt modifier - tmap := map[string]key{} + tmap := map[string]Key{} for seq, key := range table { key := key - key.mod |= ModAlt + key.Mod |= ModAlt + key.Text = "" // Clear runes tmap["\x1b"+seq] = key } for seq, key := range tmap { @@ -308,38 +309,38 @@ func buildKeysTable(flags int, term string) map[string]key { } // SS3 keypad function keys - ss3FuncKeys := map[string]key{ + ss3FuncKeys := map[string]Key{ // These are defined in XTerm // Taken from Foot keymap.h and XTerm modifyOtherKeys // https://codeberg.org/dnkl/foot/src/branch/master/keymap.h - "M": {typ: KeyKpEnter}, "X": {typ: KeyKpEqual}, - "j": {typ: KeyKpMultiply}, "k": {typ: KeyKpPlus}, - "l": {typ: KeyKpComma}, "m": {typ: KeyKpMinus}, - "n": {typ: KeyKpDecimal}, "o": {typ: KeyKpDivide}, - "p": {typ: KeyKp0}, "q": {typ: KeyKp1}, - "r": {typ: KeyKp2}, "s": {typ: KeyKp3}, - "t": {typ: KeyKp4}, "u": {typ: KeyKp5}, - "v": {typ: KeyKp6}, "w": {typ: KeyKp7}, - "x": {typ: KeyKp8}, "y": {typ: KeyKp9}, + "M": {Code: KeyKpEnter}, "X": {Code: KeyKpEqual}, + "j": {Code: KeyKpMultiply}, "k": {Code: KeyKpPlus}, + "l": {Code: KeyKpComma}, "m": {Code: KeyKpMinus}, + "n": {Code: KeyKpDecimal}, "o": {Code: KeyKpDivide}, + "p": {Code: KeyKp0}, "q": {Code: KeyKp1}, + "r": {Code: KeyKp2}, "s": {Code: KeyKp3}, + "t": {Code: KeyKp4}, "u": {Code: KeyKp5}, + "v": {Code: KeyKp6}, "w": {Code: KeyKp7}, + "x": {Code: KeyKp8}, "y": {Code: KeyKp9}, } // XTerm keys - csiFuncKeys := map[string]key{ - "A": {typ: KeyUp}, "B": {typ: KeyDown}, - "C": {typ: KeyRight}, "D": {typ: KeyLeft}, - "E": {typ: KeyBegin}, "F": {typ: KeyEnd}, - "H": {typ: KeyHome}, "P": {typ: KeyF1}, - "Q": {typ: KeyF2}, "R": {typ: KeyF3}, - "S": {typ: KeyF4}, + csiFuncKeys := map[string]Key{ + "A": {Code: KeyUp}, "B": {Code: KeyDown}, + "C": {Code: KeyRight}, "D": {Code: KeyLeft}, + "E": {Code: KeyBegin}, "F": {Code: KeyEnd}, + "H": {Code: KeyHome}, "P": {Code: KeyF1}, + "Q": {Code: KeyF2}, "R": {Code: KeyF3}, + "S": {Code: KeyF4}, } // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys - modifyOtherKeys := map[int]key{ - ansi.BS: {typ: KeyBackspace}, - ansi.HT: {typ: KeyTab}, - ansi.CR: {typ: KeyEnter}, - ansi.ESC: {typ: KeyEscape}, - ansi.DEL: {typ: KeyBackspace}, + modifyOtherKeys := map[int]Key{ + ansi.BS: {Code: KeyBackspace}, + ansi.HT: {Code: KeyTab}, + ansi.CR: {Code: KeyEnter}, + ansi.ESC: {Code: KeyEscape}, + ansi.DEL: {Code: KeyBackspace}, } for _, m := range modifiers { @@ -351,21 +352,21 @@ func buildKeysTable(flags int, term string) map[string]key { // Functions always have a leading 1 param seq := "\x1b[1;" + xtermMod + k key := v - key.mod = m + key.Mod = m table[seq] = key } // SS3 for k, v := range ss3FuncKeys { seq := "\x1bO" + xtermMod + k key := v - key.mod = m + key.Mod = m table[seq] = key } // CSI ; ~ for k, v := range csiTildeKeys { seq := "\x1b[" + k + ";" + xtermMod + "~" key := v - key.mod = m + key.Mod = m table[seq] = key } // CSI 27 ; ; ~ @@ -373,7 +374,7 @@ func buildKeysTable(flags int, term string) map[string]key { code := strconv.Itoa(k) seq := "\x1b[27;" + xtermMod + ";" + code + "~" key := v - key.mod = m + key.Mod = m table[seq] = key } } diff --git a/tea_test.go b/tea_test.go index 359465b80c..4c78e7e47c 100644 --- a/tea_test.go +++ b/tea_test.go @@ -30,7 +30,7 @@ func (m *testModel) Update(msg Msg) (Model, Cmd) { m.counter.Store(i.(int) + 1) } - case KeyMsg: + case KeyPressMsg: return m, Quit } diff --git a/terminfo.go b/terminfo.go index 32a33d5bd1..3f74b2d746 100644 --- a/terminfo.go +++ b/terminfo.go @@ -6,8 +6,8 @@ import ( "github.com/xo/terminfo" ) -func buildTerminfoKeys(flags int, term string) map[string]key { - table := make(map[string]key) +func buildTerminfoKeys(flags int, term string) map[string]Key { + table := make(map[string]Key) ti, _ := terminfo.Load(term) if ti == nil { return table @@ -54,93 +54,93 @@ func buildTerminfoKeys(flags int, term string) map[string]key { // // See https://man7.org/linux/man-pages/man5/terminfo.5.html // See https://github.com/mirror/ncurses/blob/master/include/Caps-ncurses -func defaultTerminfoKeys(flags int) map[string]key { - keys := map[string]key{ - "kcuu1": {typ: KeyUp}, - "kUP": {typ: KeyUp, mod: ModShift}, - "kUP3": {typ: KeyUp, mod: ModAlt}, - "kUP4": {typ: KeyUp, mod: ModShift | ModAlt}, - "kUP5": {typ: KeyUp, mod: ModCtrl}, - "kUP6": {typ: KeyUp, mod: ModShift | ModCtrl}, - "kUP7": {typ: KeyUp, mod: ModAlt | ModCtrl}, - "kUP8": {typ: KeyUp, mod: ModShift | ModAlt | ModCtrl}, - "kcud1": {typ: KeyDown}, - "kDN": {typ: KeyDown, mod: ModShift}, - "kDN3": {typ: KeyDown, mod: ModAlt}, - "kDN4": {typ: KeyDown, mod: ModShift | ModAlt}, - "kDN5": {typ: KeyDown, mod: ModCtrl}, - "kDN7": {typ: KeyDown, mod: ModAlt | ModCtrl}, - "kDN6": {typ: KeyDown, mod: ModShift | ModCtrl}, - "kDN8": {typ: KeyDown, mod: ModShift | ModAlt | ModCtrl}, - "kcub1": {typ: KeyLeft}, - "kLFT": {typ: KeyLeft, mod: ModShift}, - "kLFT3": {typ: KeyLeft, mod: ModAlt}, - "kLFT4": {typ: KeyLeft, mod: ModShift | ModAlt}, - "kLFT5": {typ: KeyLeft, mod: ModCtrl}, - "kLFT6": {typ: KeyLeft, mod: ModShift | ModCtrl}, - "kLFT7": {typ: KeyLeft, mod: ModAlt | ModCtrl}, - "kLFT8": {typ: KeyLeft, mod: ModShift | ModAlt | ModCtrl}, - "kcuf1": {typ: KeyRight}, - "kRIT": {typ: KeyRight, mod: ModShift}, - "kRIT3": {typ: KeyRight, mod: ModAlt}, - "kRIT4": {typ: KeyRight, mod: ModShift | ModAlt}, - "kRIT5": {typ: KeyRight, mod: ModCtrl}, - "kRIT6": {typ: KeyRight, mod: ModShift | ModCtrl}, - "kRIT7": {typ: KeyRight, mod: ModAlt | ModCtrl}, - "kRIT8": {typ: KeyRight, mod: ModShift | ModAlt | ModCtrl}, - "kich1": {typ: KeyInsert}, - "kIC": {typ: KeyInsert, mod: ModShift}, - "kIC3": {typ: KeyInsert, mod: ModAlt}, - "kIC4": {typ: KeyInsert, mod: ModShift | ModAlt}, - "kIC5": {typ: KeyInsert, mod: ModCtrl}, - "kIC6": {typ: KeyInsert, mod: ModShift | ModCtrl}, - "kIC7": {typ: KeyInsert, mod: ModAlt | ModCtrl}, - "kIC8": {typ: KeyInsert, mod: ModShift | ModAlt | ModCtrl}, - "kdch1": {typ: KeyDelete}, - "kDC": {typ: KeyDelete, mod: ModShift}, - "kDC3": {typ: KeyDelete, mod: ModAlt}, - "kDC4": {typ: KeyDelete, mod: ModShift | ModAlt}, - "kDC5": {typ: KeyDelete, mod: ModCtrl}, - "kDC6": {typ: KeyDelete, mod: ModShift | ModCtrl}, - "kDC7": {typ: KeyDelete, mod: ModAlt | ModCtrl}, - "kDC8": {typ: KeyDelete, mod: ModShift | ModAlt | ModCtrl}, - "khome": {typ: KeyHome}, - "kHOM": {typ: KeyHome, mod: ModShift}, - "kHOM3": {typ: KeyHome, mod: ModAlt}, - "kHOM4": {typ: KeyHome, mod: ModShift | ModAlt}, - "kHOM5": {typ: KeyHome, mod: ModCtrl}, - "kHOM6": {typ: KeyHome, mod: ModShift | ModCtrl}, - "kHOM7": {typ: KeyHome, mod: ModAlt | ModCtrl}, - "kHOM8": {typ: KeyHome, mod: ModShift | ModAlt | ModCtrl}, - "kend": {typ: KeyEnd}, - "kEND": {typ: KeyEnd, mod: ModShift}, - "kEND3": {typ: KeyEnd, mod: ModAlt}, - "kEND4": {typ: KeyEnd, mod: ModShift | ModAlt}, - "kEND5": {typ: KeyEnd, mod: ModCtrl}, - "kEND6": {typ: KeyEnd, mod: ModShift | ModCtrl}, - "kEND7": {typ: KeyEnd, mod: ModAlt | ModCtrl}, - "kEND8": {typ: KeyEnd, mod: ModShift | ModAlt | ModCtrl}, - "kpp": {typ: KeyPgUp}, - "kprv": {typ: KeyPgUp}, - "kPRV": {typ: KeyPgUp, mod: ModShift}, - "kPRV3": {typ: KeyPgUp, mod: ModAlt}, - "kPRV4": {typ: KeyPgUp, mod: ModShift | ModAlt}, - "kPRV5": {typ: KeyPgUp, mod: ModCtrl}, - "kPRV6": {typ: KeyPgUp, mod: ModShift | ModCtrl}, - "kPRV7": {typ: KeyPgUp, mod: ModAlt | ModCtrl}, - "kPRV8": {typ: KeyPgUp, mod: ModShift | ModAlt | ModCtrl}, - "knp": {typ: KeyPgDown}, - "knxt": {typ: KeyPgDown}, - "kNXT": {typ: KeyPgDown, mod: ModShift}, - "kNXT3": {typ: KeyPgDown, mod: ModAlt}, - "kNXT4": {typ: KeyPgDown, mod: ModShift | ModAlt}, - "kNXT5": {typ: KeyPgDown, mod: ModCtrl}, - "kNXT6": {typ: KeyPgDown, mod: ModShift | ModCtrl}, - "kNXT7": {typ: KeyPgDown, mod: ModAlt | ModCtrl}, - "kNXT8": {typ: KeyPgDown, mod: ModShift | ModAlt | ModCtrl}, +func defaultTerminfoKeys(flags int) map[string]Key { + keys := map[string]Key{ + "kcuu1": {Code: KeyUp}, + "kUP": {Code: KeyUp, Mod: ModShift}, + "kUP3": {Code: KeyUp, Mod: ModAlt}, + "kUP4": {Code: KeyUp, Mod: ModShift | ModAlt}, + "kUP5": {Code: KeyUp, Mod: ModCtrl}, + "kUP6": {Code: KeyUp, Mod: ModShift | ModCtrl}, + "kUP7": {Code: KeyUp, Mod: ModAlt | ModCtrl}, + "kUP8": {Code: KeyUp, Mod: ModShift | ModAlt | ModCtrl}, + "kcud1": {Code: KeyDown}, + "kDN": {Code: KeyDown, Mod: ModShift}, + "kDN3": {Code: KeyDown, Mod: ModAlt}, + "kDN4": {Code: KeyDown, Mod: ModShift | ModAlt}, + "kDN5": {Code: KeyDown, Mod: ModCtrl}, + "kDN7": {Code: KeyDown, Mod: ModAlt | ModCtrl}, + "kDN6": {Code: KeyDown, Mod: ModShift | ModCtrl}, + "kDN8": {Code: KeyDown, Mod: ModShift | ModAlt | ModCtrl}, + "kcub1": {Code: KeyLeft}, + "kLFT": {Code: KeyLeft, Mod: ModShift}, + "kLFT3": {Code: KeyLeft, Mod: ModAlt}, + "kLFT4": {Code: KeyLeft, Mod: ModShift | ModAlt}, + "kLFT5": {Code: KeyLeft, Mod: ModCtrl}, + "kLFT6": {Code: KeyLeft, Mod: ModShift | ModCtrl}, + "kLFT7": {Code: KeyLeft, Mod: ModAlt | ModCtrl}, + "kLFT8": {Code: KeyLeft, Mod: ModShift | ModAlt | ModCtrl}, + "kcuf1": {Code: KeyRight}, + "kRIT": {Code: KeyRight, Mod: ModShift}, + "kRIT3": {Code: KeyRight, Mod: ModAlt}, + "kRIT4": {Code: KeyRight, Mod: ModShift | ModAlt}, + "kRIT5": {Code: KeyRight, Mod: ModCtrl}, + "kRIT6": {Code: KeyRight, Mod: ModShift | ModCtrl}, + "kRIT7": {Code: KeyRight, Mod: ModAlt | ModCtrl}, + "kRIT8": {Code: KeyRight, Mod: ModShift | ModAlt | ModCtrl}, + "kich1": {Code: KeyInsert}, + "kIC": {Code: KeyInsert, Mod: ModShift}, + "kIC3": {Code: KeyInsert, Mod: ModAlt}, + "kIC4": {Code: KeyInsert, Mod: ModShift | ModAlt}, + "kIC5": {Code: KeyInsert, Mod: ModCtrl}, + "kIC6": {Code: KeyInsert, Mod: ModShift | ModCtrl}, + "kIC7": {Code: KeyInsert, Mod: ModAlt | ModCtrl}, + "kIC8": {Code: KeyInsert, Mod: ModShift | ModAlt | ModCtrl}, + "kdch1": {Code: KeyDelete}, + "kDC": {Code: KeyDelete, Mod: ModShift}, + "kDC3": {Code: KeyDelete, Mod: ModAlt}, + "kDC4": {Code: KeyDelete, Mod: ModShift | ModAlt}, + "kDC5": {Code: KeyDelete, Mod: ModCtrl}, + "kDC6": {Code: KeyDelete, Mod: ModShift | ModCtrl}, + "kDC7": {Code: KeyDelete, Mod: ModAlt | ModCtrl}, + "kDC8": {Code: KeyDelete, Mod: ModShift | ModAlt | ModCtrl}, + "khome": {Code: KeyHome}, + "kHOM": {Code: KeyHome, Mod: ModShift}, + "kHOM3": {Code: KeyHome, Mod: ModAlt}, + "kHOM4": {Code: KeyHome, Mod: ModShift | ModAlt}, + "kHOM5": {Code: KeyHome, Mod: ModCtrl}, + "kHOM6": {Code: KeyHome, Mod: ModShift | ModCtrl}, + "kHOM7": {Code: KeyHome, Mod: ModAlt | ModCtrl}, + "kHOM8": {Code: KeyHome, Mod: ModShift | ModAlt | ModCtrl}, + "kend": {Code: KeyEnd}, + "kEND": {Code: KeyEnd, Mod: ModShift}, + "kEND3": {Code: KeyEnd, Mod: ModAlt}, + "kEND4": {Code: KeyEnd, Mod: ModShift | ModAlt}, + "kEND5": {Code: KeyEnd, Mod: ModCtrl}, + "kEND6": {Code: KeyEnd, Mod: ModShift | ModCtrl}, + "kEND7": {Code: KeyEnd, Mod: ModAlt | ModCtrl}, + "kEND8": {Code: KeyEnd, Mod: ModShift | ModAlt | ModCtrl}, + "kpp": {Code: KeyPgUp}, + "kprv": {Code: KeyPgUp}, + "kPRV": {Code: KeyPgUp, Mod: ModShift}, + "kPRV3": {Code: KeyPgUp, Mod: ModAlt}, + "kPRV4": {Code: KeyPgUp, Mod: ModShift | ModAlt}, + "kPRV5": {Code: KeyPgUp, Mod: ModCtrl}, + "kPRV6": {Code: KeyPgUp, Mod: ModShift | ModCtrl}, + "kPRV7": {Code: KeyPgUp, Mod: ModAlt | ModCtrl}, + "kPRV8": {Code: KeyPgUp, Mod: ModShift | ModAlt | ModCtrl}, + "knp": {Code: KeyPgDown}, + "knxt": {Code: KeyPgDown}, + "kNXT": {Code: KeyPgDown, Mod: ModShift}, + "kNXT3": {Code: KeyPgDown, Mod: ModAlt}, + "kNXT4": {Code: KeyPgDown, Mod: ModShift | ModAlt}, + "kNXT5": {Code: KeyPgDown, Mod: ModCtrl}, + "kNXT6": {Code: KeyPgDown, Mod: ModShift | ModCtrl}, + "kNXT7": {Code: KeyPgDown, Mod: ModAlt | ModCtrl}, + "kNXT8": {Code: KeyPgDown, Mod: ModShift | ModAlt | ModCtrl}, - "kbs": {typ: KeyBackspace}, - "kcbt": {typ: KeyTab, mod: ModShift}, + "kbs": {Code: KeyBackspace}, + "kcbt": {Code: KeyTab, Mod: ModShift}, // Function keys // This only includes the first 12 function keys. The rest are treated @@ -152,125 +152,125 @@ func defaultTerminfoKeys(flags int) map[string]key { // See https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:modifyFunctionKeys // See https://invisible-island.net/xterm/terminfo.html - "kf1": {typ: KeyF1}, - "kf2": {typ: KeyF2}, - "kf3": {typ: KeyF3}, - "kf4": {typ: KeyF4}, - "kf5": {typ: KeyF5}, - "kf6": {typ: KeyF6}, - "kf7": {typ: KeyF7}, - "kf8": {typ: KeyF8}, - "kf9": {typ: KeyF9}, - "kf10": {typ: KeyF10}, - "kf11": {typ: KeyF11}, - "kf12": {typ: KeyF12}, - "kf13": {typ: KeyF1, mod: ModShift}, - "kf14": {typ: KeyF2, mod: ModShift}, - "kf15": {typ: KeyF3, mod: ModShift}, - "kf16": {typ: KeyF4, mod: ModShift}, - "kf17": {typ: KeyF5, mod: ModShift}, - "kf18": {typ: KeyF6, mod: ModShift}, - "kf19": {typ: KeyF7, mod: ModShift}, - "kf20": {typ: KeyF8, mod: ModShift}, - "kf21": {typ: KeyF9, mod: ModShift}, - "kf22": {typ: KeyF10, mod: ModShift}, - "kf23": {typ: KeyF11, mod: ModShift}, - "kf24": {typ: KeyF12, mod: ModShift}, - "kf25": {typ: KeyF1, mod: ModCtrl}, - "kf26": {typ: KeyF2, mod: ModCtrl}, - "kf27": {typ: KeyF3, mod: ModCtrl}, - "kf28": {typ: KeyF4, mod: ModCtrl}, - "kf29": {typ: KeyF5, mod: ModCtrl}, - "kf30": {typ: KeyF6, mod: ModCtrl}, - "kf31": {typ: KeyF7, mod: ModCtrl}, - "kf32": {typ: KeyF8, mod: ModCtrl}, - "kf33": {typ: KeyF9, mod: ModCtrl}, - "kf34": {typ: KeyF10, mod: ModCtrl}, - "kf35": {typ: KeyF11, mod: ModCtrl}, - "kf36": {typ: KeyF12, mod: ModCtrl}, - "kf37": {typ: KeyF1, mod: ModShift | ModCtrl}, - "kf38": {typ: KeyF2, mod: ModShift | ModCtrl}, - "kf39": {typ: KeyF3, mod: ModShift | ModCtrl}, - "kf40": {typ: KeyF4, mod: ModShift | ModCtrl}, - "kf41": {typ: KeyF5, mod: ModShift | ModCtrl}, - "kf42": {typ: KeyF6, mod: ModShift | ModCtrl}, - "kf43": {typ: KeyF7, mod: ModShift | ModCtrl}, - "kf44": {typ: KeyF8, mod: ModShift | ModCtrl}, - "kf45": {typ: KeyF9, mod: ModShift | ModCtrl}, - "kf46": {typ: KeyF10, mod: ModShift | ModCtrl}, - "kf47": {typ: KeyF11, mod: ModShift | ModCtrl}, - "kf48": {typ: KeyF12, mod: ModShift | ModCtrl}, - "kf49": {typ: KeyF1, mod: ModAlt}, - "kf50": {typ: KeyF2, mod: ModAlt}, - "kf51": {typ: KeyF3, mod: ModAlt}, - "kf52": {typ: KeyF4, mod: ModAlt}, - "kf53": {typ: KeyF5, mod: ModAlt}, - "kf54": {typ: KeyF6, mod: ModAlt}, - "kf55": {typ: KeyF7, mod: ModAlt}, - "kf56": {typ: KeyF8, mod: ModAlt}, - "kf57": {typ: KeyF9, mod: ModAlt}, - "kf58": {typ: KeyF10, mod: ModAlt}, - "kf59": {typ: KeyF11, mod: ModAlt}, - "kf60": {typ: KeyF12, mod: ModAlt}, - "kf61": {typ: KeyF1, mod: ModShift | ModAlt}, - "kf62": {typ: KeyF2, mod: ModShift | ModAlt}, - "kf63": {typ: KeyF3, mod: ModShift | ModAlt}, + "kf1": {Code: KeyF1}, + "kf2": {Code: KeyF2}, + "kf3": {Code: KeyF3}, + "kf4": {Code: KeyF4}, + "kf5": {Code: KeyF5}, + "kf6": {Code: KeyF6}, + "kf7": {Code: KeyF7}, + "kf8": {Code: KeyF8}, + "kf9": {Code: KeyF9}, + "kf10": {Code: KeyF10}, + "kf11": {Code: KeyF11}, + "kf12": {Code: KeyF12}, + "kf13": {Code: KeyF1, Mod: ModShift}, + "kf14": {Code: KeyF2, Mod: ModShift}, + "kf15": {Code: KeyF3, Mod: ModShift}, + "kf16": {Code: KeyF4, Mod: ModShift}, + "kf17": {Code: KeyF5, Mod: ModShift}, + "kf18": {Code: KeyF6, Mod: ModShift}, + "kf19": {Code: KeyF7, Mod: ModShift}, + "kf20": {Code: KeyF8, Mod: ModShift}, + "kf21": {Code: KeyF9, Mod: ModShift}, + "kf22": {Code: KeyF10, Mod: ModShift}, + "kf23": {Code: KeyF11, Mod: ModShift}, + "kf24": {Code: KeyF12, Mod: ModShift}, + "kf25": {Code: KeyF1, Mod: ModCtrl}, + "kf26": {Code: KeyF2, Mod: ModCtrl}, + "kf27": {Code: KeyF3, Mod: ModCtrl}, + "kf28": {Code: KeyF4, Mod: ModCtrl}, + "kf29": {Code: KeyF5, Mod: ModCtrl}, + "kf30": {Code: KeyF6, Mod: ModCtrl}, + "kf31": {Code: KeyF7, Mod: ModCtrl}, + "kf32": {Code: KeyF8, Mod: ModCtrl}, + "kf33": {Code: KeyF9, Mod: ModCtrl}, + "kf34": {Code: KeyF10, Mod: ModCtrl}, + "kf35": {Code: KeyF11, Mod: ModCtrl}, + "kf36": {Code: KeyF12, Mod: ModCtrl}, + "kf37": {Code: KeyF1, Mod: ModShift | ModCtrl}, + "kf38": {Code: KeyF2, Mod: ModShift | ModCtrl}, + "kf39": {Code: KeyF3, Mod: ModShift | ModCtrl}, + "kf40": {Code: KeyF4, Mod: ModShift | ModCtrl}, + "kf41": {Code: KeyF5, Mod: ModShift | ModCtrl}, + "kf42": {Code: KeyF6, Mod: ModShift | ModCtrl}, + "kf43": {Code: KeyF7, Mod: ModShift | ModCtrl}, + "kf44": {Code: KeyF8, Mod: ModShift | ModCtrl}, + "kf45": {Code: KeyF9, Mod: ModShift | ModCtrl}, + "kf46": {Code: KeyF10, Mod: ModShift | ModCtrl}, + "kf47": {Code: KeyF11, Mod: ModShift | ModCtrl}, + "kf48": {Code: KeyF12, Mod: ModShift | ModCtrl}, + "kf49": {Code: KeyF1, Mod: ModAlt}, + "kf50": {Code: KeyF2, Mod: ModAlt}, + "kf51": {Code: KeyF3, Mod: ModAlt}, + "kf52": {Code: KeyF4, Mod: ModAlt}, + "kf53": {Code: KeyF5, Mod: ModAlt}, + "kf54": {Code: KeyF6, Mod: ModAlt}, + "kf55": {Code: KeyF7, Mod: ModAlt}, + "kf56": {Code: KeyF8, Mod: ModAlt}, + "kf57": {Code: KeyF9, Mod: ModAlt}, + "kf58": {Code: KeyF10, Mod: ModAlt}, + "kf59": {Code: KeyF11, Mod: ModAlt}, + "kf60": {Code: KeyF12, Mod: ModAlt}, + "kf61": {Code: KeyF1, Mod: ModShift | ModAlt}, + "kf62": {Code: KeyF2, Mod: ModShift | ModAlt}, + "kf63": {Code: KeyF3, Mod: ModShift | ModAlt}, } // Preserve F keys from F13 to F63 instead of using them for F-keys // modifiers. if flags&_FlagFKeys != 0 { - keys["kf13"] = key{typ: KeyF13} - keys["kf14"] = key{typ: KeyF14} - keys["kf15"] = key{typ: KeyF15} - keys["kf16"] = key{typ: KeyF16} - keys["kf17"] = key{typ: KeyF17} - keys["kf18"] = key{typ: KeyF18} - keys["kf19"] = key{typ: KeyF19} - keys["kf20"] = key{typ: KeyF20} - keys["kf21"] = key{typ: KeyF21} - keys["kf22"] = key{typ: KeyF22} - keys["kf23"] = key{typ: KeyF23} - keys["kf24"] = key{typ: KeyF24} - keys["kf25"] = key{typ: KeyF25} - keys["kf26"] = key{typ: KeyF26} - keys["kf27"] = key{typ: KeyF27} - keys["kf28"] = key{typ: KeyF28} - keys["kf29"] = key{typ: KeyF29} - keys["kf30"] = key{typ: KeyF30} - keys["kf31"] = key{typ: KeyF31} - keys["kf32"] = key{typ: KeyF32} - keys["kf33"] = key{typ: KeyF33} - keys["kf34"] = key{typ: KeyF34} - keys["kf35"] = key{typ: KeyF35} - keys["kf36"] = key{typ: KeyF36} - keys["kf37"] = key{typ: KeyF37} - keys["kf38"] = key{typ: KeyF38} - keys["kf39"] = key{typ: KeyF39} - keys["kf40"] = key{typ: KeyF40} - keys["kf41"] = key{typ: KeyF41} - keys["kf42"] = key{typ: KeyF42} - keys["kf43"] = key{typ: KeyF43} - keys["kf44"] = key{typ: KeyF44} - keys["kf45"] = key{typ: KeyF45} - keys["kf46"] = key{typ: KeyF46} - keys["kf47"] = key{typ: KeyF47} - keys["kf48"] = key{typ: KeyF48} - keys["kf49"] = key{typ: KeyF49} - keys["kf50"] = key{typ: KeyF50} - keys["kf51"] = key{typ: KeyF51} - keys["kf52"] = key{typ: KeyF52} - keys["kf53"] = key{typ: KeyF53} - keys["kf54"] = key{typ: KeyF54} - keys["kf55"] = key{typ: KeyF55} - keys["kf56"] = key{typ: KeyF56} - keys["kf57"] = key{typ: KeyF57} - keys["kf58"] = key{typ: KeyF58} - keys["kf59"] = key{typ: KeyF59} - keys["kf60"] = key{typ: KeyF60} - keys["kf61"] = key{typ: KeyF61} - keys["kf62"] = key{typ: KeyF62} - keys["kf63"] = key{typ: KeyF63} + keys["kf13"] = Key{Code: KeyF13} + keys["kf14"] = Key{Code: KeyF14} + keys["kf15"] = Key{Code: KeyF15} + keys["kf16"] = Key{Code: KeyF16} + keys["kf17"] = Key{Code: KeyF17} + keys["kf18"] = Key{Code: KeyF18} + keys["kf19"] = Key{Code: KeyF19} + keys["kf20"] = Key{Code: KeyF20} + keys["kf21"] = Key{Code: KeyF21} + keys["kf22"] = Key{Code: KeyF22} + keys["kf23"] = Key{Code: KeyF23} + keys["kf24"] = Key{Code: KeyF24} + keys["kf25"] = Key{Code: KeyF25} + keys["kf26"] = Key{Code: KeyF26} + keys["kf27"] = Key{Code: KeyF27} + keys["kf28"] = Key{Code: KeyF28} + keys["kf29"] = Key{Code: KeyF29} + keys["kf30"] = Key{Code: KeyF30} + keys["kf31"] = Key{Code: KeyF31} + keys["kf32"] = Key{Code: KeyF32} + keys["kf33"] = Key{Code: KeyF33} + keys["kf34"] = Key{Code: KeyF34} + keys["kf35"] = Key{Code: KeyF35} + keys["kf36"] = Key{Code: KeyF36} + keys["kf37"] = Key{Code: KeyF37} + keys["kf38"] = Key{Code: KeyF38} + keys["kf39"] = Key{Code: KeyF39} + keys["kf40"] = Key{Code: KeyF40} + keys["kf41"] = Key{Code: KeyF41} + keys["kf42"] = Key{Code: KeyF42} + keys["kf43"] = Key{Code: KeyF43} + keys["kf44"] = Key{Code: KeyF44} + keys["kf45"] = Key{Code: KeyF45} + keys["kf46"] = Key{Code: KeyF46} + keys["kf47"] = Key{Code: KeyF47} + keys["kf48"] = Key{Code: KeyF48} + keys["kf49"] = Key{Code: KeyF49} + keys["kf50"] = Key{Code: KeyF50} + keys["kf51"] = Key{Code: KeyF51} + keys["kf52"] = Key{Code: KeyF52} + keys["kf53"] = Key{Code: KeyF53} + keys["kf54"] = Key{Code: KeyF54} + keys["kf55"] = Key{Code: KeyF55} + keys["kf56"] = Key{Code: KeyF56} + keys["kf57"] = Key{Code: KeyF57} + keys["kf58"] = Key{Code: KeyF58} + keys["kf59"] = Key{Code: KeyF59} + keys["kf60"] = Key{Code: KeyF60} + keys["kf61"] = Key{Code: KeyF61} + keys["kf62"] = Key{Code: KeyF62} + keys["kf63"] = Key{Code: KeyF63} } return keys diff --git a/win32input.go b/win32input.go index 092f93a889..220a740c60 100644 --- a/win32input.go +++ b/win32input.go @@ -31,7 +31,7 @@ func disableWindowsInputMode() Msg { //nolint:unused } func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uint32, repeatCount uint16) Msg { - var key key + var key Key isCtrl := cks&(_LEFT_CTRL_PRESSED|_RIGHT_CTRL_PRESSED) != 0 switch vkc { case _VK_SHIFT: @@ -39,67 +39,67 @@ func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uin return nil case _VK_MENU: if cks&_LEFT_ALT_PRESSED != 0 { - key.typ = KeyLeftAlt + key.Code = KeyLeftAlt } else if cks&_RIGHT_ALT_PRESSED != 0 { - key.typ = KeyRightAlt + key.Code = KeyRightAlt } else if !keyDown { return nil } case _VK_CONTROL: if cks&_LEFT_CTRL_PRESSED != 0 { - key.typ = KeyLeftCtrl + key.Code = KeyLeftCtrl } else if cks&_RIGHT_CTRL_PRESSED != 0 { - key.typ = KeyRightCtrl + key.Code = KeyRightCtrl } else if !keyDown { return nil } case _VK_CAPITAL: - key.typ = KeyCapsLock + key.Code = KeyCapsLock default: var ok bool key, ok = vkKeyEvent[vkc] if !ok { if isCtrl { - key.runes = []rune{vkCtrlRune(key, r, vkc)} + key.Text = string(vkCtrlRune(key, r, vkc)) } else { - key.runes = []rune{r} + key.Text = string(r) } } } if isCtrl { - key.mod |= ModCtrl + key.Mod |= ModCtrl } if cks&(_LEFT_ALT_PRESSED|_RIGHT_ALT_PRESSED) != 0 { - key.mod |= ModAlt + key.Mod |= ModAlt } if cks&_SHIFT_PRESSED != 0 { - key.mod |= ModShift + key.Mod |= ModShift } if cks&_CAPSLOCK_ON != 0 { - key.mod |= ModCapsLock + key.Mod |= ModCapsLock } if cks&_NUMLOCK_ON != 0 { - key.mod |= ModNumLock + key.Mod |= ModNumLock } if cks&_SCROLLLOCK_ON != 0 { - key.mod |= ModScrollLock + key.Mod |= ModScrollLock } // Use the unshifted key - keyRune := key.Rune() + keyRune := key.Code if cks&(_SHIFT_PRESSED^_CAPSLOCK_ON) != 0 { if unicode.IsLower(keyRune) { - key.altRune = unicode.ToUpper(key.Rune()) + key.ShiftedCode = unicode.ToUpper(key.Code) } } else { if unicode.IsUpper(keyRune) { - key.altRune = unicode.ToLower(keyRune) + key.ShiftedCode = unicode.ToLower(keyRune) } } var e Msg = KeyPressMsg(key) - key.isRepeat = repeatCount > 1 + key.IsRepeat = repeatCount > 1 if !keyDown { e = KeyReleaseMsg(key) } @@ -116,80 +116,80 @@ func parseWin32InputKeyEvent(vkc uint16, _ uint16, r rune, keyDown bool, cks uin return multiMsg(kevents) } -var vkKeyEvent = map[uint16]key{ - _VK_RETURN: {typ: KeyEnter}, - _VK_BACK: {typ: KeyBackspace}, - _VK_TAB: {typ: KeyTab}, - _VK_ESCAPE: {typ: KeyEscape}, - _VK_SPACE: {typ: KeySpace, runes: []rune{' '}}, - _VK_UP: {typ: KeyUp}, - _VK_DOWN: {typ: KeyDown}, - _VK_RIGHT: {typ: KeyRight}, - _VK_LEFT: {typ: KeyLeft}, - _VK_HOME: {typ: KeyHome}, - _VK_END: {typ: KeyEnd}, - _VK_PRIOR: {typ: KeyPgUp}, - _VK_NEXT: {typ: KeyPgDown}, - _VK_DELETE: {typ: KeyDelete}, - _VK_SELECT: {typ: KeySelect}, - _VK_SNAPSHOT: {typ: KeyPrintScreen}, - _VK_INSERT: {typ: KeyInsert}, - _VK_LWIN: {typ: KeyLeftSuper}, - _VK_RWIN: {typ: KeyRightSuper}, - _VK_APPS: {typ: KeyMenu}, - _VK_NUMPAD0: {typ: KeyKp0}, - _VK_NUMPAD1: {typ: KeyKp1}, - _VK_NUMPAD2: {typ: KeyKp2}, - _VK_NUMPAD3: {typ: KeyKp3}, - _VK_NUMPAD4: {typ: KeyKp4}, - _VK_NUMPAD5: {typ: KeyKp5}, - _VK_NUMPAD6: {typ: KeyKp6}, - _VK_NUMPAD7: {typ: KeyKp7}, - _VK_NUMPAD8: {typ: KeyKp8}, - _VK_NUMPAD9: {typ: KeyKp9}, - _VK_MULTIPLY: {typ: KeyKpMultiply}, - _VK_ADD: {typ: KeyKpPlus}, - _VK_SEPARATOR: {typ: KeyKpComma}, - _VK_SUBTRACT: {typ: KeyKpMinus}, - _VK_DECIMAL: {typ: KeyKpDecimal}, - _VK_DIVIDE: {typ: KeyKpDivide}, - _VK_F1: {typ: KeyF1}, - _VK_F2: {typ: KeyF2}, - _VK_F3: {typ: KeyF3}, - _VK_F4: {typ: KeyF4}, - _VK_F5: {typ: KeyF5}, - _VK_F6: {typ: KeyF6}, - _VK_F7: {typ: KeyF7}, - _VK_F8: {typ: KeyF8}, - _VK_F9: {typ: KeyF9}, - _VK_F10: {typ: KeyF10}, - _VK_F11: {typ: KeyF11}, - _VK_F12: {typ: KeyF12}, - _VK_F13: {typ: KeyF13}, - _VK_F14: {typ: KeyF14}, - _VK_F15: {typ: KeyF15}, - _VK_F16: {typ: KeyF16}, - _VK_F17: {typ: KeyF17}, - _VK_F18: {typ: KeyF18}, - _VK_F19: {typ: KeyF19}, - _VK_F20: {typ: KeyF20}, - _VK_F21: {typ: KeyF21}, - _VK_F22: {typ: KeyF22}, - _VK_F23: {typ: KeyF23}, - _VK_F24: {typ: KeyF24}, - _VK_NUMLOCK: {typ: KeyNumLock}, - _VK_SCROLL: {typ: KeyScrollLock}, - _VK_LSHIFT: {typ: KeyLeftShift}, - _VK_RSHIFT: {typ: KeyRightShift}, - _VK_LCONTROL: {typ: KeyLeftCtrl}, - _VK_RCONTROL: {typ: KeyRightCtrl}, - _VK_LMENU: {typ: KeyLeftAlt}, - _VK_RMENU: {typ: KeyRightAlt}, - _VK_OEM_4: {runes: []rune{'['}}, +var vkKeyEvent = map[uint16]Key{ + _VK_RETURN: {Code: KeyEnter}, + _VK_BACK: {Code: KeyBackspace}, + _VK_TAB: {Code: KeyTab}, + _VK_ESCAPE: {Code: KeyEscape}, + _VK_SPACE: {Code: KeySpace, Text: " "}, + _VK_UP: {Code: KeyUp}, + _VK_DOWN: {Code: KeyDown}, + _VK_RIGHT: {Code: KeyRight}, + _VK_LEFT: {Code: KeyLeft}, + _VK_HOME: {Code: KeyHome}, + _VK_END: {Code: KeyEnd}, + _VK_PRIOR: {Code: KeyPgUp}, + _VK_NEXT: {Code: KeyPgDown}, + _VK_DELETE: {Code: KeyDelete}, + _VK_SELECT: {Code: KeySelect}, + _VK_SNAPSHOT: {Code: KeyPrintScreen}, + _VK_INSERT: {Code: KeyInsert}, + _VK_LWIN: {Code: KeyLeftSuper}, + _VK_RWIN: {Code: KeyRightSuper}, + _VK_APPS: {Code: KeyMenu}, + _VK_NUMPAD0: {Code: KeyKp0}, + _VK_NUMPAD1: {Code: KeyKp1}, + _VK_NUMPAD2: {Code: KeyKp2}, + _VK_NUMPAD3: {Code: KeyKp3}, + _VK_NUMPAD4: {Code: KeyKp4}, + _VK_NUMPAD5: {Code: KeyKp5}, + _VK_NUMPAD6: {Code: KeyKp6}, + _VK_NUMPAD7: {Code: KeyKp7}, + _VK_NUMPAD8: {Code: KeyKp8}, + _VK_NUMPAD9: {Code: KeyKp9}, + _VK_MULTIPLY: {Code: KeyKpMultiply}, + _VK_ADD: {Code: KeyKpPlus}, + _VK_SEPARATOR: {Code: KeyKpComma}, + _VK_SUBTRACT: {Code: KeyKpMinus}, + _VK_DECIMAL: {Code: KeyKpDecimal}, + _VK_DIVIDE: {Code: KeyKpDivide}, + _VK_F1: {Code: KeyF1}, + _VK_F2: {Code: KeyF2}, + _VK_F3: {Code: KeyF3}, + _VK_F4: {Code: KeyF4}, + _VK_F5: {Code: KeyF5}, + _VK_F6: {Code: KeyF6}, + _VK_F7: {Code: KeyF7}, + _VK_F8: {Code: KeyF8}, + _VK_F9: {Code: KeyF9}, + _VK_F10: {Code: KeyF10}, + _VK_F11: {Code: KeyF11}, + _VK_F12: {Code: KeyF12}, + _VK_F13: {Code: KeyF13}, + _VK_F14: {Code: KeyF14}, + _VK_F15: {Code: KeyF15}, + _VK_F16: {Code: KeyF16}, + _VK_F17: {Code: KeyF17}, + _VK_F18: {Code: KeyF18}, + _VK_F19: {Code: KeyF19}, + _VK_F20: {Code: KeyF20}, + _VK_F21: {Code: KeyF21}, + _VK_F22: {Code: KeyF22}, + _VK_F23: {Code: KeyF23}, + _VK_F24: {Code: KeyF24}, + _VK_NUMLOCK: {Code: KeyNumLock}, + _VK_SCROLL: {Code: KeyScrollLock}, + _VK_LSHIFT: {Code: KeyLeftShift}, + _VK_RSHIFT: {Code: KeyRightShift}, + _VK_LCONTROL: {Code: KeyLeftCtrl}, + _VK_RCONTROL: {Code: KeyRightCtrl}, + _VK_LMENU: {Code: KeyLeftAlt}, + _VK_RMENU: {Code: KeyRightAlt}, + _VK_OEM_4: {Text: "["}, // TODO: add more keys } -func vkCtrlRune(k key, r rune, kc uint16) rune { +func vkCtrlRune(k Key, r rune, kc uint16) rune { switch r { case 0x01: return 'a' @@ -257,7 +257,7 @@ func vkCtrlRune(k key, r rune, kc uint16) rune { } // https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes - if len(k.runes) == 0 && + if len(k.Text) == 0 && (kc >= 0x30 && kc <= 0x39) || (kc >= 0x41 && kc <= 0x5a) { return rune(kc) diff --git a/xterm.go b/xterm.go index 7055fbe18b..298f494a6c 100644 --- a/xterm.go +++ b/xterm.go @@ -34,22 +34,24 @@ func parseXTermModifyOtherKeys(csi *ansi.CsiSequence) Msg { switch r { case ansi.BS: - return KeyPressMsg{mod: mod, typ: KeyBackspace} + return KeyPressMsg{Mod: mod, Code: KeyBackspace} case ansi.HT: - return KeyPressMsg{mod: mod, typ: KeyTab} + return KeyPressMsg{Mod: mod, Code: KeyTab} case ansi.CR: - return KeyPressMsg{mod: mod, typ: KeyEnter} + return KeyPressMsg{Mod: mod, Code: KeyEnter} case ansi.ESC: - return KeyPressMsg{mod: mod, typ: KeyEscape} + return KeyPressMsg{Mod: mod, Code: KeyEscape} case ansi.DEL: - return KeyPressMsg{mod: mod, typ: KeyBackspace} + return KeyPressMsg{Mod: mod, Code: KeyBackspace} } // CSI 27 ; ; ~ keys defined in XTerm modifyOtherKeys - return KeyPressMsg{ - mod: mod, - runes: []rune{r}, + k := KeyPressMsg{Code: r, Mod: mod} + if k.Mod <= ModShift { + k.Text = string(r) } + + return k } // modifyOtherKeys is an internal message that queries the terminal for its From e8903bbd92e3f939e810cddefc001b97704ec1bc Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 27 Aug 2024 11:21:18 -0400 Subject: [PATCH 4/7] feat!: use KeyExtended to signify more than one rune --- key.go | 6 ++++-- parse.go | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/key.go b/key.go index 0a77612b45..1f735172d7 100644 --- a/key.go +++ b/key.go @@ -9,7 +9,9 @@ import ( ) const ( - extended = unicode.MaxRune + 1 + // KeyExtended is a special key code used to signify that a key event + // contains multiple runes. + KeyExtended = unicode.MaxRune + 1 ) // Special key symbols. @@ -17,7 +19,7 @@ const ( // Special keys - KeyUp rune = extended + iota + KeyUp rune = KeyExtended + iota + 1 KeyDown KeyRight KeyLeft diff --git a/parse.go b/parse.go index bc5569319c..00bf4638c0 100644 --- a/parse.go +++ b/parse.go @@ -4,7 +4,6 @@ import ( "encoding/base64" "strings" "unicode" - "unicode/utf8" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi/parser" @@ -783,13 +782,18 @@ func parseUtf8(b []byte) (int, Msg) { return 1, k } - r, _ := utf8.DecodeRune(b) - if r == utf8.RuneError { - return 1, UnknownMsg(b[0]) + cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) + text := string(cluster) + var code rune + for i, r := range text { + if i > 0 { + code = KeyExtended + break + } + code = r } - cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) - return len(cluster), KeyPressMsg{Code: r, Text: string(cluster)} + return len(cluster), KeyPressMsg{Code: code, Text: text} } func parseControl(b byte) Msg { From 2f14548625a1b492196996a1700f7566d08a2483 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 27 Aug 2024 11:40:02 -0400 Subject: [PATCH 5/7] docs: add godoc examples --- key.go | 34 ++++++++++++++++++++++++++++++++++ mouse.go | 19 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/key.go b/key.go index 1f735172d7..a2d1a108e9 100644 --- a/key.go +++ b/key.go @@ -231,6 +231,40 @@ type KeyMsg interface { // 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 msg := msg.(type) { +// case KeyPressMsg: +// switch msg.String() { +// case "enter": +// fmt.Println("you pressed enter!") +// case "a": +// fmt.Println("you pressed a!") +// } +// } +// +// // Switch on the key type (more foolproof) +// switch msg := msg.(type) { +// case KeyMsg: +// // catch both KeyPressMsg and KeyReleaseMsg +// switch key := msg.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 diff --git a/mouse.go b/mouse.go index 58949b8cfc..14ce0ad1f7 100644 --- a/mouse.go +++ b/mouse.go @@ -65,7 +65,24 @@ type MouseMsg interface { Mouse() Mouse } -// Mouse represents a Mouse message. +// Mouse represents a Mouse message. Use [MouseMsg] to represent all mouse +// messages. +// +// The X and Y coordinates are zero-based, with (0,0) being the upper left +// corner of the terminal. +// +// // Catch all mouse events +// switch msg := msg.(type) { +// case MouseMsg: +// m := msg.Mouse() +// fmt.Println("Mouse event:", m.X, m.Y, m) +// } +// +// // Only catch mouse click events +// switch msg := msg.(type) { +// case MouseClickMsg: +// fmt.Println("Mouse click event:", msg.X, msg.Y, msg) +// } type Mouse struct { X, Y int Button MouseButton From 0c8967d8a973cd9cb70bfe5ff03ed0f6cf61acc0 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 28 Aug 2024 11:54:36 -0400 Subject: [PATCH 6/7] fix(kitty): only use printables --- kitty.go | 8 ++++---- parse.go | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/kitty.go b/kitty.go index 41b738cdeb..834ad6d8d7 100644 --- a/kitty.go +++ b/kitty.go @@ -294,11 +294,11 @@ func parseKittyKeyboard(csi *ansi.CsiSequence) Msg { } } - isShifted := key.Mod <= ModShift && key.Code != KeyLeftShift && key.Code != KeyRightShift - if len(key.Text) == 0 && isShifted { - if key.ShiftedCode != 0 { + noneOrShifted := key.Mod <= ModShift && key.Code != KeyLeftShift && key.Code != KeyRightShift + if len(key.Text) == 0 && noneOrShifted { + if key.ShiftedCode != 0 && unicode.IsPrint(key.ShiftedCode) { key.Text = string(key.ShiftedCode) - } else { + } else if unicode.IsPrint(key.Code) { key.Text = string(key.Code) } } diff --git a/parse.go b/parse.go index 00bf4638c0..4427c38da7 100644 --- a/parse.go +++ b/parse.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "strings" "unicode" + "unicode/utf8" "github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi/parser" @@ -774,6 +775,7 @@ func parseUtf8(b []byte) (int, Msg) { code := rune(c) k := KeyPressMsg{Code: code, Text: string(code)} if unicode.IsUpper(code) { + // Convert upper case letters to lower case + shift modifier k.Code = unicode.ToLower(code) k.ShiftedCode = code k.Mod |= ModShift @@ -782,15 +784,19 @@ func parseUtf8(b []byte) (int, Msg) { return 1, k } + code, _ := utf8.DecodeRune(b) + if code == utf8.RuneError { + return 1, UnknownMsg(b[0]) + } + cluster, _, _, _ := uniseg.FirstGraphemeCluster(b, -1) text := string(cluster) - var code rune - for i, r := range text { + for i := range text { if i > 0 { + // Use [KeyExtended] for multi-rune graphemes code = KeyExtended break } - code = r } return len(cluster), KeyPressMsg{Code: code, Text: text} From 2f6637bdbd54467cfbd755977bcdf4c99a32b22b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 29 Aug 2024 11:40:11 -0400 Subject: [PATCH 7/7] fix: windows driver build --- driver_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver_windows.go b/driver_windows.go index b9e750cef5..70029d7dbc 100644 --- a/driver_windows.go +++ b/driver_windows.go @@ -163,7 +163,7 @@ func parseConInputEvent(event xwindows.InputRecord, buttonState *uint32, windowS return event } - key.baseCode = runes[0] + key.BaseCode = runes[0] if kevent.KeyDown { return KeyPressMsg(key) }