Added SkipAction exception

This commit is contained in:
Will McGugan
2022-12-19 10:01:06 +00:00
parent af049d4a4d
commit ee59c5882e
4 changed files with 54 additions and 36 deletions

View File

@@ -4,6 +4,10 @@ import ast
import re
class SkipAction(Exception):
"""Raise in an action to skip the action (and allow any parent bindings to run)."""
class ActionError(Exception):
pass

View File

@@ -38,7 +38,7 @@ from rich.protocol import is_renderable
from rich.segment import Segment, Segments
from rich.traceback import Traceback
from . import Logger, LogGroup, LogVerbosity, actions, events, log, messages
from . import actions, Logger, LogGroup, LogVerbosity, events, log, messages
from ._animator import DEFAULT_EASING, Animatable, Animator, EasingFunction
from ._ansi_sequences import SYNC_END, SYNC_START
from ._callback import invoke
@@ -47,6 +47,7 @@ from ._event_broker import NoHandler, extract_handler_actions
from ._filter import LineFilter, Monochrome
from ._path import _make_path_object_relative
from ._typing import Final, TypeAlias
from .actions import SkipAction
from .await_remove import AwaitRemove
from .binding import Binding, Bindings
from .css.query import NoMatches
@@ -1752,7 +1753,7 @@ class App(Generic[ReturnType], DOMNode):
):
binding = bindings.keys.get(key)
if binding is not None and binding.priority == priority:
if await self.action(binding.action, namespace) in (True, None):
if await self.action(binding.action, namespace):
return True
return False
@@ -1822,30 +1823,41 @@ class App(Generic[ReturnType], DOMNode):
async def _dispatch_action(
self, namespace: object, action_name: str, params: Any
) -> bool:
"""Dispatch an action to an action method.
Args:
namespace (object): Namespace (object) of action.
action_name (str): Name of the action.
params (Any): Action parameters.
Returns:
bool: True if handled, otherwise False.
"""
_rich_traceback_guard = True
log(
"<action>",
namespace=namespace,
action_name=action_name,
params=params,
)
_rich_traceback_guard = True
)
public_method_name = f"action_{action_name}"
private_method_name = f"_{public_method_name}"
private_method = getattr(namespace, private_method_name, None)
public_method = getattr(namespace, public_method_name, None)
if private_method is None and public_method is None:
try:
private_method = getattr(namespace, f"_action_{action_name}", None)
if callable(private_method):
await invoke(private_method, *params)
return True
public_method = getattr(namespace, f"action_{action_name}", None)
if callable(public_method):
await invoke(public_method, *params)
return True
log(
f"<action> {action_name!r} has no target. Couldn't find methods {public_method_name!r} or {private_method_name!r}"
f"<action> {action_name!r} has no target."
f" Could not find methods '_action_{action_name}' or 'action_{action_name}'"
)
if callable(private_method):
return await invoke(private_method, *params)
elif callable(public_method):
return await invoke(public_method, *params)
except SkipAction:
# The action method raised this to explicitly not handle the action
log("<action> {action_name!r} skipped.")
return False
async def _broker_event(
@@ -1856,7 +1868,7 @@ class App(Generic[ReturnType], DOMNode):
Args:
event_name (str): _description_
event (events.Event): An event object.
default_namespace (object | None): TODO: _description_
default_namespace (object | None): The default namespace, where one isn't supplied.
Returns:
bool: True if an action was processed.

View File

@@ -44,6 +44,7 @@ from ._layout import Layout
from ._segment_tools import align_lines
from ._styles_cache import StylesCache
from ._types import Lines
from .actions import SkipAction
from .await_remove import AwaitRemove
from .binding import Binding
from .box_model import BoxModel, get_box_model
@@ -2403,42 +2404,42 @@ class Widget(DOMNode):
def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None:
self.scroll_to_region(message.region, animate=True)
def action_scroll_home(self) -> bool | None:
def action_scroll_home(self) -> None:
if not self._allow_scroll:
return False
raise SkipAction()
self.scroll_home()
def action_scroll_end(self) -> bool | None:
def action_scroll_end(self) -> None:
if not self._allow_scroll:
return False
raise SkipAction()
self.scroll_end()
def action_scroll_left(self) -> bool | None:
def action_scroll_left(self) -> None:
if not self.allow_horizontal_scroll:
return False
raise SkipAction()
self.scroll_left()
def action_scroll_right(self) -> bool | None:
def action_scroll_right(self) -> None:
if not self.allow_horizontal_scroll:
return False
raise SkipAction()
self.scroll_right()
def action_scroll_up(self) -> bool | None:
def action_scroll_up(self) -> None:
if not self.allow_vertical_scroll:
return False
raise SkipAction()
self.scroll_up()
def action_scroll_down(self) -> bool | None:
def action_scroll_down(self) -> None:
if not self.allow_vertical_scroll:
return False
raise SkipAction()
self.scroll_down()
def action_page_down(self) -> bool | None:
def action_page_down(self) -> None:
if not self.allow_vertical_scroll:
return False
raise SkipAction()
self.scroll_page_down()
def action_page_up(self) -> bool | None:
def action_page_up(self) -> None:
if not self.allow_vertical_scroll:
return False
raise SkipAction()
self.scroll_page_up()

View File

@@ -11,6 +11,7 @@ background relating to this.
from __future__ import annotations
from textual.actions import SkipAction
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container
@@ -626,7 +627,7 @@ async def test_skip_action() -> None:
def action_test(self, text: str) -> bool:
nonlocal no_handle_invoked
no_handle_invoked = True
return False
raise SkipAction()
class SkipApp(App):
def compose(self) -> ComposeResult: