prompt: 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()
  
  ```
  
golden_diff: 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
  
verification_info: {"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"}]}