Move the action parsing code into options_types

This commit is contained in:
Kovid Goyal
2021-05-26 09:11:18 +05:30
parent d7aa9952d8
commit 6c344d4ae2
8 changed files with 199 additions and 216 deletions

View File

@@ -19,11 +19,7 @@ from .child import cached_process_data, cwd_of_process, default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .conf.utils import BadLine, to_cmdline
from .config import (
KeyAction, SubSequenceMap, common_opts_as_dict,
prepare_config_file_for_editing
)
from .options_types import MINIMUM_FONT_SIZE
from .config import common_opts_as_dict, prepare_config_file_for_editing
from .constants import (
appname, config_dir, is_macos, kitty_exe, supports_primary_selection
)
@@ -42,6 +38,7 @@ from .keys import get_shortcut, shortcut_matches
from .layout.base import set_layout_options
from .notify import notification_activated
from .options_stub import Options
from .options_types import MINIMUM_FONT_SIZE, KeyAction, SubSequenceMap
from .os_window_size import initial_window_size_func
from .rgb import Color, color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data

View File

@@ -13,7 +13,7 @@ from typing import (
from .cli_stub import CLIOptions
from .conf.utils import resolve_config
from .config import KeyAction, MouseMap
from .options_types import KeyAction, MouseMap
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .options_stub import Options as OptionsStub
from .types import MouseEvent, SingleKey

View File

@@ -9,40 +9,26 @@ from contextlib import contextmanager, suppress
from functools import partial
from typing import (
Any, Callable, Dict, FrozenSet, Generator, Iterable, List, NamedTuple,
Optional, Sequence, Set, Tuple, Type, Union
Optional, Sequence, Tuple, Type, Union
)
from . import fast_data_types as defines
from .conf.definition import as_conf_file, config_lines
from .conf.utils import (
BadLine, init_config, key_func, load_config as _load_config, merge_dicts,
BadLine, init_config, load_config as _load_config, merge_dicts,
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import all_options
from .constants import cache_dir, defconf, is_macos
from .options_stub import Options as OptionsStub
from .options_types import (
InvalidMods, env, font_features, parse_mods, parse_shortcut, symbol_map
FuncArgsType, KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap,
env, font_features, func_with_args, parse_key, parse_key_action,
parse_mouse_action, symbol_map
)
from .types import MouseEvent, SingleKey
from .typing import TypedDict
from .utils import log_error
KeyMap = Dict[SingleKey, 'KeyAction']
MouseMap = Dict[MouseEvent, 'KeyAction']
KeySequence = Tuple[SingleKey, ...]
SubSequenceMap = Dict[KeySequence, 'KeyAction']
SequenceMap = Dict[SingleKey, SubSequenceMap]
class KeyAction(NamedTuple):
func: str
args: Sequence[str] = ()
func_with_args, args_funcs = key_func()
FuncArgsType = Tuple[str, Sequence[Any]]
@func_with_args(
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
@@ -341,176 +327,6 @@ def mouse_selection(func: str, rest: str) -> FuncArgsType:
return func, [cmap[rest]]
def parse_key_action(action: str) -> Optional[KeyAction]:
parts = action.strip().split(maxsplit=1)
func = parts[0]
if len(parts) == 1:
return KeyAction(func, ())
rest = parts[1]
parser = args_funcs.get(func)
if parser is not None:
try:
func, args = parser(func, rest)
except Exception as err:
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
else:
return KeyAction(func, args)
return None
all_key_actions: Set[str] = set()
sequence_sep = '>'
class BaseDefinition:
action: KeyAction
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
if not self.action.args:
return
kitten = self.action.args[0]
rest = self.action.args[1] if len(self.action.args) > 1 else ''
changed = False
for key, expanded in aliases.items():
if key == kitten:
changed = True
kitten = expanded[0]
if len(expanded) > 1:
rest = expanded[1] + ' ' + rest
if changed:
self.action = self.action._replace(args=[kitten, rest.rstrip()])
class MouseMapping(BaseDefinition):
def __init__(self, button: int, mods: int, repeat_count: int, grabbed: bool, action: KeyAction):
self.button = button
self.mods = mods
self.repeat_count = repeat_count
self.grabbed = grabbed
self.action = action
def resolve(self, kitty_mod: int) -> None:
self.mods = defines.resolve_key_mods(kitty_mod, self.mods)
@property
def trigger(self) -> MouseEvent:
return MouseEvent(self.button, self.mods, self.repeat_count, self.grabbed)
class KeyDefinition(BaseDefinition):
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[SingleKey, ...] = ()):
self.is_sequence = is_sequence
self.action = action
self.trigger = SingleKey(mods, is_native, key)
self.rest = rest
def resolve(self, kitty_mod: int) -> None:
def r(k: SingleKey) -> SingleKey:
mods = defines.resolve_key_mods(kitty_mod, k.mods)
key = k.key
is_native = k.is_native
return SingleKey(mods, is_native, key)
self.trigger = r(self.trigger)
self.rest = tuple(map(r, self.rest))
def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
parts = val.split(maxsplit=1)
if len(parts) != 2:
return
sc, action = parts
sc, action = sc.strip().strip(sequence_sep), action.strip()
if not sc or not action:
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger: Optional[SingleKey] = None
restl: List[SingleKey] = []
for part in sc.split(sequence_sep):
try:
mods, is_native, key = parse_shortcut(part)
except InvalidMods:
return
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
if trigger is None:
trigger = SingleKey(mods, is_native, key)
else:
restl.append(SingleKey(mods, is_native, key))
rest = tuple(restl)
else:
try:
mods, is_native, key = parse_shortcut(sc)
except InvalidMods:
return
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
try:
paction = parse_key_action(action)
except Exception:
log_error('Invalid shortcut action: {}. Ignoring.'.format(
action))
else:
if paction is not None:
all_key_actions.add(paction.func)
if is_sequence:
if trigger is not None:
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], trigger[2], rest))
else:
assert key is not None
key_definitions.append(KeyDefinition(False, paction, mods, is_native, key))
def parse_mouse_action(val: str, mouse_mappings: List[MouseMapping]) -> None:
parts = val.split(maxsplit=3)
if len(parts) != 4:
log_error(f'Ignoring invalid mouse action: {val}')
return
xbutton, event, modes, action = parts
kparts = xbutton.split('+')
if len(kparts) > 1:
mparts, obutton = kparts[:-1], kparts[-1].lower()
mods = parse_mods(mparts, obutton)
if mods is None:
return
else:
obutton = parts[0].lower()
mods = 0
try:
b = {'left': 'b1', 'middle': 'b3', 'right': 'b2'}.get(obutton, obutton)[1:]
button = getattr(defines, f'GLFW_MOUSE_BUTTON_{b}')
except Exception:
log_error(f'Mouse button: {xbutton} not recognized, ignoring')
return
try:
count = {'doubleclick': -3, 'click': -2, 'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3}[event.lower()]
except KeyError:
log_error(f'Mouse event type: {event} not recognized, ignoring')
return
specified_modes = frozenset(modes.lower().split(','))
if specified_modes - {'grabbed', 'ungrabbed'}:
log_error(f'Mouse modes: {modes} not recognized, ignoring')
return
try:
paction = parse_key_action(action)
except Exception:
log_error(f'Invalid mouse action: {action}. Ignoring.')
return
if paction is None:
log_error(f'Ignoring unknown mouse action: {action}')
return
for mode in specified_modes:
mouse_mappings.append(MouseMapping(button, mods, count, mode == 'grabbed', paction))
def parse_send_text_bytes(text: str) -> bytes:
return python_string(text).encode('utf-8')
@@ -663,10 +479,6 @@ def parse_defaults(lines: Iterable[str], check_keys: bool = False) -> Dict[str,
xc = init_config(config_lines(all_options), parse_defaults)
Options: Type[OptionsStub] = xc[0]
defaults: OptionsStub = xc[1]
actions = frozenset(all_key_actions) | frozenset(
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
split()
)
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})

View File

@@ -4,15 +4,14 @@
from typing import Optional, Union
from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap
from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, KeyEvent
)
from .options_types import KeyAction, KeyMap, SequenceMap, SubSequenceMap
from .types import SingleKey
from .typing import ScreenType
mod_mask = GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER | GLFW_MOD_META | GLFW_MOD_HYPER
lock_mask = GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK

View File

@@ -12,9 +12,9 @@ from typing import (
from urllib.parse import ParseResult, unquote, urlparse
from .conf.utils import to_cmdline_implementation
from .config import KeyAction, parse_key_action
from .constants import config_dir
from .guess_mime_type import guess_type
from .options_types import KeyAction, parse_key_action
from .types import run_once
from .typing import MatchType
from .utils import expandvars, log_error

View File

@@ -17,7 +17,7 @@ def generate_stub():
all_options,
preamble_lines=(
'from kitty.types import SingleKey',
'from kitty.config import KeyAction, KeyMap, SequenceMap, MouseMap',
'from kitty.options_types import KeyAction, KeyMap, SequenceMap, MouseMap',
'from kitty.fonts import FontFeature',
),
extra_fields=(

View File

@@ -6,14 +6,15 @@
import os
import sys
from typing import (
Callable, Dict, FrozenSet, Iterable, List, Optional, Tuple, Union
Any, Callable, Dict, FrozenSet, Iterable, List, NamedTuple, Optional,
Sequence, Tuple, Union
)
import kitty.fast_data_types as defines
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .conf.utils import (
positive_float, positive_int, to_bool, to_color, uniq, unit_float
key_func, positive_float, positive_int, to_bool, to_color, uniq, unit_float
)
from .constants import config_dir
from .fonts import FontFeature
@@ -23,9 +24,14 @@ from .key_names import (
)
from .layout.interface import all_layouts
from .rgb import Color, color_as_int
from .types import FloatEdges, SingleKey
from .types import FloatEdges, MouseEvent, SingleKey
from .utils import expandvars, log_error
KeyMap = Dict[SingleKey, 'KeyAction']
MouseMap = Dict[MouseEvent, 'KeyAction']
KeySequence = Tuple[SingleKey, ...]
SubSequenceMap = Dict[KeySequence, 'KeyAction']
SequenceMap = Dict[SingleKey, SubSequenceMap]
MINIMUM_FONT_SIZE = 4
default_tab_separator = ''
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER',
@@ -33,6 +39,14 @@ mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER',
character_key_name_aliases_with_ascii_lowercase: Dict[str, str] = character_key_name_aliases.copy()
for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
character_key_name_aliases_with_ascii_lowercase[x] = x.lower()
sequence_sep = '>'
func_with_args, args_funcs = key_func()
FuncArgsType = Tuple[str, Sequence[Any]]
class KeyAction(NamedTuple):
func: str
args: Sequence[str] = ()
class InvalidMods(ValueError):
@@ -400,3 +414,168 @@ def symbol_map(val: str) -> Iterable[Tuple[Tuple[int, int], str]]:
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
return abort()
yield (a, b), family
def parse_key_action(action: str) -> Optional[KeyAction]:
parts = action.strip().split(maxsplit=1)
func = parts[0]
if len(parts) == 1:
return KeyAction(func, ())
rest = parts[1]
parser = args_funcs.get(func)
if parser is not None:
try:
func, args = parser(func, rest)
except Exception as err:
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
else:
return KeyAction(func, args)
return None
class BaseDefinition:
action: KeyAction
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
if not self.action.args:
return
kitten = self.action.args[0]
rest = self.action.args[1] if len(self.action.args) > 1 else ''
changed = False
for key, expanded in aliases.items():
if key == kitten:
changed = True
kitten = expanded[0]
if len(expanded) > 1:
rest = expanded[1] + ' ' + rest
if changed:
self.action = self.action._replace(args=[kitten, rest.rstrip()])
class MouseMapping(BaseDefinition):
def __init__(self, button: int, mods: int, repeat_count: int, grabbed: bool, action: KeyAction):
self.button = button
self.mods = mods
self.repeat_count = repeat_count
self.grabbed = grabbed
self.action = action
def resolve(self, kitty_mod: int) -> None:
self.mods = defines.resolve_key_mods(kitty_mod, self.mods)
@property
def trigger(self) -> MouseEvent:
return MouseEvent(self.button, self.mods, self.repeat_count, self.grabbed)
class KeyDefinition(BaseDefinition):
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[SingleKey, ...] = ()):
self.is_sequence = is_sequence
self.action = action
self.trigger = SingleKey(mods, is_native, key)
self.rest = rest
def resolve(self, kitty_mod: int) -> None:
def r(k: SingleKey) -> SingleKey:
mods = defines.resolve_key_mods(kitty_mod, k.mods)
key = k.key
is_native = k.is_native
return SingleKey(mods, is_native, key)
self.trigger = r(self.trigger)
self.rest = tuple(map(r, self.rest))
def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
parts = val.split(maxsplit=1)
if len(parts) != 2:
return
sc, action = parts
sc, action = sc.strip().strip(sequence_sep), action.strip()
if not sc or not action:
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger: Optional[SingleKey] = None
restl: List[SingleKey] = []
for part in sc.split(sequence_sep):
try:
mods, is_native, key = parse_shortcut(part)
except InvalidMods:
return
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
if trigger is None:
trigger = SingleKey(mods, is_native, key)
else:
restl.append(SingleKey(mods, is_native, key))
rest = tuple(restl)
else:
try:
mods, is_native, key = parse_shortcut(sc)
except InvalidMods:
return
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
try:
paction = parse_key_action(action)
except Exception:
log_error('Invalid shortcut action: {}. Ignoring.'.format(
action))
else:
if paction is not None:
if is_sequence:
if trigger is not None:
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], trigger[2], rest))
else:
assert key is not None
key_definitions.append(KeyDefinition(False, paction, mods, is_native, key))
def parse_mouse_action(val: str, mouse_mappings: List[MouseMapping]) -> None:
parts = val.split(maxsplit=3)
if len(parts) != 4:
log_error(f'Ignoring invalid mouse action: {val}')
return
xbutton, event, modes, action = parts
kparts = xbutton.split('+')
if len(kparts) > 1:
mparts, obutton = kparts[:-1], kparts[-1].lower()
mods = parse_mods(mparts, obutton)
if mods is None:
return
else:
obutton = parts[0].lower()
mods = 0
try:
b = {'left': 'b1', 'middle': 'b3', 'right': 'b2'}.get(obutton, obutton)[1:]
button = getattr(defines, f'GLFW_MOUSE_BUTTON_{b}')
except Exception:
log_error(f'Mouse button: {xbutton} not recognized, ignoring')
return
try:
count = {'doubleclick': -3, 'click': -2, 'release': -1, 'press': 1, 'doublepress': 2, 'triplepress': 3}[event.lower()]
except KeyError:
log_error(f'Mouse event type: {event} not recognized, ignoring')
return
specified_modes = frozenset(modes.lower().split(','))
if specified_modes - {'grabbed', 'ungrabbed'}:
log_error(f'Mouse modes: {modes} not recognized, ignoring')
return
try:
paction = parse_key_action(action)
except Exception:
log_error(f'Invalid mouse action: {action}. Ignoring.')
return
if paction is None:
log_error(f'Ignoring unknown mouse action: {action}')
return
for mode in specified_modes:
mouse_mappings.append(MouseMapping(button, mods, count, mode == 'grabbed', paction))

