diff --git a/cmd2/rich_utils.py b/cmd2/rich_utils.py index cc96e4bdc..efc9a29a2 100644 --- a/cmd2/rich_utils.py +++ b/cmd2/rich_utils.py @@ -477,64 +477,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 wrapper -_orig_text_from_ansi = Text.from_ansi - - -@classmethod # type: ignore[misc] -def _from_ansi_wrapper(cls: type[Text], text: str, *args: Any, **kwargs: Any) -> Text: # noqa: 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 = _from_ansi_wrapper # type: ignore[assignment] diff --git a/pyproject.toml b/pyproject.toml index 950708aa9..5285755d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,8 +33,8 @@ dependencies = [ "gnureadline>=8; platform_system == 'Darwin'", "pyperclip>=1.8.2", "pyreadline3>=3.4; platform_system == 'Windows'", - "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 ea7eb9e8c..71e3f5741 100644 --- a/tests/test_rich_utils.py +++ b/tests/test_rich_utils.py @@ -110,42 +110,6 @@ def test_set_theme() -> None: assert ru.APP_THEME.styles[rich_style_key] == theme[rich_style_key] -def test_from_ansi_wrapper() -> 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._from_ansi_wrapper. - assert Text.from_ansi.__func__ is ru._from_ansi_wrapper.__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 - - @with_ansi_style(ru.AllowStyle.ALWAYS) def test_cmd2_base_console_print() -> None: """Test that Cmd2BaseConsole.print() correctly propagates formatting overrides to structured renderables."""