diff --git a/CHANGELOG.md b/CHANGELOG.md index e7583d893..11ad81ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- Fixed Text.wrap trimming lines in no_wrap mode https://github.com/Textualize/rich/issues/3841 + ## [14.1.0] - 2025-06-25 ### Changed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4b04786b9..0ce667322 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -47,6 +47,7 @@ The following people have contributed to the development of Rich: - [Antony Milne](https://github.com/AntonyMilneQB) - [Michael Milton](https://github.com/multimeric) - [Martina Oefelein](https://github.com/oefe) +- [Igor Oleynik](https://github.com/KiGamji) - [Nathan Page](https://github.com/nathanrpage97) - [Dave Pearson](https://github.com/davep/) - [Avi Perl](https://github.com/avi-perl) diff --git a/rich/text.py b/rich/text.py index b57d77c27..6be949ece 100644 --- a/rich/text.py +++ b/rich/text.py @@ -1236,14 +1236,13 @@ def wrap( else: offsets = divide_line(str(line), width, fold=wrap_overflow == "fold") new_lines = line.divide(offsets) - for line in new_lines: - line.rstrip_end(width) + for line in new_lines: + line.rstrip_end(width) + line.truncate(width, overflow=wrap_overflow) if wrap_justify: new_lines.justify( console, width, justify=wrap_justify, overflow=wrap_overflow ) - for line in new_lines: - line.truncate(width, overflow=wrap_overflow) lines.extend(new_lines) return lines diff --git a/tests/test_syntax.py b/tests/test_syntax.py index 3537ab5b6..b8373d8e5 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -115,7 +115,7 @@ def test_python_render_simple_indent_guides() -> None: ) rendered_syntax = render(syntax) print(repr(rendered_syntax)) - expected = '\x1b[34mdef\x1b[0m\x1b[37m \x1b[0m\x1b[32mloop_first_last\x1b[0m(values: Iterable[T]) -> Iterable[Tuple[\x1b[36mb\x1b[0m\n\x1b[2;37m│ \x1b[0m\x1b[33m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[2m│ \x1b[0miter_values = \x1b[36miter\x1b[0m(values)\n\x1b[2m│ \x1b[0m\x1b[34mtry\x1b[0m:\n\x1b[2m│ │ \x1b[0mprevious_value = \x1b[36mnext\x1b[0m(iter_values)\n\x1b[2m│ \x1b[0m\x1b[34mexcept\x1b[0m \x1b[36mStopIteration\x1b[0m:\n\x1b[2m│ │ \x1b[0m\x1b[34mreturn\x1b[0m\n\x1b[2m│ \x1b[0mfirst = \x1b[34mTrue\x1b[0m\n\x1b[2m│ \x1b[0m\x1b[34mfor\x1b[0m value \x1b[35min\x1b[0m iter_values:\n\x1b[2m│ │ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mFalse\x1b[0m, previous_value\n\x1b[2m│ │ \x1b[0mfirst = \x1b[34mFalse\x1b[0m\n\x1b[2m│ │ \x1b[0mprevious_value = value\n\x1b[2m│ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mTrue\x1b[0m, previous_value\n' + expected = '\x1b[34mdef\x1b[0m\x1b[37m \x1b[0m\x1b[32mloop_first_last\x1b[0m(values: Iterable[T]) -> Iterable[Tuple[\x1b[36mbool\x1b[0m, \x1b[36mbool\x1b[0m, T]]:\n\x1b[2;37m│ \x1b[0m\x1b[33m"""Iterate and generate a tuple with a flag for first and last value."""\x1b[0m\n\x1b[2m│ \x1b[0miter_values = \x1b[36miter\x1b[0m(values)\n\x1b[2m│ \x1b[0m\x1b[34mtry\x1b[0m:\n\x1b[2m│ │ \x1b[0mprevious_value = \x1b[36mnext\x1b[0m(iter_values)\n\x1b[2m│ \x1b[0m\x1b[34mexcept\x1b[0m \x1b[36mStopIteration\x1b[0m:\n\x1b[2m│ │ \x1b[0m\x1b[34mreturn\x1b[0m\n\x1b[2m│ \x1b[0mfirst = \x1b[34mTrue\x1b[0m\n\x1b[2m│ \x1b[0m\x1b[34mfor\x1b[0m value \x1b[35min\x1b[0m iter_values:\n\x1b[2m│ │ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mFalse\x1b[0m, previous_value\n\x1b[2m│ │ \x1b[0mfirst = \x1b[34mFalse\x1b[0m\n\x1b[2m│ │ \x1b[0mprevious_value = value\n\x1b[2m│ \x1b[0m\x1b[34myield\x1b[0m first, \x1b[34mTrue\x1b[0m, previous_value\n' assert rendered_syntax == expected diff --git a/tests/test_text.py b/tests/test_text.py index fee7302f2..68506a46e 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -641,6 +641,22 @@ def test_no_wrap_no_crop(): ) +def test_no_wrap_no_strip_trailing_space(): + """Test that Text.wrap doesn't strip trailing spaces from styled segments in no wrap mode.""" + console = Console(width=40) + + text = Text() + text.append("x" * 35) + text.append(" test ", style="white on blue") + + lines = text.wrap(console, width=40, no_wrap=True) + + assert len(lines) == 1 + result_text = lines[0] + + assert result_text.plain == "x" * 35 + " test " + + def test_fit(): text = Text("Hello\nWorld") lines = text.fit(3)