diff --git a/rich/ansi.py b/rich/ansi.py index 7bbcb0bfcc..ffe657f276 100644 --- a/rich/ansi.py +++ b/rich/ansi.py @@ -133,7 +133,7 @@ def decode(self, terminal_text: str) -> Iterable[Text]: Text: Marked up Text. """ for line in re.split(r"(?<=\n)", terminal_text): - yield self.decode_line(line.rstrip("\n")) + yield self.decode_line(line.rstrip("\r\n")) def decode_line(self, line: str) -> Text: """Decode a line containing ansi codes. diff --git a/rich/traceback.py b/rich/traceback.py index 66eaecaae9..52629744fd 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -467,8 +467,6 @@ def extract( from rich import _IMPORT_CWD - notes: List[str] = getattr(exc_value, "__notes__", None) or [] - grouped_exceptions: Set[BaseException] = ( set() if _visited_exceptions is None else _visited_exceptions ) @@ -481,6 +479,7 @@ def safe_str(_object: Any) -> str: return "" while True: + notes: List[str] = getattr(exc_value, "__notes__", None) or [] stack = Stack( exc_type=safe_str(exc_type.__name__), exc_value=safe_str(exc_value), diff --git a/tests/test_ansi.py b/tests/test_ansi.py index 21e20b4403..95cbd09006 100644 --- a/tests/test_ansi.py +++ b/tests/test_ansi.py @@ -102,3 +102,12 @@ def test_decode_newlines(): assert Text.from_ansi("Hello\nWorld\n").plain == "Hello\nWorld\n" assert Text.from_ansi("Hello\nWorld\n\n").plain == "Hello\nWorld\n\n" assert Text.from_ansi("\nHello\nWorld\n\n").plain == "\nHello\nWorld\n\n" + + +def test_decode_crlf(): + """Test CRLF line endings are handled correctly. + Regression test for https://github.com/Textualize/rich/issues/4090 + """ + assert Text.from_ansi("Hello\r\n").plain == "Hello\n" + assert Text.from_ansi("Hello\r\nWorld\r\n").plain == "Hello\nWorld\n" + assert Text.from_ansi("Hello\r\nWorld").plain == "Hello\nWorld" diff --git a/tests/test_traceback.py b/tests/test_traceback.py index bcae6920b1..85fc68b19f 100644 --- a/tests/test_traceback.py +++ b/tests/test_traceback.py @@ -375,6 +375,24 @@ def test_notes() -> None: assert traceback.trace.stacks[0].notes == ["Hello", "World"] +def test_notes_chained_exceptions() -> None: + """Check __notes__ are only shown on the exception that has them. + Regression test for https://github.com/Textualize/rich/issues/3960 + """ + try: + try: + raise ValueError("inner") + except ValueError as exc: + raise RuntimeError("outer") from exc + except RuntimeError as exc: + exc.add_note("only on outer") + traceback = Traceback() + + stacks = traceback.trace.stacks + assert stacks[0].notes == ["only on outer"] # RuntimeError (outermost) + assert stacks[1].notes == [] # ValueError (inner, no notes) + + def test_recursive_exception() -> None: """Regression test for https://github.com/Textualize/rich/issues/3708