From c28587bce6da1cbf56caf9e49fc7ca3eb89ca060 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 28 Oct 2026 23:50:22 -0400 Subject: [PATCH 1/2] Keep workspace shortcut availability within the active native mode --- src/toad/widget_actions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/toad/widget_actions.py b/src/toad/widget_actions.py index 340c559a9..2277090eb 100644 --- a/src/toad/widget_actions.py +++ b/src/toad/widget_actions.py @@ -196,6 +196,8 @@ class VimWorkspaceAction(ApplicationAction, declared_name=None): priority = True def key_available(self, app): + if app.current_mode != "workspace": + return False return (app.screen is app.workspace_screen and app.screen.allows_application_commands) From e37df9ee7f443954cb206ab53bffb15d2ef88cf2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 29 Oct 2026 01:22:49 -0400 Subject: [PATCH 2/2] Use native arrow preferences in the keymap catalog and focused controls --- src/toad/keymap.py | 2 +- src/toad/screens/keymap.py | 7 ++++--- src/toad/widget_actions.py | 16 ++++++++-------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/toad/keymap.py b/src/toad/keymap.py index 65d67b9db..c1ddddd1a 100644 --- a/src/toad/keymap.py +++ b/src/toad/keymap.py @@ -113,7 +113,7 @@ def capture(cls, modes: tuple[type[KeyMode], ...]) -> KeymapCatalog: for binding in declaration.exclusive_bindings(native_mode)) owned = [application] for owner in DOMNode.contexts(): - defaults = (*owner.declared_bindings(native_mode), + defaults = (*(binding for _, binding in owner.binding_defaults(native_mode)), *owner.editor_command_bindings(native_mode)) if not issubclass(owner, ModalScreen): defaults = (*defaults, *application) diff --git a/src/toad/screens/keymap.py b/src/toad/screens/keymap.py index 1bc27b94a..5ea7962f0 100644 --- a/src/toad/screens/keymap.py +++ b/src/toad/screens/keymap.py @@ -7,7 +7,6 @@ from textual import on from textual.app import ComposeResult from textual.containers import Horizontal, Vertical -from textual.keys import KeyInvocation from textual.widgets import Button, Checkbox, DataTable, Input, Static from textual.document._editing import EditingMode @@ -70,7 +69,8 @@ def populate(self, term: str) -> None: table.clear() focused_only = self.query_one("#keymap-focused", Checkbox).value self.visible_declarations = tuple(declaration for declaration in self.declarations - if (not focused_only or KeyInvocation.in_mode(declaration, self.focus_mode) in self.focused_ids) + if (not focused_only or self.catalog.require(declaration).visible_for_focus( + self.focus_mode, self.focused_ids)) and term.casefold() in (self.catalog.require(declaration).key_title() + " " + self.catalog.require(declaration).key_summary()).casefold()) keymap = self.app.settings.ui.keymap @@ -78,7 +78,8 @@ def populate(self, term: str) -> None: for row in (conflict.first, conflict.second)} for declaration in self.visible_declarations: title = Text(self.catalog.require(declaration).key_title(), - style="" if KeyInvocation.in_mode(declaration, self.focus_mode) in self.focused_ids else "dim") + style="" if self.catalog.require(declaration).visible_for_focus( + self.focus_mode, self.focused_ids) else "dim") table.add_row(title, *(Text(", ".join(self.catalog.effective(row, keymap)) or "—", style="bold red" if row.binding_id in conflicts else "") for mode in self.modes for row in (KeymapRow(declaration, mode),))) diff --git a/src/toad/widget_actions.py b/src/toad/widget_actions.py index 2277090eb..8bd6325d8 100644 --- a/src/toad/widget_actions.py +++ b/src/toad/widget_actions.py @@ -12,7 +12,7 @@ from agent_comms.command import Command from agent_comms.declared_family import DeclaredFamily from agent_comms.field_codec import FieldCodec, FieldPlan -from textual.binding import Binding, CommandBinding, ModeBinding +from textual.binding import Binding, BindingsMap, CommandBinding, ModeBinding from textual import events from textual._activation import PrimaryActivation from textual._callback import invoke @@ -73,17 +73,15 @@ def bindings(cls) -> tuple[Binding, ...]: @classmethod def mode_binding(cls, mode: type[EditingMode], key: str) -> ModeBinding: command = cls.command_for_key(mode, key) - return cls.command_binding(mode, key, command, mode) + return cls.command_binding(mode, key, command) @classmethod - def command_binding(cls, capability: type[EditingMode], key: str, command: Self, - mode: type[EditingMode]) -> ModeBinding: + def command_binding(cls, capability: type[EditingMode], key: str, command: Self) -> ModeBinding: return CommandBinding( key=key, action=command, description=cls.title, available=command.key_available, mode=capability, tooltip=cls.summary(), show=cls.show, priority=cls.priority, system=cls.system, group=cls.group, key_display=cls.key_display, - id=command.binding_identity(mode), ) @classmethod @@ -91,9 +89,11 @@ def bindings_for(cls, mode: type[EditingMode]) -> tuple[Binding, ...]: """The nearest declared capability supplies defaults; identity follows active mode.""" for capability in mode.__mro__: if capability in cls.keys: - return tuple(cls.command_binding(capability, key, - cls.command_for_key(capability, key), mode) - for key in cls.keys[capability]) + return tuple(binding for _, binding in BindingsMap( + cls.command_binding(capability, key, + cls.command_for_key(capability, key)) + for key in cls.keys[capability] + ).project(mode)) return () @classmethod