Files
terminal/key.go
T
2026-07-12 18:41:05 -04:00

395 lines
10 KiB
Go

package terminal
// Key represents a parsed input key
type Key uint16
// Key constants - designed for expansion
const (
KeyNone Key = iota
KeyRune // Printable character (check Event.Rune)
// Control keys
KeyEscape
KeyEnter
KeyTab
KeyBacktab // Shift+Tab
KeyShiftTab // Same as KeyBacktab,for clarity
KeyBackspace
KeyDelete
KeySpace
// Navigation
KeyUp
KeyDown
KeyLeft
KeyRight
KeyHome
KeyEnd
KeyPageUp
KeyPageDown
KeyInsert
// Function keys
KeyF1
KeyF2
KeyF3
KeyF4
KeyF5
KeyF6
KeyF7
KeyF8
KeyF9
KeyF10
KeyF11
KeyF12
// Ctrl+letter (Ctrl+A = 0x01, Ctrl+Z = 0x1A)
KeyCtrlA
KeyCtrlB
KeyCtrlC
KeyCtrlD
KeyCtrlE
KeyCtrlF
KeyCtrlG
KeyCtrlH // Often same as Backspace
KeyCtrlI // Often same as Tab
KeyCtrlJ // Often same as Enter
KeyCtrlK
KeyCtrlL
KeyCtrlM // Often same as Enter
KeyCtrlN
KeyCtrlO
KeyCtrlP
KeyCtrlQ
KeyCtrlR
KeyCtrlS
KeyCtrlT
KeyCtrlU
KeyCtrlV
KeyCtrlW
KeyCtrlX
KeyCtrlY
KeyCtrlZ
// Ctrl+special
KeyCtrlSpace // Ctrl+@ / Ctrl+Space (Ctrl+@ produces NUL byte 0x00)
KeyCtrlBackslash
KeyCtrlBracketLeft
KeyCtrlBracketRight
KeyCtrlCaret
KeyCtrlUnderscore
)
// Modifier flags
type Modifier uint8
const (
ModNone Modifier = 0
ModShift Modifier = 1 << 0
ModAlt Modifier = 1 << 1
ModCtrl Modifier = 1 << 2
)
// escapeSequence maps escape sequences to keys
// Key: sequence after ESC [ (e.g., "A" for up arrow)
type escapeSequence struct {
seq string
key Key
mod Modifier
}
// Known escape sequences (CSI sequences: ESC [ ...)
var csiSequences = []escapeSequence{
// Arrow keys
{"A", KeyUp, ModNone},
{"B", KeyDown, ModNone},
{"C", KeyRight, ModNone},
{"D", KeyLeft, ModNone},
{"Z", KeyBacktab, ModShift}, // Shift+Tab
// Arrow keys with modifiers (xterm style: ESC [ 1 ; mod X)
{"1;2A", KeyUp, ModShift},
{"1;2B", KeyDown, ModShift},
{"1;2C", KeyRight, ModShift},
{"1;2D", KeyLeft, ModShift},
{"1;3A", KeyUp, ModAlt},
{"1;3B", KeyDown, ModAlt},
{"1;3C", KeyRight, ModAlt},
{"1;3D", KeyLeft, ModAlt},
{"1;5A", KeyUp, ModCtrl},
{"1;5B", KeyDown, ModCtrl},
{"1;5C", KeyRight, ModCtrl},
{"1;5D", KeyLeft, ModCtrl},
// Navigation
{"H", KeyHome, ModNone},
{"F", KeyEnd, ModNone},
{"1~", KeyHome, ModNone},
{"4~", KeyEnd, ModNone},
{"5~", KeyPageUp, ModNone},
{"6~", KeyPageDown, ModNone},
{"2~", KeyInsert, ModNone},
{"3~", KeyDelete, ModNone},
{"7~", KeyHome, ModNone},
{"8~", KeyEnd, ModNone},
// Function keys (xterm)
{"11~", KeyF1, ModNone},
{"12~", KeyF2, ModNone},
{"13~", KeyF3, ModNone},
{"14~", KeyF4, ModNone},
{"15~", KeyF5, ModNone},
{"17~", KeyF6, ModNone},
{"18~", KeyF7, ModNone},
{"19~", KeyF8, ModNone},
{"20~", KeyF9, ModNone},
{"21~", KeyF10, ModNone},
{"23~", KeyF11, ModNone},
{"24~", KeyF12, ModNone},
// Function keys (vt style)
{"[A", KeyF1, ModNone},
{"[B", KeyF2, ModNone},
{"[C", KeyF3, ModNone},
{"[D", KeyF4, ModNone},
{"[E", KeyF5, ModNone},
// Shift+Navigation (mod=2)
{"1;2H", KeyHome, ModShift},
{"1;2F", KeyEnd, ModShift},
{"2;2~", KeyInsert, ModShift},
{"3;2~", KeyDelete, ModShift},
{"5;2~", KeyPageUp, ModShift},
{"6;2~", KeyPageDown, ModShift},
// Alt+Arrows (mod=3) - already have 1;3A-D
// Alt+Navigation (mod=3)
{"1;3H", KeyHome, ModAlt},
{"1;3F", KeyEnd, ModAlt},
{"2;3~", KeyInsert, ModAlt},
{"3;3~", KeyDelete, ModAlt},
{"5;3~", KeyPageUp, ModAlt},
{"6;3~", KeyPageDown, ModAlt},
// Shift+Alt (mod=4)
{"1;4A", KeyUp, ModShift | ModAlt},
{"1;4B", KeyDown, ModShift | ModAlt},
{"1;4C", KeyRight, ModShift | ModAlt},
{"1;4D", KeyLeft, ModShift | ModAlt},
{"1;4H", KeyHome, ModShift | ModAlt},
{"1;4F", KeyEnd, ModShift | ModAlt},
{"2;4~", KeyInsert, ModShift | ModAlt},
{"3;4~", KeyDelete, ModShift | ModAlt},
{"5;4~", KeyPageUp, ModShift | ModAlt},
{"6;4~", KeyPageDown, ModShift | ModAlt},
// Ctrl+Arrows (mod=5) - already have 1;5A-D
// Ctrl+Navigation (mod=5)
{"1;5H", KeyHome, ModCtrl},
{"1;5F", KeyEnd, ModCtrl},
{"2;5~", KeyInsert, ModCtrl},
{"3;5~", KeyDelete, ModCtrl},
{"5;5~", KeyPageUp, ModCtrl},
{"6;5~", KeyPageDown, ModCtrl},
// Shift+Ctrl (mod=6)
{"1;6A", KeyUp, ModShift | ModCtrl},
{"1;6B", KeyDown, ModShift | ModCtrl},
{"1;6C", KeyRight, ModShift | ModCtrl},
{"1;6D", KeyLeft, ModShift | ModCtrl},
{"1;6H", KeyHome, ModShift | ModCtrl},
{"1;6F", KeyEnd, ModShift | ModCtrl},
{"2;6~", KeyInsert, ModShift | ModCtrl},
{"3;6~", KeyDelete, ModShift | ModCtrl},
{"5;6~", KeyPageUp, ModShift | ModCtrl},
{"6;6~", KeyPageDown, ModShift | ModCtrl},
// Alt+Ctrl (mod=7)
{"1;7A", KeyUp, ModAlt | ModCtrl},
{"1;7B", KeyDown, ModAlt | ModCtrl},
{"1;7C", KeyRight, ModAlt | ModCtrl},
{"1;7D", KeyLeft, ModAlt | ModCtrl},
{"1;7H", KeyHome, ModAlt | ModCtrl},
{"1;7F", KeyEnd, ModAlt | ModCtrl},
{"2;7~", KeyInsert, ModAlt | ModCtrl},
{"3;7~", KeyDelete, ModAlt | ModCtrl},
{"5;7~", KeyPageUp, ModAlt | ModCtrl},
{"6;7~", KeyPageDown, ModAlt | ModCtrl},
// Shift+Alt+Ctrl (mod=8)
{"1;8A", KeyUp, ModShift | ModAlt | ModCtrl},
{"1;8B", KeyDown, ModShift | ModAlt | ModCtrl},
{"1;8C", KeyRight, ModShift | ModAlt | ModCtrl},
{"1;8D", KeyLeft, ModShift | ModAlt | ModCtrl},
{"1;8H", KeyHome, ModShift | ModAlt | ModCtrl},
{"1;8F", KeyEnd, ModShift | ModAlt | ModCtrl},
{"2;8~", KeyInsert, ModShift | ModAlt | ModCtrl},
{"3;8~", KeyDelete, ModShift | ModAlt | ModCtrl},
{"5;8~", KeyPageUp, ModShift | ModAlt | ModCtrl},
{"6;8~", KeyPageDown, ModShift | ModAlt | ModCtrl},
// F-keys with modifiers (CSI style: ESC [ 1 ; mod P/Q/R/S for F1-F4)
// F1-F4 with Shift (mod=2)
{"1;2P", KeyF1, ModShift},
{"1;2Q", KeyF2, ModShift},
{"1;2R", KeyF3, ModShift},
{"1;2S", KeyF4, ModShift},
// F1-F4 with Alt (mod=3)
{"1;3P", KeyF1, ModAlt},
{"1;3Q", KeyF2, ModAlt},
{"1;3R", KeyF3, ModAlt},
{"1;3S", KeyF4, ModAlt},
// F1-F4 with Ctrl (mod=5)
{"1;5P", KeyF1, ModCtrl},
{"1;5Q", KeyF2, ModCtrl},
{"1;5R", KeyF3, ModCtrl},
{"1;5S", KeyF4, ModCtrl},
// F1-F4 with Shift+Alt (mod=4)
{"1;4P", KeyF1, ModShift | ModAlt},
{"1;4Q", KeyF2, ModShift | ModAlt},
{"1;4R", KeyF3, ModShift | ModAlt},
{"1;4S", KeyF4, ModShift | ModAlt},
// F1-F4 with Shift+Ctrl (mod=6)
{"1;6P", KeyF1, ModShift | ModCtrl},
{"1;6Q", KeyF2, ModShift | ModCtrl},
{"1;6R", KeyF3, ModShift | ModCtrl},
{"1;6S", KeyF4, ModShift | ModCtrl},
// F1-F4 with Alt+Ctrl (mod=7)
{"1;7P", KeyF1, ModAlt | ModCtrl},
{"1;7Q", KeyF2, ModAlt | ModCtrl},
{"1;7R", KeyF3, ModAlt | ModCtrl},
{"1;7S", KeyF4, ModAlt | ModCtrl},
// F1-F4 with Shift+Alt+Ctrl (mod=8)
{"1;8P", KeyF1, ModShift | ModAlt | ModCtrl},
{"1;8Q", KeyF2, ModShift | ModAlt | ModCtrl},
{"1;8R", KeyF3, ModShift | ModAlt | ModCtrl},
{"1;8S", KeyF4, ModShift | ModAlt | ModCtrl},
// F5-F12 with modifiers (ESC [ N ; mod ~)
// F5 (15~)
{"15;2~", KeyF5, ModShift},
{"15;3~", KeyF5, ModAlt},
{"15;4~", KeyF5, ModShift | ModAlt},
{"15;5~", KeyF5, ModCtrl},
{"15;6~", KeyF5, ModShift | ModCtrl},
{"15;7~", KeyF5, ModAlt | ModCtrl},
{"15;8~", KeyF5, ModShift | ModAlt | ModCtrl},
// F6 (17~)
{"17;2~", KeyF6, ModShift},
{"17;3~", KeyF6, ModAlt},
{"17;4~", KeyF6, ModShift | ModAlt},
{"17;5~", KeyF6, ModCtrl},
{"17;6~", KeyF6, ModShift | ModCtrl},
{"17;7~", KeyF6, ModAlt | ModCtrl},
{"17;8~", KeyF6, ModShift | ModAlt | ModCtrl},
// F7 (18~)
{"18;2~", KeyF7, ModShift},
{"18;3~", KeyF7, ModAlt},
{"18;4~", KeyF7, ModShift | ModAlt},
{"18;5~", KeyF7, ModCtrl},
{"18;6~", KeyF7, ModShift | ModCtrl},
{"18;7~", KeyF7, ModAlt | ModCtrl},
{"18;8~", KeyF7, ModShift | ModAlt | ModCtrl},
// F8 (19~)
{"19;2~", KeyF8, ModShift},
{"19;3~", KeyF8, ModAlt},
{"19;4~", KeyF8, ModShift | ModAlt},
{"19;5~", KeyF8, ModCtrl},
{"19;6~", KeyF8, ModShift | ModCtrl},
{"19;7~", KeyF8, ModAlt | ModCtrl},
{"19;8~", KeyF8, ModShift | ModAlt | ModCtrl},
// F9 (20~)
{"20;2~", KeyF9, ModShift},
{"20;3~", KeyF9, ModAlt},
{"20;4~", KeyF9, ModShift | ModAlt},
{"20;5~", KeyF9, ModCtrl},
{"20;6~", KeyF9, ModShift | ModCtrl},
{"20;7~", KeyF9, ModAlt | ModCtrl},
{"20;8~", KeyF9, ModShift | ModAlt | ModCtrl},
// F10 (21~)
{"21;2~", KeyF10, ModShift},
{"21;3~", KeyF10, ModAlt},
{"21;4~", KeyF10, ModShift | ModAlt},
{"21;5~", KeyF10, ModCtrl},
{"21;6~", KeyF10, ModShift | ModCtrl},
{"21;7~", KeyF10, ModAlt | ModCtrl},
{"21;8~", KeyF10, ModShift | ModAlt | ModCtrl},
// F11 (23~)
{"23;2~", KeyF11, ModShift},
{"23;3~", KeyF11, ModAlt},
{"23;4~", KeyF11, ModShift | ModAlt},
{"23;5~", KeyF11, ModCtrl},
{"23;6~", KeyF11, ModShift | ModCtrl},
{"23;7~", KeyF11, ModAlt | ModCtrl},
{"23;8~", KeyF11, ModShift | ModAlt | ModCtrl},
// F12 (24~)
{"24;2~", KeyF12, ModShift},
{"24;3~", KeyF12, ModAlt},
{"24;4~", KeyF12, ModShift | ModAlt},
{"24;5~", KeyF12, ModCtrl},
{"24;6~", KeyF12, ModShift | ModCtrl},
{"24;7~", KeyF12, ModAlt | ModCtrl},
{"24;8~", KeyF12, ModShift | ModAlt | ModCtrl},
}
// SS3 sequences (ESC O ...)
var ss3Sequences = []escapeSequence{
{"A", KeyUp, ModNone},
{"B", KeyDown, ModNone},
{"C", KeyRight, ModNone},
{"D", KeyLeft, ModNone},
{"H", KeyHome, ModNone},
{"F", KeyEnd, ModNone},
{"P", KeyF1, ModNone},
{"Q", KeyF2, ModNone},
{"R", KeyF3, ModNone},
{"S", KeyF4, ModNone},
// Numeric Keypad (Application Mode)
{"M", KeyEnter, ModNone}, // Keypad Enter
{"X", KeyRune, ModNone}, // Keypad = (some terminals)
{"j", KeyRune, ModNone}, // Keypad *
{"k", KeyRune, ModNone}, // Keypad +
{"l", KeyRune, ModNone}, // Keypad ,
{"m", KeyRune, ModNone}, // Keypad -
{"n", KeyRune, ModNone}, // Keypad .
{"o", KeyRune, ModNone}, // Keypad /
{"p", KeyRune, ModNone}, // Keypad 0
{"q", KeyRune, ModNone}, // Keypad 1
{"r", KeyRune, ModNone}, // Keypad 2
{"s", KeyRune, ModNone}, // Keypad 3
{"t", KeyRune, ModNone}, // Keypad 4
{"u", KeyRune, ModNone}, // Keypad 5
{"v", KeyRune, ModNone}, // Keypad 6
{"w", KeyRune, ModNone}, // Keypad 7
{"x", KeyRune, ModNone}, // Keypad 8
{"y", KeyRune, ModNone}, // Keypad 9
}
var csiMap = buildSequenceMap(csiSequences)
var ss3Map = buildSequenceMap(ss3Sequences)
func buildSequenceMap(seqs []escapeSequence) map[string]escapeSequence {
m := make(map[string]escapeSequence, len(seqs))
for _, s := range seqs {
m[s.seq] = s
}
return m
}
// lookupCSI performs zero-alloc map lookup via compiler optimization
// The string([]byte) conversion inline in map access does not allocate
func lookupCSI(seq []byte) (Key, Modifier, bool) {
if s, ok := csiMap[string(seq)]; ok {
return s.key, s.mod, true
}
return KeyNone, ModNone, false
}
// lookupSS3 performs zero-alloc map lookup
func lookupSS3(seq []byte) (Key, Modifier, bool) {
if s, ok := ss3Map[string(seq)]; ok {
return s.key, s.mod, true
}
return KeyNone, ModNone, false
}