Refactor single key config parsing to use a special type

This commit is contained in:
Kovid Goyal 2021-01-07 18:07:17 +05:30
parent 31cb68840a
commit b94d2b27f4
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 86 additions and 72 deletions

View file

@ -25,7 +25,8 @@
)
from .config_data import MINIMUM_FONT_SIZE
from .constants import (
appname, config_dir, is_macos, kitty_exe, supports_primary_selection
SingleKey, appname, config_dir, is_macos, kitty_exe,
supports_primary_selection
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
@ -140,7 +141,7 @@ def __init__(
opts: Options,
args: CLIOptions,
cached_values: Dict[str, Any],
new_os_window_trigger: Optional[Tuple[int, bool, int]]
new_os_window_trigger: Optional[SingleKey]
):
set_layout_options(opts)
self.clipboard_buffers: Dict[str, str] = {}

View file

@ -14,9 +14,9 @@
from .cli_stub import CLIOptions
from .conf.utils import resolve_config
from .config import KeyAction
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .constants import appname, defconf, is_macos, is_wayland, str_version, SingleKey
from .options_stub import Options as OptionsStub
from .typing import BadLineType, KeySpec, SequenceMap, TypedDict
from .typing import BadLineType, SequenceMap, TypedDict
class OptionDict(TypedDict):
@ -755,10 +755,10 @@ def parse_args(
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
ShortcutMap = Dict[Tuple[KeySpec, ...], KeyAction]
ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction]
def print_shortcut(key_sequence: Iterable[KeySpec], action: KeyAction) -> None:
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
if not getattr(print_shortcut, 'maps', None):
from kitty.keys import defines
v = vars(defines)
@ -786,7 +786,7 @@ def print_shortcut(key_sequence: Iterable[KeySpec], action: KeyAction) -> None:
print('\t', ' > '.join(keys), action)
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[KeySpec, ...]]) -> None:
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]]) -> None:
if changes:
print(title(text))
@ -804,7 +804,7 @@ def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None:
def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
ans: Dict[Tuple[KeySpec, ...], KeyAction] = {}
ans: Dict[Tuple[SingleKey, ...], KeyAction] = {}
for key_spec, rest_map in m.items():
for r, action in rest_map.items():
ans[(key_spec,) + (r)] = action

View file

@ -19,44 +19,17 @@
BadLine, init_config, key_func, load_config as _load_config, merge_dicts,
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import all_options, parse_mods, type_convert
from .constants import cache_dir, defconf, is_macos
from .config_data import InvalidMods, all_options, parse_shortcut, type_convert
from .constants import SingleKey, cache_dir, defconf, is_macos
from .fonts import FontFeature
from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub
from .typing import TypedDict
from .utils import expandvars, log_error
KeySpec = Tuple[int, bool, int]
KeyMap = Dict[KeySpec, 'KeyAction']
KeySequence = Tuple[KeySpec, ...]
KeyMap = Dict[SingleKey, 'KeyAction']
KeySequence = Tuple[SingleKey, ...]
SubSequenceMap = Dict[KeySequence, 'KeyAction']
SequenceMap = Dict[KeySpec, SubSequenceMap]
class InvalidMods(ValueError):
pass
def parse_shortcut(sc: str) -> Tuple[int, bool, Optional[int]]:
parts = sc.split('+')
mods = 0
if len(parts) > 1:
mods = parse_mods(parts[:-1], sc) or 0
if not mods:
raise InvalidMods('Invalid shortcut')
q = parts[-1].upper()
key: Optional[int] = getattr(defines, 'GLFW_KEY_' + key_name_aliases.get(q, q), None)
is_native = False
if key is None:
q = parts[-1]
if q.startswith('0x'):
with suppress(Exception):
key = int(q, 16)
else:
key = get_key_name_lookup()(q, False)
is_native = key is not None
return mods, is_native, key
SequenceMap = Dict[SingleKey, SubSequenceMap]
class KeyAction(NamedTuple):
@ -371,15 +344,22 @@ def parse_key_action(action: str) -> Optional[KeyAction]:
class KeyDefinition:
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[KeySpec, ...] = ()):
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 = mods, is_native, key
self.trigger = SingleKey(mods, is_native, key)
self.rest = rest
def resolve(self, kitty_mod: int) -> None:
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1], self.trigger[2]
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), is_native, key) for mods, is_native, key in self.rest)
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 resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
if not self.action.args:
@ -407,28 +387,28 @@ def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger: Optional[Tuple[int, bool, int]] = None
restl: List[Tuple[int, bool, int]] = []
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 is None:
if key is defines.GLFW_KEY_UNKNOWN:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
if trigger is None:
trigger = mods, is_native, key
trigger = SingleKey(mods, is_native, key)
else:
restl.append((mods, is_native, key))
restl.append(SingleKey(mods, is_native, key))
rest = tuple(restl)
else:
try:
mods, is_native, key = parse_shortcut(sc)
except InvalidMods:
return
if key is None:
if key is defines.GLFW_KEY_UNKNOWN:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return

View file

@ -4,6 +4,7 @@
# Utils {{{
import os
from contextlib import suppress
from gettext import gettext as _
from typing import (
Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set,
@ -16,15 +17,21 @@
choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color,
to_color_or_none, unit_float
)
from .constants import FloatEdges, config_dir, is_macos
from .constants import (
FloatEdges, SingleKey, config_dir, is_macos
)
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .key_names import get_key_name_lookup, key_name_aliases
from .layout.interface import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
from .utils import log_error
class InvalidMods(ValueError):
pass
MINIMUM_FONT_SIZE = 4
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER',
'': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
@ -53,6 +60,27 @@ def to_modifiers(val: str) -> int:
return parse_mods(val.split('+'), val) or 0
def parse_shortcut(sc: str) -> SingleKey:
parts = sc.split('+')
mods = 0
if len(parts) > 1:
mods = parse_mods(parts[:-1], sc) or 0
if not mods:
raise InvalidMods('Invalid shortcut')
q = parts[-1].upper()
key: Optional[int] = getattr(defines, 'GLFW_KEY_' + key_name_aliases.get(q, q), None)
is_native = False
if key is None:
q = parts[-1]
if q.startswith('0x'):
with suppress(Exception):
key = int(q, 16)
else:
key = get_key_name_lookup()(q, False)
is_native = key is not None
return SingleKey(mods, is_native, key or defines.GLFW_KEY_UNKNOWN)
T = TypeVar('T')

