From 5f97bbd33f72a88effed16a2c77426943712e00c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 29 Sep 2022 16:33:19 +0100 Subject: [PATCH 1/4] actions docs --- docs/examples/guide/actions/actions01.py | 17 ++++ docs/examples/guide/actions/actions02.py | 17 ++++ docs/examples/guide/actions/actions03.py | 23 +++++ docs/examples/guide/actions/actions04.py | 29 ++++++ docs/examples/guide/actions/actions05.css | 11 +++ docs/examples/guide/actions/actions05.py | 36 ++++++++ docs/guide/actions.md | 106 +++++++++++++++++++++- docs/images/actions/format.excalidraw.svg | 16 ++++ src/textual/app.py | 27 ++++-- 9 files changed, 275 insertions(+), 7 deletions(-) create mode 100644 docs/examples/guide/actions/actions01.py create mode 100644 docs/examples/guide/actions/actions02.py create mode 100644 docs/examples/guide/actions/actions03.py create mode 100644 docs/examples/guide/actions/actions04.py create mode 100644 docs/examples/guide/actions/actions05.css create mode 100644 docs/examples/guide/actions/actions05.py create mode 100644 docs/images/actions/format.excalidraw.svg diff --git a/docs/examples/guide/actions/actions01.py b/docs/examples/guide/actions/actions01.py new file mode 100644 index 000000000..03d83530e --- /dev/null +++ b/docs/examples/guide/actions/actions01.py @@ -0,0 +1,17 @@ +from textual.app import App +from textual import events + + +class ActionsApp(App): + def action_set_background(self, color: str) -> None: + self.screen.styles.background = color + self.bell() + + def on_key(self, event: events.Key) -> None: + if event.key == "r": + self.action_set_background("red") + + +if __name__ == "__main__": + app = ActionsApp() + app.run() diff --git a/docs/examples/guide/actions/actions02.py b/docs/examples/guide/actions/actions02.py new file mode 100644 index 000000000..b322b6aa8 --- /dev/null +++ b/docs/examples/guide/actions/actions02.py @@ -0,0 +1,17 @@ +from textual.app import App +from textual import events + + +class ActionsApp(App): + def action_set_background(self, color: str) -> None: + self.screen.styles.background = color + self.bell() + + async def on_key(self, event: events.Key) -> None: + if event.key == "r": + await self.action("set_background('red')") + + +if __name__ == "__main__": + app = ActionsApp() + app.run() diff --git a/docs/examples/guide/actions/actions03.py b/docs/examples/guide/actions/actions03.py new file mode 100644 index 000000000..22ccf57aa --- /dev/null +++ b/docs/examples/guide/actions/actions03.py @@ -0,0 +1,23 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """ +[b]Set your background[/b] +[@click=set_background('red')]Red[/] +[@click=set_background('green')]Green[/] +[@click=set_background('blue')]Blue[/] +""" + + +class ActionsApp(App): + def compose(self) -> ComposeResult: + yield Static(TEXT) + + def action_set_background(self, color: str) -> None: + self.screen.styles.background = color + self.bell() + + +if __name__ == "__main__": + app = ActionsApp() + app.run() diff --git a/docs/examples/guide/actions/actions04.py b/docs/examples/guide/actions/actions04.py new file mode 100644 index 000000000..6a1ac9d46 --- /dev/null +++ b/docs/examples/guide/actions/actions04.py @@ -0,0 +1,29 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """ +[b]Set your background[/b] +[@click=set_background('red')]Red[/] +[@click=set_background('green')]Green[/] +[@click=set_background('blue')]Blue[/] +""" + + +class ActionsApp(App): + BINDINGS = [ + ("r", "set_background('red')", "Red"), + ("g", "set_background('green')", "Green"), + ("b", "set_background('blue')", "Blue"), + ] + + def compose(self) -> ComposeResult: + yield Static(TEXT) + + def action_set_background(self, color: str) -> None: + self.screen.styles.background = color + self.bell() + + +if __name__ == "__main__": + app = ActionsApp() + app.run() diff --git a/docs/examples/guide/actions/actions05.css b/docs/examples/guide/actions/actions05.css new file mode 100644 index 000000000..3306d8a88 --- /dev/null +++ b/docs/examples/guide/actions/actions05.css @@ -0,0 +1,11 @@ +Screen { + layout: grid; + grid-size: 1; + grid-gutter: 2 4; + grid-rows: 1fr; +} + +ColorSwitcher { + height: 100%; + margin: 2 4; +} diff --git a/docs/examples/guide/actions/actions05.py b/docs/examples/guide/actions/actions05.py new file mode 100644 index 000000000..a73ccfd21 --- /dev/null +++ b/docs/examples/guide/actions/actions05.py @@ -0,0 +1,36 @@ +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """ +[b]Set your background[/b] +[@click=set_background('cyan')]Cyan[/] +[@click=set_background('magenta')]Magenta[/] +[@click=set_background('yellow')]Yellow[/] +""" + + +class ColorSwitcher(Static): + def action_set_background(self, color: str) -> None: + self.styles.background = color + + +class ActionsApp(App): + CSS_PATH = "actions05.css" + BINDINGS = [ + ("r", "set_background('red')", "Red"), + ("g", "set_background('green')", "Green"), + ("b", "set_background('blue')", "Blue"), + ] + + def compose(self) -> ComposeResult: + yield ColorSwitcher(TEXT) + yield ColorSwitcher(TEXT) + + def action_set_background(self, color: str) -> None: + self.screen.styles.background = color + self.bell() + + +if __name__ == "__main__": + app = ActionsApp() + app.run() diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 03e8cb174..527214d53 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -1,3 +1,107 @@ # Actions -TODO: Actions docs +Actions are white-listed functions with a string syntax you can embed in to links and bind to keys. In this chapter wee will discuss how to create actions and how to run them. + +## Action methods + +Action methods are methods on your app or widgets prefixed with `action_`. Aside from the prefix these are regular methods which you could call directly if you wished. + +Let's write an app with a simple action. + +```python title="actions01.py" hl_lines="6-8" +--8<-- "docs/examples/guide/actions/actions01.py" +``` + +The `action_set_background` method is an action which sets the background of the screen. The key handler calls this action if you press the ++r++ key, to set the background color to red. + +Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string "set_background('red')" is an action string that would call `self.action_set_background('red')`. + +The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action strings and dispatches it to the appropriate action method. + +```python title="actions02.py" hl_lines="10-12" +--8<-- "docs/examples/guide/actions/actions02.py" +``` + +Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` key. + +You will not typically need this in a real app as Textual will run actions for you from key bindings or links. Before we discuss more practical uses for action strings, let's have a look at the syntax for actions. + +## Action syntax + +Action strings have a simple syntax, which for the most part replicates Python's function call syntax. + +!!! important + + As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to execute action strings, as this would create a security risk. + +Action strings have the following format: + +- The name of an action on is own will call the action method with no parameters. For example, `"bell"` will call `action_bell()`. +- Actions may be followed by braces containing Python objects. For example, the action string `set_background('red')` will call `action_set_background("red")`. +- Actions may be prefixed with a _namespace_ (see below) follow by a dot. + +
+--8<-- "docs/images/actions/format.excalidraw.svg" +
+ +### Action parameters + +If the action strings contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbols. + +Consequently `"set_background('blue')"` is a valid action string, but `"set_background(new_color)"` is not — because `new_color` is a variable and not a literal. + +## Actions in links + +Actions may be embedded in links with console markup, which you can introduce the `@click` tag. + +The following example mounts simple static text with embedded action links. + +=== "actions03.py" + + ```python title="actions03.py" hl_lines="4-9 13-14" + --8<-- "docs/examples/guide/actions/actions03.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/actions/actions03.py"} + ``` + +When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color. + +## Actions in binding + +Textual will also run actions that are bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action. + +=== "actions04.py" + + ```python title="actions04.py" hl_lines="13-17" + --8<-- "docs/examples/guide/actions/actions04.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/guide/actions/actions04.py" press="g"} + ``` + +If you run this example, you can change the background by pressing keys in addition to clicking links. + +## Action namespaces + +Textual will look for action methods on the widget or app where they are used. If we were to create a [custom widget](./widgets.md#custom-widgets) with an action method, it can have its own set of actions. + +The following example defines a custom widget with its own `set_background` action. + +=== "actions05.py" + + ```python title="actions05.py" hl_lines="13-14" + --8<-- "docs/examples/guide/actions/actions05.py" + ``` + +=== "actions05.py" + + ```sass title="actions05.css" + --8<-- "docs/examples/guide/actions/actions05.css" + ``` + +If you click on the links, it will call the action method for the _widget_ where the click is handler. The ++r++, ++g++, and ++b++ keys are defined on the App so will set the background for the app. diff --git a/docs/images/actions/format.excalidraw.svg b/docs/images/actions/format.excalidraw.svg new file mode 100644 index 000000000..70bd62300 --- /dev/null +++ b/docs/images/actions/format.excalidraw.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nNVaa2/bxlx1MDAxMv2eX2GoXHUwMDFmelx1MDAwYkTMPmd3XHUwMDBiXHUwMDE0XHUwMDE3fjVw4jpcdTAwMGbbcZqLomDItcWIXCJZkn418H/vLCOTXHUwMDE0ZcmSXCL7uoRhS7tL7nB2zpmzs/76bGOjV15ntvfzRs9eXHUwMDA1flx1MDAxY4W5f9l77tovbF5EaYJdrPpepOd5UI1cdTAwMWOUZVb8/OLFyM+HtsxiP7DeRVSc+3FRnodR6lx1MDAwNenoRVTaUfFf9/vAXHUwMDFm2V+ydFx1MDAxNJa510zSt2FUpvm3uWxsRzYpXHUwMDBifPr/8PvGxtfqd8u63Fx1MDAwNqWfnMW2uqHqalxmXHUwMDA0KrutXHUwMDA3aVJcdTAwMTnLtdKSMM7rXHUwMDAxUbGD05U2xN5TNNk2Pa6p9+7T5eB9/29/U6Tw8cvx9vbA3/3QzHpcdTAwMWHF8WF5XHUwMDFkV1ZcdTAwMTUpvkzTV5R5OrQnUVhcdTAwMGWwl3ba67tCv1x1MDAxONjWbXl6fjZIbOFen9StaeZcdTAwMDdRee1cdTAwMWVEmtZvPmiPu3IrRISnmZCSgeGGNS/rbmfGXHUwMDAzpZUxWitOjaSiY9h2XHUwMDFh40KgYT+Q6mos++xcdTAwMDfDMzQvXHSbMae+ZDhcdTAwMGY0oy7HL1xm0jOUSUOM4GBcdTAwMTiwesTARmeDXHUwMDEyh1xi4UluXGKwZsFcblsthWGUK2HA1Fx1MDAxZG7ibC+sguKPri9cdTAwMDd+no1d1qtcZmxcdTAwMTntvu52I6pcdTAwMWRVrdVO7Jvdk/2rsDw42d3f2e9fj+SRqZ81XHUwMDExgn6ep5e9uudm/Kkx7TxcdTAwMGL9b3FFXHUwMDAxhMCoRCeoZjHiKFx1MDAxOWJnclx1MDAxZcdNW1x1MDAxYVxmm1CsWm+er1x1MDAwMFx1MDAwMUPoLFxiUKGUkcCVXFxcdTAwMThcdTAwMDPHcue9v/82f9+/fnM5XHUwMDFjfKJhXGafnjpcdTAwMDZcdTAwMTR4lFxigj9cXIEhXHUwMDA0JlGgPSlBXHUwMDAxxdhcdTAwMTNCtJyxXG5cYij7rDXcXHUwMDA1XHUwMDAyRqUnKaFcdTAwMDTQ50aKVqQvgFx1MDAwMspcdK6WlvSRYXAwpMPPXHUwMDE3Ly/ebX+6OmC/lW8p7P25Nlx1MDAxOFx1MDAwMFFCP1xuXGbQ8TNxYIjmgkiyOFxmxMdNyj9cdTAwMGVfb8U7XHUwMDA3aXiYWf0qOX7iMFx1MDAxMEZcIlxmjEFcdTAwMThcdTAwMDBnTIOahFx1MDAwMXjGYIagmlxuzTl8XHUwMDFmXGZEXHUwMDAw9lTeXHUwMDA1XHUwMDAzzFx1MDAwM57A1ZCwJFx1MDAwMJCoXHUwMDA0MNpapUdcdTAwMDFAn21cclx1MDAwZlxyifLDS0h1cUTgaP/lulx1MDAwMFx1MDAwMOhrotZcdTAwMDWA0l6Vd8Y+XHUwMDEzM2NcdTAwMWa0ozzFXHUwMDE3j/3w9emm3Nl59TtcdTAwMWOL4faxlvFfx+laY79zVzv06Uqhj972XHUwMDAwJGpcdTAwMTCmuVRsMlx1MDAwMVAtPS6EXHUwMDExVFx1MDAxMc6IXHUwMDAxeKjQN3Q64qcjXWNUMJRldPlAL9yXpyh4kHFcdTAwMDQsw/RNPKVJeVx1MDAxOP1tKzE70fqrP4ri64mgqFx1MDAxMIBcdTAwMDa+yUqMcT/eSHCLUWCk2PaaXHUwMDE1XHUwMDE2569cdTAwMDSQnrhzM47OXHUwMDFjXnqxPZ1cdTAwMDRSXHUwMDE54e6k7i7Tlo9cdTAwMDO0xMfH5Xth943SPDqL0Iqj2VathGejWbe1lnSYT4nQokUq9+HZJDH1X+d7OiXvroqj/ZBe7bx82njmXHUwMDAyN1x1MDAxNJjINEdEc6NkXHUwMDA30IJ5RjGD1OpcdTAwMTKNbC3zmlx1MDAwMU1cdTAwMWI3z1x1MDAwMTRHWkF24c3gR1x1MDAwMfSDSjdcdTAwMDAnZFx1MDAxZlxy0JuBg05cdTAwMDWcpZBcdTAwMWOgo2z+XHUwMDAwWG5cdTAwMWK0XHUwMDEyiLXU3dZbXHUwMDEwo85XRGmzeE4mfn+U/nUlePllfy+/lEM4/rL9tDEswHhcXDtRp1x0vqxmTfHhNilcdTAwMDMxXFxcdTAwMTJcdTAwMTdqilx01rFsXVx1MDAxOGZkoaRsqGRSK/rISflh1ScqXCK+jPpcXFNSzvxcdTAwMWNhg7gsnlx1MDAwNpbvMmwupr+5+k6lXHK821xcp2ZXY3A+b1jzPljPl2RLwLpcdTAwMGKeVWGt7q21cNTSrrAqXGKqXHUwMDE0qVkzc1x1MDAxNVx1MDAxNIp7KFEkQ19cYsXYXHUwMDFjqVx1MDAxZFx1MDAxYSSG05VR7aGeXHUwMDA3yiVcdTAwMDXBmeLNPDXGtfFcdTAwMThqXGLEXHUwMDAyXHUwMDEwXCJpq1x1MDAxYzeGPFPMbYXVXG5p+75ccueDKOWi9PNyK0rCKDnDzoZNbqvoe1x1MDAwYuzfKlx1MDAxOFx1MDAwN+fOyj7xmCtlgVwigGqKcPRVa9iZn1VcdTAwMTGPXHUwMDBiil5cdTAwMTSGa6HVuPumNsom4f0mza+sT5pEmOFcdTAwMTR/JCXSlaz5lElcdTAwMDb3esCEUrimOIBO2Vx1MDAxNPtFuZ2ORlGJnn+bRknZ9XDlyk2H8oH1p/hcdTAwMDPfqd3XpYPMPXGS2ZtPXHUwMDFiXHJgqi/15z+e3zl6ZiC7qz9cdTAwMTXDzeOetf8uT2RgZpZcZjRTuIcmbPGTk/lK9CnyXHUwMDE41+5sRHJcdTAwMTBcdTAwMDTRiJ7vqFx1MDAxM849TqlEUIDGQCSzT05kYFx1MDAwNFx0V95heIai0Ka4oVx1MDAwM8aYaJV/m5qZ8ZQwmlKGklGA1FPiRVHArVx1MDAxMsbQ4zLZyluEXHUwMDA1mWz+zrXDZID+XHUwMDE1mHU4Ulx1MDAwNlKabFxya2iDckdzXGZcdTAwMTeUitWobP5cdTAwMDFJh8pcdTAwMTBJTFxu3Fx1MDAxZbpiKGt29Vxyu1x1MDAxMlx1MDAwZlx1MDAxN1x1MDAxNFfVSFxmN66I+VeT2exodld/OpDXRWeMXHUwMDBi022udZlGO0RbuN1HZ/NF+f+BzvR9dCZBeZjCXHUwMDA1V5jRdbf+qbTHXHUwMDE0pkyNvmfMmNnlXHUwMDEy5MPTQK1KZoCcqVHWUaJcZpuoOddcXGaER5lSKLtcZlx1MDAwZWmpw/EhgCFcdTAwMDaIkuJcdTAwMDFcdTAwMGVcdTAwMDFcdTAwMWVko7Qgk83fv3dYg2hcdTAwMTQ1RlwiRtCNmH5aIFx1MDAxYdOG8jh36lx1MDAxNUNcdTAwMWIjW4hbWbAkl80/5epYRVx1MDAxZEVp7bbzQipcdNO6XGalXCJmXHUwMDA0qVx1MDAwMdU/Ulx1MDAwMGf/ai7rz4zmqrdcdTAwMTPIS1x1MDAxMtmsolx1MDAxMa7qTFx1MDAxYeNOXHUwMDE242SLby9ccuzCLlx1MDAwZt5cdTAwMWR9+Lj14eTVSbydZ2RcdTAwMDaPXHL8YHCe21lMtq66kbl3g0m5J7RmyFTCuGuCyrjbjVx1MDAxONDfd4hf5n5SZH6OQJgmMVSG07TFW0w+ZirBmFx1MDAxNu6sf3mmerKnONJR73ef4lx1MDAwMC4gZlwiScaXmlx1MDAxOFVcdTAwMTeQmnx8W0Dys8wrbPlns2T/+TG34Y8/3VlGamW5xzjcmW3cs1vvVp51XHUwMDAzXHUwMDBmS/RrTbxcdTAwMThcdTAwMThROHZOM0XvXCKyl1t3/ZtVdbmnVuzhYGpdWHy9eXbzXHUwMDBmaTC2YiJ9 + + + + Optional namespaceAction nameOptional parametersapp.set_background('red') \ No newline at end of file diff --git a/src/textual/app.py b/src/textual/app.py index fbc69b880..a8bc00a39 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1366,30 +1366,43 @@ class App(Generic[ReturnType], DOMNode): await super().on_event(event) async def action( - self, action: str, default_namespace: object | None = None - ) -> None: + self, + action: str | tuple[str, tuple[str, ...]], + default_namespace: object | None = None, + ) -> bool: """Perform an action. Args: action (str): Action encoded in a string. default_namespace (object | None): Namespace to use if not provided in the action, or None to use app. Defaults to None. + + Returns: + bool: True if the event has handled. """ - target, params = actions.parse(action) + if isinstance(action, str): + target, params = actions.parse(action) + else: + target, params = action + implicit_destination = True if "." in target: destination, action_name = target.split(".", 1) if destination not in self._action_targets: raise ActionError("Action namespace {destination} is not known") action_target = getattr(self, destination) + implicit_destination = True else: action_target = default_namespace or self action_name = target - await self._dispatch_action(action_target, action_name, params) + handled = await self._dispatch_action(action_target, action_name, params) + if not handled and implicit_destination and not isinstance(action_target, App): + handled = await self.app._dispatch_action(self.app, action_name, params) + return handled async def _dispatch_action( self, namespace: object, action_name: str, params: Any - ) -> None: + ) -> bool: log( "", namespace=namespace, @@ -1403,6 +1416,8 @@ class App(Generic[ReturnType], DOMNode): log(f" {action_name!r} has no target") if callable(method): await invoke(method, *params) + return True + return False async def _broker_event( self, event_name: str, event: events.Event, default_namespace: object | None @@ -1427,7 +1442,7 @@ class App(Generic[ReturnType], DOMNode): return False else: event.stop() - if isinstance(action, str): + if isinstance(action, (str, tuple)): await self.action(action, default_namespace=default_namespace) elif callable(action): await action() From 7bfe178c691bb193a84463dc2db5a0793075b71b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 29 Sep 2022 17:19:07 +0100 Subject: [PATCH 2/4] actions docs --- docs/guide/actions.md | 70 ++++++++++++++++++----- docs/images/actions/format.excalidraw.svg | 6 +- src/textual/app.py | 15 ++++- 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 527214d53..3a0f3138a 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -1,22 +1,26 @@ # Actions -Actions are white-listed functions with a string syntax you can embed in to links and bind to keys. In this chapter wee will discuss how to create actions and how to run them. +Actions are allow-listed functions with a string syntax you can embed in links and bind to keys. In this chapter we will discuss how to create actions and how to run them. ## Action methods Action methods are methods on your app or widgets prefixed with `action_`. Aside from the prefix these are regular methods which you could call directly if you wished. +!!! information + + Action methods may be coroutines (prefixed with `async`). + Let's write an app with a simple action. ```python title="actions01.py" hl_lines="6-8" --8<-- "docs/examples/guide/actions/actions01.py" ``` -The `action_set_background` method is an action which sets the background of the screen. The key handler calls this action if you press the ++r++ key, to set the background color to red. +The `action_set_background` method is an action which sets the background of the screen. The key handler calls this action if you press the ++r++ key to set the background color to red. -Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string "set_background('red')" is an action string that would call `self.action_set_background('red')`. +Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string `"set_background('red')"` is an action string that would call `self.action_set_background('red')`. -The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action strings and dispatches it to the appropriate action method. +The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action string and dispatches it to the appropriate method. ```python title="actions02.py" hl_lines="10-12" --8<-- "docs/examples/guide/actions/actions02.py" @@ -24,33 +28,33 @@ The following example replaces the immediate call with a call to [action()][text Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` key. -You will not typically need this in a real app as Textual will run actions for you from key bindings or links. Before we discuss more practical uses for action strings, let's have a look at the syntax for actions. +You will not typically need this in a real app as Textual will run actions in links or key bindings. Before we discuss these, let's have a look at the syntax for action strings. -## Action syntax +## Syntax Action strings have a simple syntax, which for the most part replicates Python's function call syntax. !!! important - As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to execute action strings, as this would create a security risk. + As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings. Action strings have the following format: -- The name of an action on is own will call the action method with no parameters. For example, `"bell"` will call `action_bell()`. -- Actions may be followed by braces containing Python objects. For example, the action string `set_background('red')` will call `action_set_background("red")`. +- The name of an action on is own will call the action method with no parameters. For example, an action string of `"bell"` will call `action_bell()`. +- Actions may be followed by braces containing Python objects. For example, the action string `set_background("red")` will call `action_set_background("red")`. - Actions may be prefixed with a _namespace_ (see below) follow by a dot.
--8<-- "docs/images/actions/format.excalidraw.svg"
-### Action parameters +### Parameters If the action strings contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbols. Consequently `"set_background('blue')"` is a valid action string, but `"set_background(new_color)"` is not — because `new_color` is a variable and not a literal. -## Actions in links +## Links Actions may be embedded in links with console markup, which you can introduce the `@click` tag. @@ -69,7 +73,7 @@ The following example mounts simple static text with embedded action links. When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color. -## Actions in binding +## Bindings Textual will also run actions that are bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action. @@ -86,9 +90,9 @@ Textual will also run actions that are bound to keys. The following example adds If you run this example, you can change the background by pressing keys in addition to clicking links. -## Action namespaces +## Namespaces -Textual will look for action methods on the widget or app where they are used. If we were to create a [custom widget](./widgets.md#custom-widgets) with an action method, it can have its own set of actions. +Textual will look for action methods on the widget or app where they are used. If we were to create a [custom widget](./widgets.md#custom-widgets) it can have its own set of actions. The following example defines a custom widget with its own `set_background` action. @@ -104,4 +108,40 @@ The following example defines a custom widget with its own `set_background` acti --8<-- "docs/examples/guide/actions/actions05.css" ``` -If you click on the links, it will call the action method for the _widget_ where the click is handler. The ++r++, ++g++, and ++b++ keys are defined on the App so will set the background for the app. +There are two instances of the custom widget mounted. If you click the links in either of them it will changed the background for that widget only. The ++r++, ++g++, and ++b++ key bindings are set on the App so will set the background for the screen. + +You can optionally prefix an action with a _namespace_, which tells Textual to run actions for a different object. + +Textual supports the following action namespaces: + +- `app` invokes actions on the App. +- `screen` invokes actions on the screen. + +In the previous example if you wanted a link to set the background on the app rather than the widget, we could set a link to `app.set_background('red')`. + + +## Builtin actions + +Textual supports the following builtin actions which are defined on the app. + + +### Bell + +::: textual.app.App.action_bell + options: + show_root_heading: false + +### Screenshot + +::: textual.app.App.action_screenshot + +### Toggle_dark + +::: textual.app.App.action_toggle_dark + +### Quit + +::: textual.app.App.action_quit + + +*TODO:* document more actions diff --git a/docs/images/actions/format.excalidraw.svg b/docs/images/actions/format.excalidraw.svg index 70bd62300..b74fbad3f 100644 --- a/docs/images/actions/format.excalidraw.svg +++ b/docs/images/actions/format.excalidraw.svg @@ -1,6 +1,6 @@ - + - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nNVaa2/bxlx1MDAxMv2eX2GoXHUwMDFmelx1MDAwYkTMPmd3XHUwMDBiXHUwMDE0XHUwMDE3fjVw4jpcdTAwMGbbcZqLomDItcWIXCJZkn418H/vLCOTXHUwMDE0ZcmSXCL7uoRhS7tL7nB2zpmzs/76bGOjV15ntvfzRs9eXHUwMDA1flx1MDAxY4W5f9l77tovbF5EaYJdrPpepOd5UI1cdTAwMWOUZVb8/OLFyM+HtsxiP7DeRVSc+3FRnodR6lx1MDAwNenoRVTaUfFf9/vAXHUwMDFm2V+ydFx1MDAxNJa510zSt2FUpvm3uWxsRzYpXHUwMDBifPr/8PvGxtfqd8u63Fx1MDAwNqWfnMW2uqHqalxmXHUwMDA0KrutXHUwMDA3aVJcdTAwMTnLtdKSMM7rXHUwMDAxUbGD05U2xN5TNNk2Pa6p9+7T5eB9/29/U6Tw8cvx9vbA3/3QzHpcdTAwMWHF8WF5XHUwMDFkV1ZcdTAwMTUpvkzTV5R5OrQnUVhcdTAwMGWwl3ba67tCv1x1MDAxONjWbXl6fjZIbOFen9StaeZcdTAwMDdRee1cdTAwMWVEmtZvPmiPu3IrRISnmZCSgeGGNS/rbmfGXHUwMDAzpZUxWitOjaSiY9h2XHUwMDFh40KgYT+Q6mos++xcdTAwMDfDMzQvXHSbMae+ZDhcdTAwMGY0oy7HL1xm0jOUSUOM4GBcdTAwMTiwesTARmeDXHUwMDEyh1xi4UluXGKwZsFcblsthWGUK2HA1Fx1MDAxZG7ibC+sguKPri9cdTAwMDd+no1d1qtcZmxcdTAwMTntvu52I6pcdTAwMWRVrdVO7Jvdk/2rsDw42d3f2e9fj+SRqZ81XHUwMDExgn6ep5e9uudm/Kkx7TxcdTAwMGL9b3FFXHUwMDAxhMCoRCeoZjHiKFx1MDAxOWJnclx1MDAxZcdNW1x1MDAxYVxmm1CsWm+er1x1MDAwMFx1MDAwMUPoLFxiUKGUkcCVXFxcdTAwMThcdTAwMDPHcue9v/82f9+/fnM5XHUwMDFjfKJhXGafnjpcdTAwMDZcdTAwMTR4lFxigj9cXIEhXHUwMDA0JlGgPSlBXHUwMDAxxdhcdTAwMTNCtJyxXG5cYij7rDXcXHUwMDA1XHUwMDAyRqUnKaFcdTAwMDTQ50aKVqQvgFx1MDAwMspcdK6WlvSRYXAwpMPPXHUwMDE3Ly/ebX+6OmC/lW8p7P25Nlx1MDAxOFx1MDAwMFFCP1xuXGbQ8TNxYIjmgkiyOFxmxMdNyj9cdTAwMGVfb8U7XHUwMDA3aXiYWf0qOX7iMFx1MDAxMEZcIlxmjEFcdTAwMThcdTAwMDBnTIOahFx1MDAwMXjGYIagmlxuzTl8XHUwMDFmXGZEXHUwMDAw9lTeXHUwMDA1XHUwMDAzzFx1MDAwM57A1ZCwJFx1MDAwMJCoXHUwMDA0MNpapUdcdTAwMDFAn21cclx1MDAwZlxyifLDS0h1cUTgaP/lulx1MDAwMFx1MDAwMOhrotZcdTAwMDWA0l6Vd8Y+XHUwMDEzM2NcdTAwMWa0ozzFXHUwMDE3j/3w9emm3Nl59TtcdTAwMWOL4faxlvFfx+laY79zVzv06Uqhj972XHUwMDAwJGpcdTAwMTCmuVRsMlx1MDAwMVAtPS6EXHUwMDExVFx1MDAxMc6IXHUwMDAxeKjQN3Q64qcjXWNUMJRldPlAL9yXpyh4kHFcdTAwMDQsw/RNPKVJeVx1MDAxOP1tKzE70fqrP4ri64mgqFx1MDAxMIBcdTAwMDa+yUqMcT/eSHCLUWCk2PaaXHUwMDE1XHUwMDE2569cdTAwMDSQnrhzM47OXHUwMDFjXnqxPZ1cdTAwMDRSXHUwMDE54e6k7i7Tlo9cdTAwMDO0xMfH5Xth943SPDqL0Iqj2VathGejWbe1lnSYT4nQokUq9+HZJDH1X+d7OiXvroqj/ZBe7bx82njmXHUwMDAyN1x1MDAxNJjINEdEc6NkXHUwMDA30IJ5RjGD1OpcdTAwMTKNbC3zmlx1MDAwMU1cdTAwMWI3z1x1MDAwMTRHWkF24c3gR1x1MDAwMfSDSjdcdTAwMDAnZFx1MDAxZlxy0JuBg05cdTAwMDWcpZBcdTAwMWOgo2z+XHUwMDAwWG5cdTAwMWK0XHUwMDEyiLXU3dZbXHUwMDEwo85XRGmzeE4mfn+U/nUlePllfy+/lEM4/rL9tDEswHhcXDtRp1x0vqxmTfHhNilcdTAwMDMxXFxcdTAwMTJcdTAwMTdqilx01rFsXVx1MDAxOGZkoaRsqGRSK/rISflh1ScqXCK+jPpcXFNSzvxcdTAwMWNhg7gsnlx1MDAwNpbvMmwupr+5+k6lXHK821xcp2ZXY3A+b1jzPljPl2RLwLpcdTAwMGKeVWGt7q21cNTSrrAqXGKqXHUwMDE0qVkzc1x1MDAxNVx1MDAxNIp7KFEkQ19cYsXYXHUwMDFjqVx1MDAxZFx1MDAxYSSG05VR7aGeXHUwMDA3yiVcdTAwMDXBmeLNPDXGtfFcdTAwMThqXGLEXHUwMDAyXHUwMDEwXCJpq1x1MDAxYzeGPFPMbYXVXG5p+75ccueDKOWi9PNyK0rCKDnDzoZNbqvoe1x1MDAwYuzfKlx1MDAxOFx1MDAwN+fOyj7xmCtlgVwigGqKcPRVa9iZn1VcdTAwMTGPXHUwMDBiil5cdTAwMTSGa6HVuPumNsom4f0mza+sT5pEmOFcdTAwMTR/JCXSlaz5lElcdTAwMDb3esCEUrimOIBO2Vx1MDAxNPtFuZ2ORlGJnn+bRknZ9XDlyk2H8oH1p/hcdTAwMDPfqd3XpYPMPXGS2ZtPXHUwMDFiXHJgqi/15z+e3zl6ZiC7qz9cdTAwMTXDzeOetf8uT2RgZpZcZjRTuIcmbPGTk/lK9CnyXHUwMDE41+5sRHJcdTAwMTBcdTAwMDTRiJ7vqFx1MDAxM849TqlEUIDGQCSzT05kYFx1MDAwNFx0V95heIai0Ka4oVx1MDAwM8aYaJV/m5qZ8ZQwmlKGklGA1FPiRVHArVx1MDAxMsbQ4zLZyluEXHUwMDA1mWz+zrXDZID+XHUwMDE1mHU4Ulx1MDAwNlKabFxya2iDckdzXGZcdTAwMTeUitWobP5cdTAwMDFJh8pcdTAwMTBJTFxu3Fx1MDAxZbpiKGt29Vxyu1x1MDAxMlx1MDAwZlx1MDAxN1x1MDAxNFfVSFxmN66I+VeT2exodld/OpDXRWeMXHUwMDBi022udZlGO0RbuN1HZ/NF+f+BzvR9dCZBeZjCXHUwMDA1V5jRdbf+qbTHXHUwMDE0pkyNvmfMmNnlXHUwMDEy5MPTQK1KZoCcqVHWUaJcZpuoOddcXGaER5lSKLtcZlx1MDAwZWmpw/EhgCFcdTAwMDaIkuJcdTAwMDFcdTAwMGVcdTAwMDFcdTAwMWVko7Qgk83fv3dYg2hcdTAwMTQ1RlwiRtCNmH5aIFx1MDAxYdOG8jh36lx1MDAxNUNcdTAwMWIjW4hbWbAkl80/5epYRVx1MDAxZEVp7bbzQipcdNO6XGalXCJmXHUwMDA0qVx1MDAwMdU/Ulx1MDAwMGf/ai7rz4zmqrdcdTAwMTPIS1x1MDAxMtmsolx1MDAxMa7qTFx1MDAxYeNOXHUwMDE242SLby9ccuzCLlx1MDAwZt5cdTAwMWR9+Lj14eTVSbydZ2RcdTAwMDaPXHL8YHCe21lMtq66kbl3g0m5J7RmyFTCuGuCyrjbjVx1MDAxONDfd4hf5n5SZH6OQJgmMVSG07TFW0w+ZirBmFx1MDAxNu6sf3mmerKnONJR73ef4lx1MDAwMC4gZlwiScaXmlx1MDAxOFVcdTAwMTeQmnx8W0Dys8wrbPlns2T/+TG34Y8/3VlGamW5xzjcmW3cs1vvVp51XHUwMDAzXHUwMDBmS/RrTbxcdTAwMThcdTAwMThROHZOM0XvXCKyl1t3/ZtVdbmnVuzhYGpdWHy9eXbzXHUwMDBmaTC2YiJ9 + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nNVaW2/bNlx1MDAxNH7vr1xivIduQK3y8M5cdTAwMDLDkNuKtFl6SdJ0XHUwMDFkhkGV6Fi1LGmScluR/75DJbFkxXZsx8kyIXAsklx1MDAxMlx1MDAwZlx1MDAwZs/3nVx1MDAwYv392dpap7zIbOfVWseeXHUwMDA3flx1MDAxY4W5f9Z54dpPbV5EaYJdtLov0pM8qEb2yzIrXr18OfTzgS2z2Fx1MDAwZqx3XHUwMDFhXHUwMDE1J35cXJQnYZR6QTp8XHUwMDE5lXZY/OI+9/yh/TlLh2GZe/UkXVx1MDAxYkZlml/NZWM7tElZ4Nv/wPu1te/VZ0O63Fx1MDAwNqWfXHUwMDFjx7Z6oOqqXHUwMDA1VES0W/fSpFx1MDAxMlx1MDAxNoQm1ChQajRcIiq2cL7ShtjdQ5lt3eOaOlx1MDAxZr6c9T92//HXeSo/fzvc3Oz725/qaXtRXHUwMDFj75dcdTAwMTdxJVaR4mrqvqLM04E9isKy7+ZutY+eXG79om9cdTAwMWKP5enJcT+xhVs/XHUwMDE5taaZXHUwMDFmROWFe1x1MDAxMalbr5TQXHUwMDFjd+62iHBPUy5cdTAwMDSVhlx1MDAxOcpGne5xajyptDJGa8XAXGLgLcE201x1MDAxOHdcdTAwMDJcdTAwMDX7gVRXLdlXP1x1MDAxOFx1MDAxY6N4SViP6fmC4jyyXHUwMDFldXa9YGk80CC4oVxcSqJlPU/fRsf9XHUwMDEyh3DuXHRmiKT1jlx1MDAxNbbaXG5DgSlupFx1MDAxOXW4ibOdsLKKP9u67Pt5dq2yTiVgQ2h3u902qaZZNXY7se+2j3bPw3LvaHt3a7d7MVx1MDAxNFx1MDAwN2b0rjFcdTAwMWL08zw964x6Lq+/1aKdZKF/ZVcgJeeodC6YqC0vjpJcdTAwMDF2JidxXFy3pcGgNsWq9fLFXHUwMDEyXHUwMDE4XHUwMDAwwshUXHUwMDEwXHUwMDE4YJQoQ+dcdTAwMDfBodj66O++zz92L96dXHL6XyCM5ZenXHUwMDBlXHUwMDAypT3FXHRcdTAwMTdcdTAwMWGhQCVt2FhcdTAwMDVcdTAwMDPtYVx1MDAxYpVMXHUwMDEypcBokPeCXHUwMDAx0K9ay0kwoFxiOM6N4kpcYqNcdTAwMTUsglx1MDAwMmCEK6VcdTAwMDU8Mlxm9lx1MDAwNjD4evr69MPml/M9+lv5XHUwMDFl5M5fK4OBxMU+XHUwMDEyXGaAT4dcdTAwMDE3XHUwMDA0jVx1MDAwMlx1MDAwMOaGXHUwMDAx/7xcdTAwMGXs8+DtRry1l4b7mdVvksMnXHUwMDBlXHUwMDAzXFygp5HrNdHGLVaNo0ChM+CK4ntcdTAwMTRChZt7gYBcdTAwMDfS9sQkXHUwMDEwXHUwMDAwUE9cdTAwMDNjaOSaKYq+aSFcdTAwMThcYsm4RCHF48KgSzdcdTAwMDb7hkT5/plMdXFA5MHu69XBQKNXXFxcdTAwMTVcZkp7Xk5EXHUwMDAwct9UXHUwMDA0XHUwMDEwyVx1MDAxOFx1MDAxN1x1MDAwNOZ3XHUwMDA04dveutjaevO7POSDzUMt4r9cdTAwMGbTlVwioPVUXHUwMDEzXHUwMDAwsFx1MDAxNFx1MDAwMFDdnpTCXHUwMDAwRdNcdTAwMTOKyjFcdTAwMDCAXHUwMDE2XHUwMDFlQ3rmoFxiekUj7+dcdTAwMDZmIMBMYP7bpq4lcIrRXHUwMDE5LG7phbt5knFcdTAwMGYzyixC+LU9pUm5XHUwMDFm/WOrmHas9Vd/XHUwMDE4xVx1MDAxN2NGUUFcdTAwMDBcdTAwMDV8l5Vo5H68lmCqUaCl2OaeXHUwMDE1XHUwMDE256+sX489uVx1MDAxZUfHXHUwMDBlMJ3Y9saRVEaYpYy6y7Sh41x1MDAwMCXx8XX5TtheUZpHx1x1MDAxMUpxMF2qpVx1MDAwMI1x+3Q8XHUwMDBizkBqMT+eTVx1MDAxMoP/Nt/RKflwXlx1MDAxY+yGcL71+mnjmVx04UmmXGIlwNBXqPHsXHUwMDA2uPKAMkFcZsewj90zqpvl0Golz4AzQ1JBbmH14EeB88PGb4rrhlN5aDivXHUwMDA3XHUwMDBlOFx1MDAxNWxcdTAwMTbCcYCKsvlcdTAwMDMguSnQUlx1MDAxMNbCtFtHXHUwMDEwXHUwMDA2iVlcdTAwMDJIPn9QSvzuMP37nLPy2+5OfiZcdTAwMDby8Nvm04Ywl8Zj2lx1MDAwNXVcdTAwMWHjTpef3fLJklx1MDAxOFx1MDAwNDFaXHUwMDFjqGZcdTAwMDSzWlx1MDAxMCOFzFx1MDAwM2JcdTAwMDOCXG7M3Fx1MDAxZdknP2z0+Vx1MDAxZvnkzM9cdTAwMTE3XGLM4mmAeZJgM0F9pepJkTafXHUwMDFlaWP2olx1MDAxNFVcctzfherZXHUwMDAx2Vx1MDAwMqhuY2dZVKs76y1cZlFcdTAwMGKMYqLJpFa6sZWVTSjmgeKCSiUx4aQzXHUwMDAy7Vx1MDAxMH036S1cdTAwMGJq5lx0TlxmwYlcYlx1MDAwN8x4xVx1MDAwNEetjUdccnZLKVx0XHUwMDEx0IghriFPMSXmmKku4bfvSjhXWSBsyOHn5UaUhFFyjJ01m9xcdTAwMTTTd+ZI39wq/axKXHUwMDFhcatQPdwwzbVq9PfS4MStoks86spdUlx1MDAxMalQlVxmdXk96nIklE3Cu0WaXV9cdTAwMWaJZDCHk5QrhZsloHaP4zJcdTAwMTFqXHUwMDE44J/AUNhVttktmWK/KDfT4TAqUfPv0ygp21x1MDAxYa5Uue5Q3rf+Lf7ANTX72nSQuTeOM3v9ba1GTHUz+v7ni4mjp1uyu7q3jLh+37Pm/8WZzGjWbr5hMlxumJ1yVPP8OcbsYPQpUlx1MDAxOTPGXHUwMDAzV1x1MDAxYmGaUIG23YpPmPKEQtunklx1MDAxM4lmJlqC1TQlXHUwMDAyw0m4dHziUUE4ZnWSMCMwXHUwMDAwmVA1XHUwMDEzzNNcdTAwMThHoaTKXGKuTENJV1SmMCNkgKB5XFwqWzpJmJPKZmeuXHLeQO05h8RBacxcdTAwMTh5Y0STzKRcdTAwMTGoYoJcdTAwMDRCXHUwMDE1Q+e0XHUwMDFjmc0+J6n5lXhCXHUwMDEzQVx1MDAxY98z9DL1vrboXGZcdTAwMDdhmFx1MDAwNoxJVCdy3/+azqZbs7u6t1xmeVV0hvQp280jOmNcZlWMucjcbDY7Kv9cdTAwMGbYTN95XHUwMDAy4Fx1MDAwZWJcdEZeLrdslz+V9ihSXHUwMDE51UZcdTAwMDOlzeJSm8qY5L1ALUtlxFx1MDAwM4NuTDrPjJmf1mzCcbBhXHUwMDFlikmFoe40gjeLN9dcdTAwMDdcdTAwMDGG4FuU4Fx1MDAwZnBcdTAwMTCwylL9omQ2O4dcdTAwMWbxhvJcdTAwMThzkSlobjjlnDVGNGmDaIyQjNCIMkOlXHUwMDA2s1x1MDAxY5vNPu5qRovS7arE0Fx1MDAxZThhdIpUKFx1MDAxMVx1MDAwM7fxXHUwMDFhjVFh9v+/ZrNcdTAwMTlcdTAwMDbtru4tW16QzqZcdTAwMTWP2PRcdTAwMTNNzShcdTAwMTdEwvxkZuS23GbBh4NPnzc+XHUwMDFkvTmKN/OMTCGzvlx1MDAxZvRPcjuNzlZVPTJ35pmAcbGghGFcdTAwMTiKITEh4+f6XGY8XHUwMDA21J1rOVx1MDAwZqu0udfPW8rcT4rMz1x1MDAxMVx1MDAxM7c5jcOE8lGD125Ii1OqMVA0S5DW0z3TXHUwMDExtFlcXF+yfqTHWkf1ozr7uKlcdTAwMWb5WeZcdTAwMTW2/Kveolx1MDAxZp/nNnz+08QqUuOXLY9xtDNduGc32qw06Vx1MDAwNu6XqMdcdTAwMTHnoiFE4bUy6ik6p5E925j0W6vqcm+tWMPh0zoz+H757PJfPFx1MDAxMrdyIn0= - Optional namespaceAction nameOptional parametersapp.set_background('red') \ No newline at end of file + Optional namespaceAction nameOptional parametersapp.set_background('red') \ No newline at end of file diff --git a/src/textual/app.py b/src/textual/app.py index a8bc00a39..9e81c94c4 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -539,8 +539,12 @@ class App(Generic[ReturnType], DOMNode): self.dark = not self.dark def action_screenshot(self, filename: str | None, path: str = "~/") -> None: - """Action to save a screenshot.""" - self.bell() + """Save an SVG "screenshot". This action will save a SVG file containing the current contents of the screen. + + Args: + filename (str | None, optional): Filename of screenshot, or None to auto-generate. Defaults to None. + path (str, optional): Path to directory. Defaults to "~/". + """ self.save_screenshot(filename, path) def export_screenshot(self, *, title: str | None = None) -> str: @@ -1490,15 +1494,22 @@ class App(Generic[ReturnType], DOMNode): await self.press(key) async def action_quit(self) -> None: + """Quit the app as soon as possible.""" await self.shutdown() async def action_bang(self) -> None: 1 / 0 async def action_bell(self) -> None: + """Play the terminal 'bell'.""" self.bell() async def action_focus(self, widget_id: str) -> None: + """Focus the given widget. + + Args: + widget_id (str): _description_ + """ try: node = self.query(f"#{widget_id}").first() except NoMatchingNodesError: From dfe4fa47d0a8345089223b595479ace5687ae7e2 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 29 Sep 2022 17:25:01 +0100 Subject: [PATCH 3/4] docstring --- src/textual/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/app.py b/src/textual/app.py index 9e81c94c4..188a49791 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1508,7 +1508,7 @@ class App(Generic[ReturnType], DOMNode): """Focus the given widget. Args: - widget_id (str): _description_ + widget_id (str): ID of widget to focus. """ try: node = self.query(f"#{widget_id}").first() From cb9d777bbca922df4cc779de3fc38e99553dba83 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 29 Sep 2022 17:31:13 +0100 Subject: [PATCH 4/4] words --- docs/guide/actions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 3a0f3138a..1d6fec643 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -8,7 +8,7 @@ Action methods are methods on your app or widgets prefixed with `action_`. Aside !!! information - Action methods may be coroutines (prefixed with `async`). + Action methods may be coroutines (methods with the `async` keyword). Let's write an app with a simple action. @@ -16,9 +16,9 @@ Let's write an app with a simple action. --8<-- "docs/examples/guide/actions/actions01.py" ``` -The `action_set_background` method is an action which sets the background of the screen. The key handler calls this action if you press the ++r++ key to set the background color to red. +The `action_set_background` method is an action which sets the background of the screen. The key handler above will call this action if you press the ++r++ key. -Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string `"set_background('red')"` is an action string that would call `self.action_set_background('red')`. +Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string `"set_background('red')"` is an action string which would call `self.action_set_background('red')`. The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action string and dispatches it to the appropriate method. @@ -26,9 +26,9 @@ The following example replaces the immediate call with a call to [action()][text --8<-- "docs/examples/guide/actions/actions02.py" ``` -Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` key. +Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` keyword. -You will not typically need this in a real app as Textual will run actions in links or key bindings. Before we discuss these, let's have a look at the syntax for action strings. +You will not typically need this in a real app as Textual will run actions in links or key bindings. Before we discuss these, let's have a closer look at the syntax for action strings. ## Syntax @@ -56,7 +56,7 @@ Consequently `"set_background('blue')"` is a valid action string, but `"set_back ## Links -Actions may be embedded in links with console markup, which you can introduce the `@click` tag. +Actions may be embedded as links within console markup. You can create such links with a `@click` tag. The following example mounts simple static text with embedded action links. @@ -71,7 +71,7 @@ The following example mounts simple static text with embedded action links. ```{.textual path="docs/examples/guide/actions/actions03.py"} ``` -When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color. +When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color and plays the terminals bell. ## Bindings