diff --git a/kitty/boss.py b/kitty/boss.py index d34017ded..3133d6870 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1369,6 +1369,13 @@ class Boss: return False if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev): return True + if not is_root_mode: + if mode.on_unknown in ('beep', 'ignore'): + if mode.on_unknown == 'beep' and get_options().enable_audio_bell: + ring_bell() + return True + if mode.on_unknown == 'passthrough': + return False if not self.pop_keyboard_mode(): if get_options().enable_audio_bell: ring_bell() @@ -1380,7 +1387,7 @@ class Boss: if final_actions[0].is_sequence: if not mode.is_sequence: sm = KeyboardMode('__sequence__') - sm.end_on_action = True + sm.on_action = 'end' sm.is_sequence = True for fa in final_actions: sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy()) @@ -1399,7 +1406,7 @@ class Boss: return True final_action = final_actions[0] consumed = self.combine(final_action.definition) - if consumed and not is_root_mode and mode.end_on_action: + if consumed and not is_root_mode and mode.on_action == 'end': if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode: del self.keyboard_mode_stack[mode_pos] if not self.keyboard_mode_stack: @@ -1463,7 +1470,7 @@ class Boss: self.current_visual_select.window_used_for_selection_id = 0 if w is None else w.id return km = KeyboardMode('__visual_select__') - km.end_on_action = True + km.on_action = 'end' fmap = get_name_to_functional_number_map() alphanumerics = get_options().visual_window_select_characters for idx, window in tab.windows.iter_windows_with_number(only_visible=True): diff --git a/kitty/config.py b/kitty/config.py index 324f9ffb6..67214175f 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -105,8 +105,8 @@ def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = for defn in defns: if defn.options.new_mode: modes[defn.options.new_mode] = nm = KeyboardMode(defn.options.new_mode) - nm.passthrough_unknown = defn.options.passthrough_unknown - nm.end_on_action = defn.options.end_on_action + nm.on_unknown = defn.options.on_unknown + nm.on_action = defn.options.on_action defn.definition = f'push_keyboard_mode {defn.options.new_mode}' try: m = modes[defn.options.mode] diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 87eeafbc0..a4c6da923 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -8,7 +8,25 @@ import sys from collections import defaultdict from dataclasses import dataclass, fields from functools import lru_cache -from typing import Any, Callable, Container, Dict, FrozenSet, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple, Union +from typing import ( + Any, + Callable, + Container, + Dict, + FrozenSet, + Generic, + Iterable, + Iterator, + List, + Literal, + NamedTuple, + Optional, + Sequence, + Tuple, + TypeVar, + Union, + get_args, +) import kitty.fast_data_types as defines from kitty.conf.utils import ( @@ -1134,20 +1152,29 @@ class MouseMapping(BaseDefinition): return MouseEvent(self.button, self.mods, self.repeat_count, self.grabbed) -class BoolField: - def __init__(self, default: bool = False): - self._default = default +T = TypeVar('T') + + +class LiteralField(Generic[T]): + def __init__(self, vals: Tuple[T, ...]): + self._vals = vals def __set_name__(self, owner: object, name: str) -> None: self._name = "_" + name - def __get__(self, obj: object, type: Optional[type] = None) -> bool: + def __get__(self, obj: object, type: Optional[type] = None) -> T: if obj is None: - return self._default - return getattr(obj, self._name, self._default) + return self._vals[0] + return getattr(obj, self._name, self._vals[0]) def __set__(self, obj: object, value: str) -> None: - setattr(obj, self._name, to_bool(value)) + if value not in self._vals: + raise KeyError(f'Invalid value for {self._name[1:]}: {value!r}') + setattr(obj, self._name, value) + + +OnUnknown = Literal['beep', 'end', 'ignore', 'passthrough'] +OnAction = Literal['keep', 'end'] @dataclass(init=False, frozen=True) @@ -1155,8 +1182,8 @@ class KeyMapOptions: when_focus_on: str = '' new_mode: str = '' mode: str = '' - passthrough_unknown: BoolField = BoolField(False) - end_on_action: BoolField = BoolField(False) + on_unknown = LiteralField[OnUnknown](get_args(OnUnknown)) + on_action = LiteralField[OnAction](get_args(OnAction)) default_key_map_options = KeyMapOptions() @@ -1205,8 +1232,8 @@ class KeyDefinition(BaseDefinition): class KeyboardMode: - passthrough_unknown: bool = False - end_on_action: bool = False + on_unknown: OnUnknown = get_args(OnUnknown)[0] + on_action : OnAction = get_args(OnAction)[0] is_sequence: bool = False def __init__(self, name: str = '') -> None: