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 .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 @@ class Boss:
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 typing import (
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 @@ from .conf.utils import (
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 @@ sequence_sep = '>'
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 @@ from .conf.utils import (
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 @@ import string
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 os
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 .cli_stub import CLIOptions
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 @@ from typing import Tuple
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']