Skip to content

Commit ee59c58

Browse files
committed
Added SkipAction exception
1 parent af049d4 commit ee59c58

File tree

4 files changed

+54
-36
lines changed

4 files changed

+54
-36
lines changed

src/textual/actions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import re
55

66

7+
class SkipAction(Exception):
8+
"""Raise in an action to skip the action (and allow any parent bindings to run)."""
9+
10+
711
class ActionError(Exception):
812
pass
913

src/textual/app.py

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from rich.segment import Segment, Segments
3939
from rich.traceback import Traceback
4040

41-
from . import Logger, LogGroup, LogVerbosity, actions, events, log, messages
41+
from . import actions, Logger, LogGroup, LogVerbosity, events, log, messages
4242
from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction
4343
from ._ansi_sequences import SYNC_END, SYNC_START
4444
from ._callback import invoke
@@ -47,6 +47,7 @@
4747
from ._filter import LineFilter, Monochrome
4848
from ._path import _make_path_object_relative
4949
from ._typing import Final, TypeAlias
50+
from .actions import SkipAction
5051
from .await_remove import AwaitRemove
5152
from .binding import Binding, Bindings
5253
from .css.query import NoMatches
@@ -1752,7 +1753,7 @@ async def check_bindings(self, key: str, priority: bool = False) -> bool:
17521753
):
17531754
binding = bindings.keys.get(key)
17541755
if binding is not None and binding.priority == priority:
1755-
if await self.action(binding.action, namespace) in (True, None):
1756+
if await self.action(binding.action, namespace):
17561757
return True
17571758
return False
17581759

@@ -1822,30 +1823,41 @@ async def action(
18221823
async def _dispatch_action(
18231824
self, namespace: object, action_name: str, params: Any
18241825
) -> bool:
1826+
"""Dispatch an action to an action method.
1827+
1828+
Args:
1829+
namespace (object): Namespace (object) of action.
1830+
action_name (str): Name of the action.
1831+
params (Any): Action parameters.
1832+
1833+
Returns:
1834+
bool: True if handled, otherwise False.
1835+
"""
1836+
_rich_traceback_guard = True
1837+
18251838
log(
18261839
"<action>",
18271840
namespace=namespace,
18281841
action_name=action_name,
18291842
params=params,
1830-
)
1831-
_rich_traceback_guard = True
1832-
1833-
public_method_name = f"action_{action_name}"
1834-
private_method_name = f"_{public_method_name}"
1835-
1836-
private_method = getattr(namespace, private_method_name, None)
1837-
public_method = getattr(namespace, public_method_name, None)
1843+
)
18381844

1839-
if private_method is None and public_method is None:
1845+
try:
1846+
private_method = getattr(namespace, f"_action_{action_name}", None)
1847+
if callable(private_method):
1848+
await invoke(private_method, *params)
1849+
return True
1850+
public_method = getattr(namespace, f"action_{action_name}", None)
1851+
if callable(public_method):
1852+
await invoke(public_method, *params)
1853+
return True
18401854
log(
1841-
f"<action> {action_name!r} has no target. Couldn't find methods {public_method_name!r} or {private_method_name!r}"
1855+
f"<action> {action_name!r} has no target."
1856+
f" Could not find methods '_action_{action_name}' or 'action_{action_name}'"
18421857
)
1843-
1844-
if callable(private_method):
1845-
return await invoke(private_method, *params)
1846-
elif callable(public_method):
1847-
return await invoke(public_method, *params)
1848-
1858+
except SkipAction:
1859+
# The action method raised this to explicitly not handle the action
1860+
log("<action> {action_name!r} skipped.")
18491861
return False
18501862

18511863
async def _broker_event(
@@ -1856,7 +1868,7 @@ async def _broker_event(
18561868
Args:
18571869
event_name (str): _description_
18581870
event (events.Event): An event object.
1859-
default_namespace (object | None): TODO: _description_
1871+
default_namespace (object | None): The default namespace, where one isn't supplied.
18601872
18611873
Returns:
18621874
bool: True if an action was processed.

src/textual/widget.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from ._segment_tools import align_lines
4545
from ._styles_cache import StylesCache
4646
from ._types import Lines
47+
from .actions import SkipAction
4748
from .await_remove import AwaitRemove
4849
from .binding import Binding
4950
from .box_model import BoxModel, get_box_model
@@ -2403,42 +2404,42 @@ def _on_hide(self, event: events.Hide) -> None:
24032404
def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None:
24042405
self.scroll_to_region(message.region, animate=True)
24052406

2406-
def action_scroll_home(self) -> bool | None:
2407+
def action_scroll_home(self) -> None:
24072408
if not self._allow_scroll:
2408-
return False
2409+
raise SkipAction()
24092410
self.scroll_home()
24102411

2411-
def action_scroll_end(self) -> bool | None:
2412+
def action_scroll_end(self) -> None:
24122413
if not self._allow_scroll:
2413-
return False
2414+
raise SkipAction()
24142415
self.scroll_end()
24152416

2416-
def action_scroll_left(self) -> bool | None:
2417+
def action_scroll_left(self) -> None:
24172418
if not self.allow_horizontal_scroll:
2418-
return False
2419+
raise SkipAction()
24192420
self.scroll_left()
24202421

2421-
def action_scroll_right(self) -> bool | None:
2422+
def action_scroll_right(self) -> None:
24222423
if not self.allow_horizontal_scroll:
2423-
return False
2424+
raise SkipAction()
24242425
self.scroll_right()
24252426

2426-
def action_scroll_up(self) -> bool | None:
2427+
def action_scroll_up(self) -> None:
24272428
if not self.allow_vertical_scroll:
2428-
return False
2429+
raise SkipAction()
24292430
self.scroll_up()
24302431

2431-
def action_scroll_down(self) -> bool | None:
2432+
def action_scroll_down(self) -> None:
24322433
if not self.allow_vertical_scroll:
2433-
return False
2434+
raise SkipAction()
24342435
self.scroll_down()
24352436

2436-
def action_page_down(self) -> bool | None:
2437+
def action_page_down(self) -> None:
24372438
if not self.allow_vertical_scroll:
2438-
return False
2439+
raise SkipAction()
24392440
self.scroll_page_down()
24402441

2441-
def action_page_up(self) -> bool | None:
2442+
def action_page_up(self) -> None:
24422443
if not self.allow_vertical_scroll:
2443-
return False
2444+
raise SkipAction()
24442445
self.scroll_page_up()

tests/test_binding_inheritance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from __future__ import annotations
1313

14+
from textual.actions import SkipAction
1415
from textual.app import App, ComposeResult
1516
from textual.binding import Binding
1617
from textual.containers import Container
@@ -626,7 +627,7 @@ class NoHandle(Widget, can_focus=True):
626627
def action_test(self, text: str) -> bool:
627628
nonlocal no_handle_invoked
628629
no_handle_invoked = True
629-
return False
630+
raise SkipAction()
630631

631632
class SkipApp(App):
632633
def compose(self) -> ComposeResult:

0 commit comments

Comments
 (0)