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 import (
) )
from .config_data import MINIMUM_FONT_SIZE from .config_data import MINIMUM_FONT_SIZE
from .constants import ( 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 ( from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
@ -140,7 +141,7 @@ class Boss:
opts: Options, opts: Options,
args: CLIOptions, args: CLIOptions,
cached_values: Dict[str, Any], cached_values: Dict[str, Any],
new_os_window_trigger: Optional[Tuple[int, bool, int]] new_os_window_trigger: Optional[SingleKey]
): ):
set_layout_options(opts) set_layout_options(opts)
self.clipboard_buffers: Dict[str, str] = {} self.clipboard_buffers: Dict[str, str] = {}

View File

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

View File

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

View File

@ -4,6 +4,7 @@
# Utils {{{ # Utils {{{
import os import os
from contextlib import suppress
from gettext import gettext as _ from gettext import gettext as _
from typing import ( from typing import (
Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Any, Callable, Dict, FrozenSet, Iterable, List, Optional, Sequence, Set,
@ -16,15 +17,21 @@ from .conf.utils import (
choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color, choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color,
to_color_or_none, unit_float 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 .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 .layout.interface import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int from .rgb import Color, color_as_int, color_as_sharp, color_from_int
from .utils import log_error from .utils import log_error
class InvalidMods(ValueError):
pass
MINIMUM_FONT_SIZE = 4 MINIMUM_FONT_SIZE = 4
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER', mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER',
'': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'} '': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
@ -53,6 +60,27 @@ def to_modifiers(val: str) -> int:
return parse_mods(val.split('+'), val) or 0 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') T = TypeVar('T')

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import os
import shutil import shutil
import sys import sys
from contextlib import contextmanager, suppress 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 .borders import load_borders_program
from .boss import Boss from .boss import Boss
@ -17,7 +17,7 @@ from .cli_stub import CLIOptions
from .conf.utils import BadLine from .conf.utils import BadLine
from .config import cached_values_for from .config import cached_values_for
from .constants import ( 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 is_wayland, kitty_exe, logo_data_file, running_in_kitty
) )
from .fast_data_types import ( from .fast_data_types import (
@ -103,7 +103,7 @@ def init_glfw(opts: OptionsStub, debug_keyboard: bool = False) -> str:
return glfw_module 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 new_os_window_trigger = None
if is_macos: if is_macos:
new_os_window_shortcuts = [] new_os_window_shortcuts = []

View File

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

View File

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

View File

@ -1,5 +1,8 @@
from asyncio import AbstractEventLoop as AbstractEventLoop # noqa from asyncio import AbstractEventLoop as AbstractEventLoop # noqa
from socket import AddressFamily as AddressFamily, socket as Socket # 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 from typing import ( # noqa
Literal, Protocol as Protocol, TypedDict as TypedDict 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 .boss import Boss as BossType # noqa
from .child import Child as ChildType # noqa from .child import Child as ChildType # noqa
from .conf.utils import BadLine as BadLineType # 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 from .fast_data_types import ( # noqa
CoreTextFont as CoreTextFont, FontConfigPattern as FontConfigPattern, CoreTextFont as CoreTextFont, FontConfigPattern as FontConfigPattern,
Screen as ScreenType, StartupCtx as StartupCtx Screen as ScreenType, StartupCtx as StartupCtx
@ -32,16 +39,6 @@ from .tabs import ( # noqa
from .utils import ScreenSize as ScreenSize # noqa from .utils import ScreenSize as ScreenSize # noqa
from .window import Window as WindowType # 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'] EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches'] MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches']
GRT_a = Literal['t', 'T', 'q', 'p', 'd'] GRT_a = Literal['t', 'T', 'q', 'p', 'd']