69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Callable
|
||
|
|
|
||
|
|
from textual import events
|
||
|
|
from textual._callback import invoke
|
||
|
|
from textual.dom import DOMNode
|
||
|
|
from textual.errors import DuplicateKeyHandlers
|
||
|
|
from textual.message_pump import MessagePump
|
||
|
|
|
||
|
|
|
||
|
|
async def dispatch_key(node: DOMNode, event: events.Key) -> bool:
|
||
|
|
"""Dispatch a key event to method.
|
||
|
|
|
||
|
|
This function will call the method named 'key_<event.key>' on a node if it exists.
|
||
|
|
Some keys have aliases. The first alias found will be invoked if it exists.
|
||
|
|
If multiple handlers exist that match the key, an exception is raised.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
event: A key event.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
True if key was handled, otherwise False.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
DuplicateKeyHandlers: When there's more than 1 handler that could handle this key.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def get_key_handler(pump: MessagePump, key: str) -> Callable | None:
|
||
|
|
"""Look for the public and private handler methods by name on self."""
|
||
|
|
return getattr(pump, f"key_{key}", None) or getattr(pump, f"_key_{key}", None)
|
||
|
|
|
||
|
|
handled = False
|
||
|
|
invoked_method = None
|
||
|
|
key_name = event.name
|
||
|
|
if not key_name:
|
||
|
|
return False
|
||
|
|
|
||
|
|
def _raise_duplicate_key_handlers_error(
|
||
|
|
key_name: str, first_handler: str, second_handler: str
|
||
|
|
) -> None:
|
||
|
|
"""Raise exception for case where user presses a key and there are multiple candidate key handler methods for it."""
|
||
|
|
raise DuplicateKeyHandlers(
|
||
|
|
f"Multiple handlers for key press {key_name!r}.\n"
|
||
|
|
f"We found both {first_handler!r} and {second_handler!r}, "
|
||
|
|
f"and didn't know which to call.\n"
|
||
|
|
f"Consider combining them into a single handler.",
|
||
|
|
)
|
||
|
|
|
||
|
|
try:
|
||
|
|
screen = node.screen
|
||
|
|
except Exception:
|
||
|
|
screen = None
|
||
|
|
for key_method_name in event.name_aliases:
|
||
|
|
if (key_method := get_key_handler(node, key_method_name)) is not None:
|
||
|
|
if invoked_method:
|
||
|
|
_raise_duplicate_key_handlers_error(
|
||
|
|
key_name, invoked_method.__name__, key_method.__name__
|
||
|
|
)
|
||
|
|
# If key handlers return False, then they are not considered handled
|
||
|
|
# This allows key handlers to do some conditional logic
|
||
|
|
|
||
|
|
if screen is not None and not screen.is_active:
|
||
|
|
break
|
||
|
|
handled = (await invoke(key_method, event)) is not False
|
||
|
|
invoked_method = key_method
|
||
|
|
|
||
|
|
return handled
|