123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670 |
- """
- Key bindings registry.
- A `KeyBindings` object is a container that holds a list of key bindings. It has a
- very efficient internal data structure for checking which key bindings apply
- for a pressed key.
- Typical usage::
- kb = KeyBindings()
- @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT)
- def handler(event):
- # Handle ControlX-ControlC key sequence.
- pass
- It is also possible to combine multiple KeyBindings objects. We do this in the
- default key bindings. There are some KeyBindings objects that contain the Emacs
- bindings, while others contain the Vi bindings. They are merged together using
- `merge_key_bindings`.
- We also have a `ConditionalKeyBindings` object that can enable/disable a group of
- key bindings at once.
- It is also possible to add a filter to a function, before a key binding has
- been assigned, through the `key_binding` decorator.::
- # First define a key handler with the `filter`.
- @key_binding(filter=condition)
- def my_key_binding(event):
- ...
- # Later, add it to the key bindings.
- kb.add(Keys.A, my_key_binding)
- """
- from __future__ import annotations
- from abc import ABCMeta, abstractmethod, abstractproperty
- from inspect import isawaitable
- from typing import (
- TYPE_CHECKING,
- Any,
- Callable,
- Coroutine,
- Hashable,
- Sequence,
- Tuple,
- TypeVar,
- Union,
- cast,
- )
- from prompt_toolkit.cache import SimpleCache
- from prompt_toolkit.filters import FilterOrBool, Never, to_filter
- from prompt_toolkit.keys import KEY_ALIASES, Keys
- if TYPE_CHECKING:
- # Avoid circular imports.
- from .key_processor import KeyPressEvent
- # The only two return values for a mouse handler (and key bindings) are
- # `None` and `NotImplemented`. For the type checker it's best to annotate
- # this as `object`. (The consumer never expects a more specific instance:
- # checking for NotImplemented can be done using `is NotImplemented`.)
- NotImplementedOrNone = object
- # Other non-working options are:
- # * Optional[Literal[NotImplemented]]
- # --> Doesn't work, Literal can't take an Any.
- # * None
- # --> Doesn't work. We can't assign the result of a function that
- # returns `None` to a variable.
- # * Any
- # --> Works, but too broad.
- __all__ = [
- "NotImplementedOrNone",
- "Binding",
- "KeyBindingsBase",
- "KeyBindings",
- "ConditionalKeyBindings",
- "merge_key_bindings",
- "DynamicKeyBindings",
- "GlobalOnlyKeyBindings",
- ]
- # Key bindings can be regular functions or coroutines.
- # In both cases, if they return `NotImplemented`, the UI won't be invalidated.
- # This is mainly used in case of mouse move events, to prevent excessive
- # repainting during mouse move events.
- KeyHandlerCallable = Callable[
- ["KeyPressEvent"],
- Union["NotImplementedOrNone", Coroutine[Any, Any, "NotImplementedOrNone"]],
- ]
- class Binding:
- """
- Key binding: (key sequence + handler + filter).
- (Immutable binding class.)
- :param record_in_macro: When True, don't record this key binding when a
- macro is recorded.
- """
- def __init__(
- self,
- keys: tuple[Keys | str, ...],
- handler: KeyHandlerCallable,
- filter: FilterOrBool = True,
- eager: FilterOrBool = False,
- is_global: FilterOrBool = False,
- save_before: Callable[[KeyPressEvent], bool] = (lambda e: True),
- record_in_macro: FilterOrBool = True,
- ) -> None:
- self.keys = keys
- self.handler = handler
- self.filter = to_filter(filter)
- self.eager = to_filter(eager)
- self.is_global = to_filter(is_global)
- self.save_before = save_before
- self.record_in_macro = to_filter(record_in_macro)
- def call(self, event: KeyPressEvent) -> None:
- result = self.handler(event)
- # If the handler is a coroutine, create an asyncio task.
- if isawaitable(result):
- awaitable = cast(Coroutine[Any, Any, "NotImplementedOrNone"], result)
- async def bg_task() -> None:
- result = await awaitable
- if result != NotImplemented:
- event.app.invalidate()
- event.app.create_background_task(bg_task())
- elif result != NotImplemented:
- event.app.invalidate()
- def __repr__(self) -> str:
- return (
- f"{self.__class__.__name__}(keys={self.keys!r}, handler={self.handler!r})"
- )
- # Sequence of keys presses.
- KeysTuple = Tuple[Union[Keys, str], ...]
- class KeyBindingsBase(metaclass=ABCMeta):
- """
- Interface for a KeyBindings.
- """
- @abstractproperty
- def _version(self) -> Hashable:
- """
- For cache invalidation. - This should increase every time that
- something changes.
- """
- return 0
- @abstractmethod
- def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
- """
- Return a list of key bindings that can handle these keys.
- (This return also inactive bindings, so the `filter` still has to be
- called, for checking it.)
- :param keys: tuple of keys.
- """
- return []
- @abstractmethod
- def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
- """
- Return a list of key bindings that handle a key sequence starting with
- `keys`. (It does only return bindings for which the sequences are
- longer than `keys`. And like `get_bindings_for_keys`, it also includes
- inactive bindings.)
- :param keys: tuple of keys.
- """
- return []
- @abstractproperty
- def bindings(self) -> list[Binding]:
- """
- List of `Binding` objects.
- (These need to be exposed, so that `KeyBindings` objects can be merged
- together.)
- """
- return []
- # `add` and `remove` don't have to be part of this interface.
- T = TypeVar("T", bound=Union[KeyHandlerCallable, Binding])
- class KeyBindings(KeyBindingsBase):
- """
- A container for a set of key bindings.
- Example usage::
- kb = KeyBindings()
- @kb.add('c-t')
- def _(event):
- print('Control-T pressed')
- @kb.add('c-a', 'c-b')
- def _(event):
- print('Control-A pressed, followed by Control-B')
- @kb.add('c-x', filter=is_searching)
- def _(event):
- print('Control-X pressed') # Works only if we are searching.
- """
- def __init__(self) -> None:
- self._bindings: list[Binding] = []
- self._get_bindings_for_keys_cache: SimpleCache[KeysTuple, list[Binding]] = (
- SimpleCache(maxsize=10000)
- )
- self._get_bindings_starting_with_keys_cache: SimpleCache[
- KeysTuple, list[Binding]
- ] = SimpleCache(maxsize=1000)
- self.__version = 0 # For cache invalidation.
- def _clear_cache(self) -> None:
- self.__version += 1
- self._get_bindings_for_keys_cache.clear()
- self._get_bindings_starting_with_keys_cache.clear()
- @property
- def bindings(self) -> list[Binding]:
- return self._bindings
- @property
- def _version(self) -> Hashable:
- return self.__version
- def add(
- self,
- *keys: Keys | str,
- filter: FilterOrBool = True,
- eager: FilterOrBool = False,
- is_global: FilterOrBool = False,
- save_before: Callable[[KeyPressEvent], bool] = (lambda e: True),
- record_in_macro: FilterOrBool = True,
- ) -> Callable[[T], T]:
- """
- Decorator for adding a key bindings.
- :param filter: :class:`~prompt_toolkit.filters.Filter` to determine
- when this key binding is active.
- :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`.
- When True, ignore potential longer matches when this key binding is
- hit. E.g. when there is an active eager key binding for Ctrl-X,
- execute the handler immediately and ignore the key binding for
- Ctrl-X Ctrl-E of which it is a prefix.
- :param is_global: When this key bindings is added to a `Container` or
- `Control`, make it a global (always active) binding.
- :param save_before: Callable that takes an `Event` and returns True if
- we should save the current buffer, before handling the event.
- (That's the default.)
- :param record_in_macro: Record these key bindings when a macro is
- being recorded. (True by default.)
- """
- assert keys
- keys = tuple(_parse_key(k) for k in keys)
- if isinstance(filter, Never):
- # When a filter is Never, it will always stay disabled, so in that
- # case don't bother putting it in the key bindings. It will slow
- # down every key press otherwise.
- def decorator(func: T) -> T:
- return func
- else:
- def decorator(func: T) -> T:
- if isinstance(func, Binding):
- # We're adding an existing Binding object.
- self.bindings.append(
- Binding(
- keys,
- func.handler,
- filter=func.filter & to_filter(filter),
- eager=to_filter(eager) | func.eager,
- is_global=to_filter(is_global) | func.is_global,
- save_before=func.save_before,
- record_in_macro=func.record_in_macro,
- )
- )
- else:
- self.bindings.append(
- Binding(
- keys,
- cast(KeyHandlerCallable, func),
- filter=filter,
- eager=eager,
- is_global=is_global,
- save_before=save_before,
- record_in_macro=record_in_macro,
- )
- )
- self._clear_cache()
- return func
- return decorator
- def remove(self, *args: Keys | str | KeyHandlerCallable) -> None:
- """
- Remove a key binding.
- This expects either a function that was given to `add` method as
- parameter or a sequence of key bindings.
- Raises `ValueError` when no bindings was found.
- Usage::
- remove(handler) # Pass handler.
- remove('c-x', 'c-a') # Or pass the key bindings.
- """
- found = False
- if callable(args[0]):
- assert len(args) == 1
- function = args[0]
- # Remove the given function.
- for b in self.bindings:
- if b.handler == function:
- self.bindings.remove(b)
- found = True
- else:
- assert len(args) > 0
- args = cast(Tuple[Union[Keys, str]], args)
- # Remove this sequence of key bindings.
- keys = tuple(_parse_key(k) for k in args)
- for b in self.bindings:
- if b.keys == keys:
- self.bindings.remove(b)
- found = True
- if found:
- self._clear_cache()
- else:
- # No key binding found for this function. Raise ValueError.
- raise ValueError(f"Binding not found: {function!r}")
- # For backwards-compatibility.
- add_binding = add
- remove_binding = remove
- def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
- """
- Return a list of key bindings that can handle this key.
- (This return also inactive bindings, so the `filter` still has to be
- called, for checking it.)
- :param keys: tuple of keys.
- """
- def get() -> list[Binding]:
- result: list[tuple[int, Binding]] = []
- for b in self.bindings:
- if len(keys) == len(b.keys):
- match = True
- any_count = 0
- for i, j in zip(b.keys, keys):
- if i != j and i != Keys.Any:
- match = False
- break
- if i == Keys.Any:
- any_count += 1
- if match:
- result.append((any_count, b))
- # Place bindings that have more 'Any' occurrences in them at the end.
- result = sorted(result, key=lambda item: -item[0])
- return [item[1] for item in result]
- return self._get_bindings_for_keys_cache.get(keys, get)
- def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
- """
- Return a list of key bindings that handle a key sequence starting with
- `keys`. (It does only return bindings for which the sequences are
- longer than `keys`. And like `get_bindings_for_keys`, it also includes
- inactive bindings.)
- :param keys: tuple of keys.
- """
- def get() -> list[Binding]:
- result = []
- for b in self.bindings:
- if len(keys) < len(b.keys):
- match = True
- for i, j in zip(b.keys, keys):
- if i != j and i != Keys.Any:
- match = False
- break
- if match:
- result.append(b)
- return result
- return self._get_bindings_starting_with_keys_cache.get(keys, get)
- def _parse_key(key: Keys | str) -> str | Keys:
- """
- Replace key by alias and verify whether it's a valid one.
- """
- # Already a parse key? -> Return it.
- if isinstance(key, Keys):
- return key
- # Lookup aliases.
- key = KEY_ALIASES.get(key, key)
- # Replace 'space' by ' '
- if key == "space":
- key = " "
- # Return as `Key` object when it's a special key.
- try:
- return Keys(key)
- except ValueError:
- pass
- # Final validation.
- if len(key) != 1:
- raise ValueError(f"Invalid key: {key}")
- return key
- def key_binding(
- filter: FilterOrBool = True,
- eager: FilterOrBool = False,
- is_global: FilterOrBool = False,
- save_before: Callable[[KeyPressEvent], bool] = (lambda event: True),
- record_in_macro: FilterOrBool = True,
- ) -> Callable[[KeyHandlerCallable], Binding]:
- """
- Decorator that turn a function into a `Binding` object. This can be added
- to a `KeyBindings` object when a key binding is assigned.
- """
- assert save_before is None or callable(save_before)
- filter = to_filter(filter)
- eager = to_filter(eager)
- is_global = to_filter(is_global)
- save_before = save_before
- record_in_macro = to_filter(record_in_macro)
- keys = ()
- def decorator(function: KeyHandlerCallable) -> Binding:
- return Binding(
- keys,
- function,
- filter=filter,
- eager=eager,
- is_global=is_global,
- save_before=save_before,
- record_in_macro=record_in_macro,
- )
- return decorator
- class _Proxy(KeyBindingsBase):
- """
- Common part for ConditionalKeyBindings and _MergedKeyBindings.
- """
- def __init__(self) -> None:
- # `KeyBindings` to be synchronized with all the others.
- self._bindings2: KeyBindingsBase = KeyBindings()
- self._last_version: Hashable = ()
- def _update_cache(self) -> None:
- """
- If `self._last_version` is outdated, then this should update
- the version and `self._bindings2`.
- """
- raise NotImplementedError
- # Proxy methods to self._bindings2.
- @property
- def bindings(self) -> list[Binding]:
- self._update_cache()
- return self._bindings2.bindings
- @property
- def _version(self) -> Hashable:
- self._update_cache()
- return self._last_version
- def get_bindings_for_keys(self, keys: KeysTuple) -> list[Binding]:
- self._update_cache()
- return self._bindings2.get_bindings_for_keys(keys)
- def get_bindings_starting_with_keys(self, keys: KeysTuple) -> list[Binding]:
- self._update_cache()
- return self._bindings2.get_bindings_starting_with_keys(keys)
- class ConditionalKeyBindings(_Proxy):
- """
- Wraps around a `KeyBindings`. Disable/enable all the key bindings according to
- the given (additional) filter.::
- @Condition
- def setting_is_true():
- return True # or False
- registry = ConditionalKeyBindings(key_bindings, setting_is_true)
- When new key bindings are added to this object. They are also
- enable/disabled according to the given `filter`.
- :param registries: List of :class:`.KeyBindings` objects.
- :param filter: :class:`~prompt_toolkit.filters.Filter` object.
- """
- def __init__(
- self, key_bindings: KeyBindingsBase, filter: FilterOrBool = True
- ) -> None:
- _Proxy.__init__(self)
- self.key_bindings = key_bindings
- self.filter = to_filter(filter)
- def _update_cache(self) -> None:
- "If the original key bindings was changed. Update our copy version."
- expected_version = self.key_bindings._version
- if self._last_version != expected_version:
- bindings2 = KeyBindings()
- # Copy all bindings from `self.key_bindings`, adding our condition.
- for b in self.key_bindings.bindings:
- bindings2.bindings.append(
- Binding(
- keys=b.keys,
- handler=b.handler,
- filter=self.filter & b.filter,
- eager=b.eager,
- is_global=b.is_global,
- save_before=b.save_before,
- record_in_macro=b.record_in_macro,
- )
- )
- self._bindings2 = bindings2
- self._last_version = expected_version
- class _MergedKeyBindings(_Proxy):
- """
- Merge multiple registries of key bindings into one.
- This class acts as a proxy to multiple :class:`.KeyBindings` objects, but
- behaves as if this is just one bigger :class:`.KeyBindings`.
- :param registries: List of :class:`.KeyBindings` objects.
- """
- def __init__(self, registries: Sequence[KeyBindingsBase]) -> None:
- _Proxy.__init__(self)
- self.registries = registries
- def _update_cache(self) -> None:
- """
- If one of the original registries was changed. Update our merged
- version.
- """
- expected_version = tuple(r._version for r in self.registries)
- if self._last_version != expected_version:
- bindings2 = KeyBindings()
- for reg in self.registries:
- bindings2.bindings.extend(reg.bindings)
- self._bindings2 = bindings2
- self._last_version = expected_version
- def merge_key_bindings(bindings: Sequence[KeyBindingsBase]) -> _MergedKeyBindings:
- """
- Merge multiple :class:`.Keybinding` objects together.
- Usage::
- bindings = merge_key_bindings([bindings1, bindings2, ...])
- """
- return _MergedKeyBindings(bindings)
- class DynamicKeyBindings(_Proxy):
- """
- KeyBindings class that can dynamically returns any KeyBindings.
- :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance.
- """
- def __init__(self, get_key_bindings: Callable[[], KeyBindingsBase | None]) -> None:
- self.get_key_bindings = get_key_bindings
- self.__version = 0
- self._last_child_version = None
- self._dummy = KeyBindings() # Empty key bindings.
- def _update_cache(self) -> None:
- key_bindings = self.get_key_bindings() or self._dummy
- assert isinstance(key_bindings, KeyBindingsBase)
- version = id(key_bindings), key_bindings._version
- self._bindings2 = key_bindings
- self._last_version = version
- class GlobalOnlyKeyBindings(_Proxy):
- """
- Wrapper around a :class:`.KeyBindings` object that only exposes the global
- key bindings.
- """
- def __init__(self, key_bindings: KeyBindingsBase) -> None:
- _Proxy.__init__(self)
- self.key_bindings = key_bindings
- def _update_cache(self) -> None:
- """
- If one of the original registries was changed. Update our merged
- version.
- """
- expected_version = self.key_bindings._version
- if self._last_version != expected_version:
- bindings2 = KeyBindings()
- for b in self.key_bindings.bindings:
- if b.is_global():
- bindings2.bindings.append(b)
- self._bindings2 = bindings2
- self._last_version = expected_version
|