View file

@ -60,6 +60,12 @@ class WindowGeometry(NamedTuple):
spaces: Edges = Edges()
class SingleKey(NamedTuple):
mods: int = 0
is_native: bool = False
key: int = -1
@lru_cache(maxsize=2)
def kitty_exe() -> str:
rpath = sys._xoptions.get('bundle_exe_dir')

View file

@ -6,7 +6,8 @@
from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union
from . import fast_data_types as defines
from .config import KeyAction, KeyMap, KeySpec, SequenceMap, SubSequenceMap
from .constants import SingleKey
from .config import KeyAction, KeyMap, SequenceMap, SubSequenceMap
from .key_encoding import KEY_MAP
from .terminfo import key_as_bytes, modify_key_bytes
from .typing import ScreenType, WindowType
@ -278,13 +279,13 @@ def interpret_key_event(key: int, native_key: int, mods: int, window: WindowType
def get_shortcut(keymap: Union[KeyMap, SequenceMap], mods: int, key: int, native_key: int) -> Optional[Union[KeyAction, SubSequenceMap]]:
mods &= 0b1111
ans = keymap.get((mods, False, key))
ans = keymap.get(SingleKey(mods, False, key))
if ans is None:
ans = keymap.get((mods, True, native_key))
ans = keymap.get(SingleKey(mods, True, native_key))
return ans
def shortcut_matches(s: KeySpec, mods: int, key: int, native_key: int) -> bool:
def shortcut_matches(s: SingleKey, mods: int, key: int, native_key: int) -> bool:
mods &= 0b1111
q = native_key if s[1] else key
return bool(s[0] & 0b1111 == mods & 0b1111 and s[2] == q)

View file

@ -7,7 +7,7 @@
import shutil
import sys
from contextlib import contextmanager, suppress
from typing import Generator, List, Mapping, Optional, Sequence, Tuple
from typing import Generator, List, Mapping, Optional, Sequence
from .borders import load_borders_program
from .boss import Boss
@ -17,7 +17,7 @@
from .conf.utils import BadLine
from .config import cached_values_for
from .constants import (
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
SingleKey, appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
is_wayland, kitty_exe, logo_data_file, running_in_kitty
)
from .fast_data_types import (
@ -103,7 +103,7 @@ def init_glfw(opts: OptionsStub, debug_keyboard: bool = False) -> str:
return glfw_module
def get_new_os_window_trigger(opts: OptionsStub) -> Optional[Tuple[int, bool, int]]:
def get_new_os_window_trigger(opts: OptionsStub) -> Optional[SingleKey]:
new_os_window_trigger = None
if is_macos:
new_os_window_shortcuts = []

View file

@ -20,7 +20,8 @@ def generate_stub():
'font_features': 'typing.Dict[str, typing.Tuple[str, ...]]'
},
preamble_lines=(
'from kitty.config import KeyAction, KeyMap, SequenceMap, KeySpec',
'from kitty.constants import SingleKey',
'from kitty.config import KeyAction, KeyMap, SequenceMap',
),
extra_fields=(
('keymap', 'KeyMap'),

View file

@ -6,7 +6,7 @@
BossType = ChildType = TabType = WindowType = ScreenType = None
BadLineType = KeySpec = SequenceMap = KeyActionType = None
BadLineType = SequenceMap = KeyActionType = None
AddressFamily = PopenType = Socket = StartupCtx = None
SessionTab = SessionType = LayoutType = SpecialWindowInstance = None
MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None

View file

@ -1,5 +1,8 @@
from asyncio import AbstractEventLoop as AbstractEventLoop # noqa
from socket import AddressFamily as AddressFamily, socket as Socket # noqa
from subprocess import ( # noqa; noqa
CompletedProcess as CompletedProcess, Popen as PopenType
)
from typing import ( # noqa
Literal, Protocol as Protocol, TypedDict as TypedDict
)
@ -18,6 +21,10 @@ from kitty.conf.utils import KittensKeyAction as KittensKeyActionType # noqa
from .boss import Boss as BossType # noqa
from .child import Child as ChildType # noqa
from .conf.utils import BadLine as BadLineType # noqa
from .config import ( # noqa; noqa
KeyAction as KeyActionType, KeyMap as KeyMap,
KittyCommonOpts as KittyCommonOpts, SequenceMap as SequenceMap
)
from .fast_data_types import ( # noqa
CoreTextFont as CoreTextFont, FontConfigPattern as FontConfigPattern,
Screen as ScreenType, StartupCtx as StartupCtx
@ -32,16 +39,6 @@ from .tabs import ( # noqa
from .utils import ScreenSize as ScreenSize # noqa
from .window import Window as WindowType # noqa
from subprocess import ( # noqa; noqa
CompletedProcess as CompletedProcess, Popen as PopenType
)
from .config import ( # noqa; noqa
KeyAction as KeyActionType, KeyMap as KeyMap, KeySpec as KeySpec,
KittyCommonOpts as KittyCommonOpts, SequenceMap as SequenceMap
)
EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches']
GRT_a = Literal['t', 'T', 'q', 'p', 'd']