Implement key event parsing and matching
This commit is contained in:
parent
63fdbd3fa0
commit
d6ed20323b
22
gen-rc-go.py
22
gen-rc-go.py
@ -3,16 +3,18 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
import kitty.constants as kc
|
import kitty.constants as kc
|
||||||
from kittens.tui.operations import Mode
|
from kittens.tui.operations import Mode
|
||||||
from kitty.cli import OptionDict, OptionSpecSeq, parse_option_spec
|
from kitty.cli import OptionDict, OptionSpecSeq, parse_option_spec
|
||||||
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||||
|
from kitty.key_names import functional_key_name_aliases, character_key_name_aliases
|
||||||
|
from kitty.key_encoding import config_mod_map
|
||||||
|
|
||||||
|
|
||||||
def serialize_as_go_string(x: str) -> str:
|
def serialize_as_go_string(x: str) -> str:
|
||||||
return x.replace('\n', '\\n').replace('"', '\\"')
|
return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
|
||||||
|
|
||||||
|
|
||||||
def replace(template: str, **kw: str) -> str:
|
def replace(template: str, **kw: str) -> str:
|
||||||
@ -183,6 +185,19 @@ def build_go_code(name: str, cmd: RemoteCommand, seq: OptionSpecSeq, template: s
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int], Dict[str, str]]) -> str:
|
||||||
|
ans = []
|
||||||
|
|
||||||
|
def s(x: Union[int, str]) -> str:
|
||||||
|
if isinstance(x, int):
|
||||||
|
return str(x)
|
||||||
|
return f'"{serialize_as_go_string(x)}"'
|
||||||
|
|
||||||
|
for k, v in x.items():
|
||||||
|
ans.append(f'{s(k)}: {s(v)}')
|
||||||
|
return '{' + ', '.join(ans) + '}'
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
with open('constants_generated.go', 'w') as f:
|
with open('constants_generated.go', 'w') as f:
|
||||||
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
||||||
@ -202,6 +217,9 @@ const IsFrozenBuild bool = false
|
|||||||
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
||||||
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
||||||
var DefaultPager []string = []string{{ {dp} }}
|
var DefaultPager []string = []string{{ {dp} }}
|
||||||
|
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
|
||||||
|
var CharacterKeyNameAliases = map[string]string{serialize_go_dict(character_key_name_aliases)}
|
||||||
|
var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)}
|
||||||
''')
|
''')
|
||||||
with open('tools/cmd/at/template.go') as f:
|
with open('tools/cmd/at/template.go') as f:
|
||||||
template = f.read()
|
template = f.read()
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from typing import Callable, Dict, Optional
|
|||||||
|
|
||||||
from .constants import is_macos
|
from .constants import is_macos
|
||||||
|
|
||||||
functional_key_name_aliases = {
|
functional_key_name_aliases: Dict[str, str] = {
|
||||||
'ESC': 'ESCAPE',
|
'ESC': 'ESCAPE',
|
||||||
'PGUP': 'PAGE_UP',
|
'PGUP': 'PAGE_UP',
|
||||||
'PAGEUP': 'PAGE_UP',
|
'PAGEUP': 'PAGE_UP',
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"kitty"
|
||||||
|
)
|
||||||
|
|
||||||
// key encoding mappings {{{
|
// key encoding mappings {{{
|
||||||
// start csi mapping (auto generated by gen-key-constants.py do not edit)
|
// start csi mapping (auto generated by gen-key-constants.py do not edit)
|
||||||
var functional_key_number_to_name_map = map[int]string{57344: "ESCAPE", 57345: "ENTER", 57346: "TAB", 57347: "BACKSPACE", 57348: "INSERT", 57349: "DELETE", 57350: "LEFT", 57351: "RIGHT", 57352: "UP", 57353: "DOWN", 57354: "PAGE_UP", 57355: "PAGE_DOWN", 57356: "HOME", 57357: "END", 57358: "CAPS_LOCK", 57359: "SCROLL_LOCK", 57360: "NUM_LOCK", 57361: "PRINT_SCREEN", 57362: "PAUSE", 57363: "MENU", 57364: "F1", 57365: "F2", 57366: "F3", 57367: "F4", 57368: "F5", 57369: "F6", 57370: "F7", 57371: "F8", 57372: "F9", 57373: "F10", 57374: "F11", 57375: "F12", 57376: "F13", 57377: "F14", 57378: "F15", 57379: "F16", 57380: "F17", 57381: "F18", 57382: "F19", 57383: "F20", 57384: "F21", 57385: "F22", 57386: "F23", 57387: "F24", 57388: "F25", 57389: "F26", 57390: "F27", 57391: "F28", 57392: "F29", 57393: "F30", 57394: "F31", 57395: "F32", 57396: "F33", 57397: "F34", 57398: "F35", 57399: "KP_0", 57400: "KP_1", 57401: "KP_2", 57402: "KP_3", 57403: "KP_4", 57404: "KP_5", 57405: "KP_6", 57406: "KP_7", 57407: "KP_8", 57408: "KP_9", 57409: "KP_DECIMAL", 57410: "KP_DIVIDE", 57411: "KP_MULTIPLY", 57412: "KP_SUBTRACT", 57413: "KP_ADD", 57414: "KP_ENTER", 57415: "KP_EQUAL", 57416: "KP_SEPARATOR", 57417: "KP_LEFT", 57418: "KP_RIGHT", 57419: "KP_UP", 57420: "KP_DOWN", 57421: "KP_PAGE_UP", 57422: "KP_PAGE_DOWN", 57423: "KP_HOME", 57424: "KP_END", 57425: "KP_INSERT", 57426: "KP_DELETE", 57427: "KP_BEGIN", 57428: "MEDIA_PLAY", 57429: "MEDIA_PAUSE", 57430: "MEDIA_PLAY_PAUSE", 57431: "MEDIA_REVERSE", 57432: "MEDIA_STOP", 57433: "MEDIA_FAST_FORWARD", 57434: "MEDIA_REWIND", 57435: "MEDIA_TRACK_NEXT", 57436: "MEDIA_TRACK_PREVIOUS", 57437: "MEDIA_RECORD", 57438: "LOWER_VOLUME", 57439: "RAISE_VOLUME", 57440: "MUTE_VOLUME", 57441: "LEFT_SHIFT", 57442: "LEFT_CONTROL", 57443: "LEFT_ALT", 57444: "LEFT_SUPER", 57445: "LEFT_HYPER", 57446: "LEFT_META", 57447: "RIGHT_SHIFT", 57448: "RIGHT_CONTROL", 57449: "RIGHT_ALT", 57450: "RIGHT_SUPER", 57451: "RIGHT_HYPER", 57452: "RIGHT_META", 57453: "ISO_LEVEL3_SHIFT", 57454: "ISO_LEVEL5_SHIFT"}
|
var functional_key_number_to_name_map = map[int]string{57344: "ESCAPE", 57345: "ENTER", 57346: "TAB", 57347: "BACKSPACE", 57348: "INSERT", 57349: "DELETE", 57350: "LEFT", 57351: "RIGHT", 57352: "UP", 57353: "DOWN", 57354: "PAGE_UP", 57355: "PAGE_DOWN", 57356: "HOME", 57357: "END", 57358: "CAPS_LOCK", 57359: "SCROLL_LOCK", 57360: "NUM_LOCK", 57361: "PRINT_SCREEN", 57362: "PAUSE", 57363: "MENU", 57364: "F1", 57365: "F2", 57366: "F3", 57367: "F4", 57368: "F5", 57369: "F6", 57370: "F7", 57371: "F8", 57372: "F9", 57373: "F10", 57374: "F11", 57375: "F12", 57376: "F13", 57377: "F14", 57378: "F15", 57379: "F16", 57380: "F17", 57381: "F18", 57382: "F19", 57383: "F20", 57384: "F21", 57385: "F22", 57386: "F23", 57387: "F24", 57388: "F25", 57389: "F26", 57390: "F27", 57391: "F28", 57392: "F29", 57393: "F30", 57394: "F31", 57395: "F32", 57396: "F33", 57397: "F34", 57398: "F35", 57399: "KP_0", 57400: "KP_1", 57401: "KP_2", 57402: "KP_3", 57403: "KP_4", 57404: "KP_5", 57405: "KP_6", 57406: "KP_7", 57407: "KP_8", 57408: "KP_9", 57409: "KP_DECIMAL", 57410: "KP_DIVIDE", 57411: "KP_MULTIPLY", 57412: "KP_SUBTRACT", 57413: "KP_ADD", 57414: "KP_ENTER", 57415: "KP_EQUAL", 57416: "KP_SEPARATOR", 57417: "KP_LEFT", 57418: "KP_RIGHT", 57419: "KP_UP", 57420: "KP_DOWN", 57421: "KP_PAGE_UP", 57422: "KP_PAGE_DOWN", 57423: "KP_HOME", 57424: "KP_END", 57425: "KP_INSERT", 57426: "KP_DELETE", 57427: "KP_BEGIN", 57428: "MEDIA_PLAY", 57429: "MEDIA_PAUSE", 57430: "MEDIA_PLAY_PAUSE", 57431: "MEDIA_REVERSE", 57432: "MEDIA_STOP", 57433: "MEDIA_FAST_FORWARD", 57434: "MEDIA_REWIND", 57435: "MEDIA_TRACK_NEXT", 57436: "MEDIA_TRACK_PREVIOUS", 57437: "MEDIA_RECORD", 57438: "LOWER_VOLUME", 57439: "RAISE_VOLUME", 57440: "MUTE_VOLUME", 57441: "LEFT_SHIFT", 57442: "LEFT_CONTROL", 57443: "LEFT_ALT", 57444: "LEFT_SUPER", 57445: "LEFT_HYPER", 57446: "LEFT_META", 57447: "RIGHT_SHIFT", 57448: "RIGHT_CONTROL", 57449: "RIGHT_ALT", 57450: "RIGHT_SUPER", 57451: "RIGHT_HYPER", 57452: "RIGHT_META", 57453: "ISO_LEVEL3_SHIFT", 57454: "ISO_LEVEL5_SHIFT"}
|
||||||
@ -10,3 +17,202 @@ var letter_trailer_to_csi_number_map = map[string]int{"A": 57352, "B": 57353, "C
|
|||||||
|
|
||||||
// end csi mapping
|
// end csi mapping
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
var name_to_functional_number_map map[string]int
|
||||||
|
|
||||||
|
type KeyEventType uint8
|
||||||
|
type KeyModifiers uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
PRESS KeyEventType = 1
|
||||||
|
REPEAT KeyEventType = 2
|
||||||
|
RELEASE KeyEventType = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SHIFT KeyModifiers = 1
|
||||||
|
ALT KeyModifiers = 2
|
||||||
|
CTRL KeyModifiers = 4
|
||||||
|
SUPER KeyModifiers = 8
|
||||||
|
HYPER KeyModifiers = 16
|
||||||
|
META KeyModifiers = 32
|
||||||
|
CAPS_LOCK KeyModifiers = 64
|
||||||
|
NUM_LOCK KeyModifiers = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *KeyModifiers) WithoutLocks() KeyModifiers {
|
||||||
|
return *self & ^(CAPS_LOCK | NUM_LOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyEvent struct {
|
||||||
|
Type KeyEventType
|
||||||
|
Mods KeyModifiers
|
||||||
|
Key string
|
||||||
|
ShiftedKey string
|
||||||
|
AlternateKey string
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyEventFromCSI(csi string) *KeyEvent {
|
||||||
|
if len(csi) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
last_char := csi[len(csi)-1:]
|
||||||
|
if !strings.Contains("u~ABCDEHFPQRS", last_char) || (last_char == "~" && (csi == "200~" || csi == "201~")) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
csi = csi[:len(csi)-1]
|
||||||
|
sections := strings.Split(csi, ";")
|
||||||
|
|
||||||
|
get_sub_sections := func(section string) []int {
|
||||||
|
p := strings.Split(section, ":")
|
||||||
|
ans := make([]int, len(p))
|
||||||
|
for i, x := range p {
|
||||||
|
q, err := strconv.Atoi(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ans[i] = q
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
first_section := get_sub_sections(sections[0])
|
||||||
|
second_section := make([]int, 0)
|
||||||
|
third_section := make([]int, 0)
|
||||||
|
if len(sections) > 1 {
|
||||||
|
second_section = get_sub_sections(sections[1])
|
||||||
|
}
|
||||||
|
if len(sections) > 2 {
|
||||||
|
third_section = get_sub_sections(sections[2])
|
||||||
|
}
|
||||||
|
var ans KeyEvent
|
||||||
|
keynum := first_section[0]
|
||||||
|
if val, ok := letter_trailer_to_csi_number_map[last_char]; ok {
|
||||||
|
keynum = val
|
||||||
|
}
|
||||||
|
|
||||||
|
key_name := func(keynum int) string {
|
||||||
|
switch keynum {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 13:
|
||||||
|
if last_char == "u" {
|
||||||
|
return "ENTER"
|
||||||
|
}
|
||||||
|
return "F3"
|
||||||
|
default:
|
||||||
|
if val, ok := csi_number_to_functional_number_map[keynum]; ok {
|
||||||
|
keynum = val
|
||||||
|
}
|
||||||
|
ans := ""
|
||||||
|
if val, ok := functional_key_number_to_name_map[keynum]; ok {
|
||||||
|
ans = val
|
||||||
|
} else {
|
||||||
|
ans = string(rune(keynum))
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ans.Key = key_name(keynum)
|
||||||
|
if len(first_section) > 1 {
|
||||||
|
ans.ShiftedKey = key_name(first_section[1])
|
||||||
|
}
|
||||||
|
if len(first_section) > 2 {
|
||||||
|
ans.AlternateKey = key_name(first_section[2])
|
||||||
|
}
|
||||||
|
if len(second_section) > 0 {
|
||||||
|
ans.Mods = KeyModifiers(second_section[0] - 1)
|
||||||
|
}
|
||||||
|
if len(second_section) > 1 {
|
||||||
|
switch second_section[1] {
|
||||||
|
case 2:
|
||||||
|
ans.Type = REPEAT
|
||||||
|
case 3:
|
||||||
|
ans.Type = RELEASE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(third_section) > 0 {
|
||||||
|
runes := make([]rune, len(third_section))
|
||||||
|
for i, ch := range third_section {
|
||||||
|
runes[i] = rune(ch)
|
||||||
|
}
|
||||||
|
ans.Text = string(runes)
|
||||||
|
}
|
||||||
|
return &ans
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParsedShortcut struct {
|
||||||
|
Mods KeyModifiers
|
||||||
|
KeyName string
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed_shortcut_cache map[string]ParsedShortcut
|
||||||
|
|
||||||
|
func ParseShortcut(spec string) *ParsedShortcut {
|
||||||
|
if val, ok := parsed_shortcut_cache[spec]; ok {
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
ospec := spec
|
||||||
|
if strings.HasSuffix(spec, "+") {
|
||||||
|
ospec = spec[:len(spec)-1] + "plus"
|
||||||
|
}
|
||||||
|
parts := strings.Split(ospec, "+")
|
||||||
|
key_name := parts[len(parts)-1]
|
||||||
|
if val, ok := kitty.FunctionalKeyNameAliases[strings.ToUpper(key_name)]; ok {
|
||||||
|
key_name = val
|
||||||
|
}
|
||||||
|
if _, is_functional_key := name_to_functional_number_map[strings.ToUpper(key_name)]; is_functional_key {
|
||||||
|
key_name = strings.ToUpper(key_name)
|
||||||
|
} else {
|
||||||
|
if val, ok := kitty.CharacterKeyNameAliases[strings.ToUpper(key_name)]; ok {
|
||||||
|
key_name = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ans := ParsedShortcut{KeyName: key_name}
|
||||||
|
parsed_shortcut_cache[spec] = ans
|
||||||
|
if len(parts) > 1 {
|
||||||
|
for _, q := range parts[:len(parts)-1] {
|
||||||
|
val, ok := kitty.ConfigModMap[strings.ToUpper(q)]
|
||||||
|
if ok {
|
||||||
|
ans.Mods |= KeyModifiers(val)
|
||||||
|
} else {
|
||||||
|
ans.Mods |= META << 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *KeyEvent) MatchesParsedShortcut(ps *ParsedShortcut, event_type KeyEventType) bool {
|
||||||
|
if self.Type&event_type == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mods := self.Mods.WithoutLocks()
|
||||||
|
if mods == ps.Mods && self.Key == ps.KeyName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if self.ShiftedKey != "" && mods&SHIFT != 0 && (mods & ^SHIFT) == ps.Mods && self.ShiftedKey == ps.KeyName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *KeyEvent) Matches(spec string, event_type KeyEventType) bool {
|
||||||
|
return self.MatchesParsedShortcut(ParseShortcut(spec), event_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *KeyEvent) MatchesPressOrRepeat(spec string) bool {
|
||||||
|
return self.MatchesParsedShortcut(ParseShortcut(spec), PRESS|REPEAT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *KeyEvent) MatchesRelease(spec string) bool {
|
||||||
|
return self.MatchesParsedShortcut(ParseShortcut(spec), RELEASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
name_to_functional_number_map = make(map[string]int, len(functional_key_number_to_name_map))
|
||||||
|
for k, v := range functional_key_number_to_name_map {
|
||||||
|
name_to_functional_number_map[v] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user