We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`)` cannot appear as part of the parameter passed to an action
Reproduction:

```python
from textual.app import App


class ActionBugApp(App):
    BINDINGS = [("a", "test(')')", "Test")]

    def action_test(self, _: str) -> None:
        pass


if __name__ == '__main__':
    app = ActionBugApp()
    app.run()
```

Omitting the full stack trace (since it's fairly easy to reproduce), the key error message is:

```
ActionError: unable to parse "(')" in action "test(')')"
```

Seems that [this regex](https://github.com/Textualize/textual/blob/2a6368754a8b3a11f1772b52298b5d3b50ceebaa/src/textual/actions.py#L20) is not general enough.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/textual/actions.py`
Content:
```
1 from __future__ import annotations
2 
3 import ast
4 import re
5 
6 from typing_extensions import Any, TypeAlias
7 
8 ActionParseResult: TypeAlias = "tuple[str, tuple[Any, ...]]"
9 """An action is its name and the arbitrary tuple of its parameters."""
10 
11 
12 class SkipAction(Exception):
13     """Raise in an action to skip the action (and allow any parent bindings to run)."""
14 
15 
16 class ActionError(Exception):
17     pass
18 
19 
20 re_action_params = re.compile(r"([\w\.]+)(\(.*?\))")
21 
22 
23 def parse(action: str) -> ActionParseResult:
24     """Parses an action string.
25 
26     Args:
27         action: String containing action.
28 
29     Raises:
30         ActionError: If the action has invalid syntax.
31 
32     Returns:
33         Action name and parameters
34     """
35     params_match = re_action_params.match(action)
36     if params_match is not None:
37         action_name, action_params_str = params_match.groups()
38         try:
39             action_params = ast.literal_eval(action_params_str)
40         except Exception:
41             raise ActionError(
42                 f"unable to parse {action_params_str!r} in action {action!r}"
43             )
44     else:
45         action_name = action
46         action_params = ()
47 
48     return (
49         action_name,
50         action_params if isinstance(action_params, tuple) else (action_params,),
51     )
52
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/textual/actions.py b/src/textual/actions.py
--- a/src/textual/actions.py
+++ b/src/textual/actions.py
@@ -6,7 +6,7 @@
 from typing_extensions import Any, TypeAlias
 
 ActionParseResult: TypeAlias = "tuple[str, tuple[Any, ...]]"
-"""An action is its name and the arbitrary tuple of its parameters."""
+"""An action is its name and the arbitrary tuple of its arguments."""
 
 
 class SkipAction(Exception):
@@ -17,7 +17,7 @@
     pass
 
 
-re_action_params = re.compile(r"([\w\.]+)(\(.*?\))")
+re_action_args = re.compile(r"([\w\.]+)\((.*)\)")
 
 
 def parse(action: str) -> ActionParseResult:
@@ -30,22 +30,25 @@
         ActionError: If the action has invalid syntax.
 
     Returns:
-        Action name and parameters
+        Action name and arguments.
     """
-    params_match = re_action_params.match(action)
-    if params_match is not None:
-        action_name, action_params_str = params_match.groups()
-        try:
-            action_params = ast.literal_eval(action_params_str)
-        except Exception:
-            raise ActionError(
-                f"unable to parse {action_params_str!r} in action {action!r}"
-            )
+    args_match = re_action_args.match(action)
+    if args_match is not None:
+        action_name, action_args_str = args_match.groups()
+        if action_args_str:
+            try:
+                # We wrap `action_args_str` to be able to disambiguate the cases where
+                # the list of arguments is a comma-separated list of values from the
+                # case where the argument is a single tuple.
+                action_args: tuple[Any, ...] = ast.literal_eval(f"({action_args_str},)")
+            except Exception:
+                raise ActionError(
+                    f"unable to parse {action_args_str!r} in action {action!r}"
+                )
+        else:
+            action_args = ()
     else:
         action_name = action
-        action_params = ()
+        action_args = ()
 
-    return (
-        action_name,
-        action_params if isinstance(action_params, tuple) else (action_params,),
-    )
+    return action_name, action_args

{"golden_diff": "diff --git a/src/textual/actions.py b/src/textual/actions.py\n--- a/src/textual/actions.py\n+++ b/src/textual/actions.py\n@@ -6,7 +6,7 @@\n from typing_extensions import Any, TypeAlias\n \n ActionParseResult: TypeAlias = \"tuple[str, tuple[Any, ...]]\"\n-\"\"\"An action is its name and the arbitrary tuple of its parameters.\"\"\"\n+\"\"\"An action is its name and the arbitrary tuple of its arguments.\"\"\"\n \n \n class SkipAction(Exception):\n@@ -17,7 +17,7 @@\n     pass\n \n \n-re_action_params = re.compile(r\"([\\w\\.]+)(\\(.*?\\))\")\n+re_action_args = re.compile(r\"([\\w\\.]+)\\((.*)\\)\")\n \n \n def parse(action: str) -> ActionParseResult:\n@@ -30,22 +30,25 @@\n         ActionError: If the action has invalid syntax.\n \n     Returns:\n-        Action name and parameters\n+        Action name and arguments.\n     \"\"\"\n-    params_match = re_action_params.match(action)\n-    if params_match is not None:\n-        action_name, action_params_str = params_match.groups()\n-        try:\n-            action_params = ast.literal_eval(action_params_str)\n-        except Exception:\n-            raise ActionError(\n-                f\"unable to parse {action_params_str!r} in action {action!r}\"\n-            )\n+    args_match = re_action_args.match(action)\n+    if args_match is not None:\n+        action_name, action_args_str = args_match.groups()\n+        if action_args_str:\n+            try:\n+                # We wrap `action_args_str` to be able to disambiguate the cases where\n+                # the list of arguments is a comma-separated list of values from the\n+                # case where the argument is a single tuple.\n+                action_args: tuple[Any, ...] = ast.literal_eval(f\"({action_args_str},)\")\n+            except Exception:\n+                raise ActionError(\n+                    f\"unable to parse {action_args_str!r} in action {action!r}\"\n+                )\n+        else:\n+            action_args = ()\n     else:\n         action_name = action\n-        action_params = ()\n+        action_args = ()\n \n-    return (\n-        action_name,\n-        action_params if isinstance(action_params, tuple) else (action_params,),\n-    )\n+    return action_name, action_args\n", "issue": "`)` cannot appear as part of the parameter passed to an action\nReproduction:\r\n\r\n```python\r\nfrom textual.app import App\r\n\r\n\r\nclass ActionBugApp(App):\r\n    BINDINGS = [(\"a\", \"test(')')\", \"Test\")]\r\n\r\n    def action_test(self, _: str) -> None:\r\n        pass\r\n\r\n\r\nif __name__ == '__main__':\r\n    app = ActionBugApp()\r\n    app.run()\r\n```\r\n\r\nOmitting the full stack trace (since it's fairly easy to reproduce), the key error message is:\r\n\r\n```\r\nActionError: unable to parse \"(')\" in action \"test(')')\"\r\n```\r\n\r\nSeems that [this regex](https://github.com/Textualize/textual/blob/2a6368754a8b3a11f1772b52298b5d3b50ceebaa/src/textual/actions.py#L20) is not general enough.\n", "before_files": [{"content": "from __future__ import annotations\n\nimport ast\nimport re\n\nfrom typing_extensions import Any, TypeAlias\n\nActionParseResult: TypeAlias = \"tuple[str, tuple[Any, ...]]\"\n\"\"\"An action is its name and the arbitrary tuple of its parameters.\"\"\"\n\n\nclass SkipAction(Exception):\n    \"\"\"Raise in an action to skip the action (and allow any parent bindings to run).\"\"\"\n\n\nclass ActionError(Exception):\n    pass\n\n\nre_action_params = re.compile(r\"([\\w\\.]+)(\\(.*?\\))\")\n\n\ndef parse(action: str) -> ActionParseResult:\n    \"\"\"Parses an action string.\n\n    Args:\n        action: String containing action.\n\n    Raises:\n        ActionError: If the action has invalid syntax.\n\n    Returns:\n        Action name and parameters\n    \"\"\"\n    params_match = re_action_params.match(action)\n    if params_match is not None:\n        action_name, action_params_str = params_match.groups()\n        try:\n            action_params = ast.literal_eval(action_params_str)\n        except Exception:\n            raise ActionError(\n                f\"unable to parse {action_params_str!r} in action {action!r}\"\n            )\n    else:\n        action_name = action\n        action_params = ()\n\n    return (\n        action_name,\n        action_params if isinstance(action_params, tuple) else (action_params,),\n    )\n", "path": "src/textual/actions.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport ast\nimport re\n\nfrom typing_extensions import Any, TypeAlias\n\nActionParseResult: TypeAlias = \"tuple[str, tuple[Any, ...]]\"\n\"\"\"An action is its name and the arbitrary tuple of its arguments.\"\"\"\n\n\nclass SkipAction(Exception):\n    \"\"\"Raise in an action to skip the action (and allow any parent bindings to run).\"\"\"\n\n\nclass ActionError(Exception):\n    pass\n\n\nre_action_args = re.compile(r\"([\\w\\.]+)\\((.*)\\)\")\n\n\ndef parse(action: str) -> ActionParseResult:\n    \"\"\"Parses an action string.\n\n    Args:\n        action: String containing action.\n\n    Raises:\n        ActionError: If the action has invalid syntax.\n\n    Returns:\n        Action name and arguments.\n    \"\"\"\n    args_match = re_action_args.match(action)\n    if args_match is not None:\n        action_name, action_args_str = args_match.groups()\n        if action_args_str:\n            try:\n                # We wrap `action_args_str` to be able to disambiguate the cases where\n                # the list of arguments is a comma-separated list of values from the\n                # case where the argument is a single tuple.\n                action_args: tuple[Any, ...] = ast.literal_eval(f\"({action_args_str},)\")\n            except Exception:\n                raise ActionError(\n                    f\"unable to parse {action_args_str!r} in action {action!r}\"\n                )\n        else:\n            action_args = ()\n    else:\n        action_name = action\n        action_args = ()\n\n    return action_name, action_args\n", "path": "src/textual/actions.py"}]}