diff --git a/cmd2/rich_utils.py b/cmd2/rich_utils.py index 4708a4e61..1c46a1573 100644 --- a/cmd2/rich_utils.py +++ b/cmd2/rich_utils.py @@ -479,64 +479,3 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]: object_list[i] = Text.from_ansi(renderable_as_str) return tuple(object_list) - - -################################################################################### -# Rich Library Monkey Patches -# -# These patches fix specific bugs in the Rich library. They are conditional and -# will only be applied if the bug is detected. When the bugs are fixed in a -# future Rich release, these patches and their corresponding tests should be -# removed. -################################################################################### - -################################################################################### -# Text.from_ansi() monkey patch -################################################################################### - -# Save original Text.from_ansi() so we can call it in our patch -_orig_text_from_ansi = Text.from_ansi - - -@classmethod # type: ignore[misc] -def _Text_from_ansi(cls: type[Text], text: str, *args: Any, **kwargs: Any) -> Text: # noqa: N802, ARG001 - r"""Wrap Text.from_ansi() to fix its trailing newline bug. - - This wrapper handles an issue where Text.from_ansi() removes the - trailing line break from a string (e.g. "Hello\n" becomes "Hello"). - - There is currently a pull request on Rich to fix this. - https://github.com/Textualize/rich/pull/3793 - """ - result = _orig_text_from_ansi(text, *args, **kwargs) - - # If the original string ends with a recognized line break character, - # then restore the missing newline. We use "\n" because Text.from_ansi() - # converts all line breaks into newlines. - # Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines - line_break_chars = { - "\n", # Line Feed - "\r", # Carriage Return - "\v", # Vertical Tab - "\f", # Form Feed - "\x1c", # File Separator - "\x1d", # Group Separator - "\x1e", # Record Separator - "\x85", # Next Line (NEL) - "\u2028", # Line Separator - "\u2029", # Paragraph Separator - } - if text and text[-1] in line_break_chars: - result.append("\n") - - return result - - -def _from_ansi_has_newline_bug() -> bool: - """Check if Test.from_ansi() strips the trailing line break from a string.""" - return Text.from_ansi("\n") == Text.from_ansi("") - - -# Only apply the monkey patch if the bug is present -if _from_ansi_has_newline_bug(): - Text.from_ansi = _Text_from_ansi # type: ignore[assignment] diff --git a/pyproject.toml b/pyproject.toml index 281032af1..606a031c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,8 @@ dependencies = [ "backports.strenum; python_version == '3.10'", "prompt-toolkit>=3.0.52", "pyperclip>=1.8.2", - "rich>=14.3.0", - "rich-argparse>=1.7.1", + "rich>=15.0.0", + "rich-argparse>=1.7.2", "typing-extensions; python_version == '3.10'", ] diff --git a/tests/test_rich_utils.py b/tests/test_rich_utils.py index 948ce5564..b631907ee 100644 --- a/tests/test_rich_utils.py +++ b/tests/test_rich_utils.py @@ -115,42 +115,6 @@ def test_set_theme() -> None: assert ru.APP_THEME.styles[rich_style_key] == theme[rich_style_key] -def test_from_ansi_patch() -> None: - # Check if we are still patching Text.from_ansi(). If this check fails, then Rich - # has fixed the bug. Therefore, we can remove this test function and ru._Text_from_ansi. - assert Text.from_ansi.__func__ is ru._Text_from_ansi.__func__ # type: ignore[attr-defined] - - # Line breaks recognized by str.splitlines(). - # Source: https://docs.python.org/3/library/stdtypes.html#str.splitlines - line_breaks = { - "\n", # Line Feed - "\r", # Carriage Return - "\r\n", # Carriage Return + Line Feed - "\v", # Vertical Tab - "\f", # Form Feed - "\x1c", # File Separator - "\x1d", # Group Separator - "\x1e", # Record Separator - "\x85", # Next Line (NEL) - "\u2028", # Line Separator - "\u2029", # Paragraph Separator - } - - # Test all line breaks - for lb in line_breaks: - input_string = f"Text{lb}" - expected_output = input_string.replace(lb, "\n") - assert Text.from_ansi(input_string).plain == expected_output - - # Test string without trailing line break - input_string = "No trailing\nline break" - assert Text.from_ansi(input_string).plain == input_string - - # Test empty string - input_string = "" - assert Text.from_ansi(input_string).plain == input_string - - def test_cmd2_base_console_print(mocker: MockerFixture) -> None: """Test that Cmd2BaseConsole.print() calls prepare_objects_for_rendering().""" # Mock prepare_objects_for_rendering to return a specific value