Coverage for src / lexigram / admin / actions / shortcuts.py: 0%
30 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-11 02:25 +0800
1"""Generic keyboard shortcut manager for action types.
3Provides a reusable :class:`KeyboardShortcutManager` that works with any
4action type that has ``keyboard_shortcut`` and ``name`` attributes.
5"""
7from __future__ import annotations
9from typing import Generic, Protocol, TypeVar
12class _HasShortcut(Protocol):
13 """Minimal protocol for actions that support keyboard shortcuts."""
15 keyboard_shortcut: str | None
16 name: str
19ActionT = TypeVar("ActionT", bound=_HasShortcut)
22class KeyboardShortcutManager(Generic[ActionT]):
23 """Manages keyboard shortcuts for any action type.
25 Handles shortcut registration, lookup, and formatting. Sub-managers
26 extend this class to add type-specific execution logic (e.g., passing
27 a ``record_id`` for row actions).
28 """
30 def __init__(self) -> None:
31 """Initialize the shortcut manager."""
32 self._shortcuts: dict[str, ActionT] = {}
34 def register_action(self, action: ActionT) -> None:
35 """Register an action under its keyboard shortcut."""
36 if action.keyboard_shortcut:
37 self._shortcuts[action.keyboard_shortcut] = action
39 def unregister_action(self, shortcut: str) -> None:
40 """Remove a shortcut registration."""
41 self._shortcuts.pop(shortcut, None)
43 def get_action_for_shortcut(self, shortcut: str) -> ActionT | None:
44 """Return the action bound to *shortcut*, or ``None``."""
45 return self._shortcuts.get(shortcut)
47 def get_registered_shortcuts(self) -> dict[str, str]:
48 """Return a mapping of shortcut → action name."""
49 return {sc: action.name for sc, action in self._shortcuts.items()}
51 def is_shortcut_registered(self, shortcut: str) -> bool:
52 """Return ``True`` if *shortcut* is currently registered."""
53 return shortcut in self._shortcuts
55 def clear_all_shortcuts(self) -> None:
56 """Remove all registered shortcuts."""
57 self._shortcuts.clear()
59 @staticmethod
60 def normalize_shortcut(shortcut: str) -> str:
61 """Normalise a shortcut string to lowercase with no spaces.
63 Examples:
64 ``'Ctrl+R'`` → ``'ctrl+r'``
65 """
66 return shortcut.lower().replace(" ", "")
68 @staticmethod
69 def format_shortcut(shortcut: str) -> str:
70 """Format a normalised shortcut for display.
72 Examples:
73 ``'ctrl+r'`` → ``'Ctrl+R'``
74 ``'delete'`` → ``'Del'``
75 """
76 _SPECIAL = {
77 "ctrl": "Ctrl",
78 "cmd": "Cmd",
79 "alt": "Alt",
80 "shift": "Shift",
81 "delete": "Del",
82 }
83 parts = shortcut.split("+")
84 return "+".join(_SPECIAL.get(p, p.upper()) for p in parts)