From 39ee57dfe70614381c3ebce34cb35cab557af2f5 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Jan 2026 15:04:35 +0000 Subject: [PATCH 1/5] fix background style with soft wrap --- rich/console.py | 11 ++++++----- tests/test_text.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/rich/console.py b/rich/console.py index 994adfc069..406a088cb1 100644 --- a/rich/console.py +++ b/rich/console.py @@ -1723,12 +1723,13 @@ def print( for renderable in renderables: extend(render(renderable, render_options)) else: + render_style = self.get_style(style) + new_line = Segment.line() for renderable in renderables: - extend( - Segment.apply_style( - render(renderable, render_options), self.get_style(style) - ) - ) + for line in Segment.split_lines(render(renderable, render_options)): + extend(Segment.apply_style(line, render_style)) + new_segments.append(new_line) + if new_line_start: if ( len("".join(segment.text for segment in new_segments).splitlines()) diff --git a/tests/test_text.py b/tests/test_text.py index b528608ad7..b6d39f5949 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -1110,3 +1110,20 @@ def test_soft_wrap() -> None: print(repr(output)) expected = "\x1b[37;44m Hello World \x1b[0m\n" assert output == expected + + +def test_soft_wrap_styled() -> None: + """Regression test for https://github.com/Textualize/rich/issues/3838 + + If soft_wrap is True and a style is set, we don't want to style the new lines. + """ + console = Console(color_system="standard", width=80, force_terminal=True) + with console.capture() as capture: + console.print("soft wrap is on", style="blue on white", soft_wrap=True) + console.print("Next line") + + output = capture.get() + print(repr(output)) + # Background is reset before \n + expected = "\x1b[34;47msoft wrap is on\x1b[0m\nNext line\n" + assert output == expected From 565087254a8c9f1e75dd6635b0c3df52190515fe Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Jan 2026 15:07:12 +0000 Subject: [PATCH 2/5] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b06bdaef..53e9256403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed extraneous blank line on non-interactive disabled `Progress` https://github.com/Textualize/rich/pull/3905 - Fixed extra padding on first cell in columns https://github.com/Textualize/rich/pull/3935 - Fixed trailing whitespace removed when soft_wrap=True https://github.com/Textualize/rich/pull/3937 +- Fixed style new-lines when soft_wrap = True and a print style is set https://github.com/Textualize/rich/pull/3938 ### Added From 9992173c74051e6f0b3e336775fea6feb5e56d33 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Jan 2026 15:26:19 +0000 Subject: [PATCH 3/5] split lines terminator --- CHANGELOG.md | 1 + rich/console.py | 7 +++++-- rich/segment.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53e9256403..33d7734cd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for `UNICODE_VERSION` environment variable https://github.com/Textualize/rich/pull/3930 - Added `last_render_height` property to LiveRender https://github.com/Textualize/rich/pull/3934 - Expose locals_max_depth and locals_overflow in traceback.install https://github.com/Textualize/rich/pull/3906/ +- Added `Segment.split_lines_terminator` https://github.com/Textualize/rich/pull/3938 ### Changed diff --git a/rich/console.py b/rich/console.py index 406a088cb1..ad92d529c0 100644 --- a/rich/console.py +++ b/rich/console.py @@ -1726,9 +1726,12 @@ def print( render_style = self.get_style(style) new_line = Segment.line() for renderable in renderables: - for line in Segment.split_lines(render(renderable, render_options)): + for line, add_new_line in Segment.split_lines_terminator( + render(renderable, render_options) + ): extend(Segment.apply_style(line, render_style)) - new_segments.append(new_line) + if add_new_line: + new_segments.append(new_line) if new_line_start: if ( diff --git a/rich/segment.py b/rich/segment.py index edcb52dd3f..0df63fdefe 100644 --- a/rich/segment.py +++ b/rich/segment.py @@ -275,6 +275,37 @@ def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]] if line: yield line + @classmethod + def split_lines_terminator( + cls, segments: Iterable["Segment"] + ) -> Iterable[Tuple[List["Segment"], bool]]: + """Split a sequence of segments in to a list of lines and a boolean to indicate if there was a new line. + + Args: + segments (Iterable[Segment]): Segments potentially containing line feeds. + + Yields: + Iterable[List[Segment]]: Iterable of segment lists, one per line. + """ + line: List[Segment] = [] + append = line.append + + for segment in segments: + if "\n" in segment.text and not segment.control: + text, style, _ = segment + while text: + _text, new_line, text = text.partition("\n") + if _text: + append(cls(_text, style)) + if new_line: + yield (line, True) + line = [] + append = line.append + else: + append(segment) + if line: + yield (line, False) + @classmethod def split_and_crop_lines( cls, From cead8a0c0898f7fe24a2c9d2f4c852fae26fef71 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Jan 2026 15:27:15 +0000 Subject: [PATCH 4/5] test --- tests/test_segment.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_segment.py b/tests/test_segment.py index 2264dbe50f..8ab169aefa 100644 --- a/tests/test_segment.py +++ b/tests/test_segment.py @@ -34,6 +34,14 @@ def test_split_lines(): assert list(Segment.split_lines(lines)) == [[Segment("Hello")], [Segment("World")]] +def test_split_lines_terminator(): + lines = [Segment("Hello\nWorld")] + assert list(Segment.split_lines_terminator(lines)) == [ + ([Segment("Hello")], True), + ([Segment("World")], False), + ] + + def test_split_and_crop_lines(): assert list( Segment.split_and_crop_lines([Segment("Hello\nWorld!\n"), Segment("foo")], 4) From 2c247e6da72dfa69dd6148f1a0657e2440d1ebe9 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Fri, 23 Jan 2026 15:28:10 +0000 Subject: [PATCH 5/5] test --- tests/test_segment.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_segment.py b/tests/test_segment.py index 8ab169aefa..60a7a9d59a 100644 --- a/tests/test_segment.py +++ b/tests/test_segment.py @@ -42,6 +42,13 @@ def test_split_lines_terminator(): ] +def test_split_lines_terminator_single_line(): + lines = [Segment("Hello")] + assert list(Segment.split_lines_terminator(lines)) == [ + ([Segment("Hello")], False), + ] + + def test_split_and_crop_lines(): assert list( Segment.split_and_crop_lines([Segment("Hello\nWorld!\n"), Segment("foo")], 4)