View File

@@ -1,11 +1,7 @@
from asyncio import AbstractEventLoop as AbstractEventLoop
from socket import AddressFamily as AddressFamily, socket as Socket
from subprocess import (
CompletedProcess as CompletedProcess, Popen as PopenType
)
from typing import (
Literal, Protocol as Protocol, TypedDict as TypedDict
)
from subprocess import CompletedProcess as CompletedProcess, Popen as PopenType
from typing import Literal, Protocol as Protocol, TypedDict as TypedDict
from kittens.hints.main import Mark as MarkType
from kittens.tui.handler import Handler as HandlerType
@@ -21,16 +17,16 @@ from kitty.conf.utils import KittensKeyAction as KittensKeyActionType
from .boss import Boss as BossType
from .child import Child as ChildType
from .conf.utils import BadLine as BadLineType
from .config import (
KeyAction as KeyActionType, KeyMap as KeyMap,
KittyCommonOpts as KittyCommonOpts, SequenceMap as SequenceMap
)
from .config import KittyCommonOpts
from .fast_data_types import (
CoreTextFont as CoreTextFont, FontConfigPattern as FontConfigPattern,
Screen as ScreenType, StartupCtx as StartupCtx
)
from .key_encoding import KeyEvent as KeyEventType
from .layout.base import Layout as LayoutType
from .options_types import (
KeyAction as KeyActionType, KeyMap as KeyMap, SequenceMap as SequenceMap
)
from .rc.base import RemoteCommand as RemoteCommandType
from .session import Session as SessionType, Tab as SessionTab
from .tabs import (