Fixed Text.wrap trimming lines in no_wrap mode

Previously, `rstrip_end` and `truncate` were being called even when
soft wrapping was enabled, which led to visual bugs where trailing
spaces and their backgrounds were incorrectly trimmed.

This change ensures that line trimming logic is now correctly
applied only during hard wrapping.

This also updates the expected output in a `Syntax` test that was
dependent on the old buggy behavior.

Fixes #3841
This commit is contained in:
KiGamji 2025-09-07 22:36:45 +05:00
parent ea9d4db5d8
commit fb00d7be6c
No known key found for this signature in database
GPG key ID: 3B5A231854B9AA85
5 changed files with 26 additions and 5 deletions

View file

@ -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

View file

@ -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)

View file

@ -1236,14 +1236,13 @@ class Text(JupyterMixin):
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

View file

@ -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

View file

@ -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)