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 import re
class SkipAction(Exception):
"""Raise in an action to skip the action (and allow any parent bindings to run)."""
class ActionError(Exception): class ActionError(Exception):
pass pass

View File

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

View File

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

View File

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