From aec897a0aedd3572edb00293348d40c01885e01f Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 03:58:13 -0500 Subject: [PATCH 1/6] Optimize syntax word wrap without line numbers --- rich/syntax.py | 13 +++++++++++++ tests/test_syntax.py | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/rich/syntax.py b/rich/syntax.py index 8c8f8315e7..e06b3f1b53 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -743,6 +743,19 @@ def _get_syntax( highlight_number_style, ) = self._get_number_styles(console) + if self.word_wrap and not self.line_numbers: + text = Text("\n").join(lines) + syntax_lines = console.render_lines( + text, + render_options.update(height=None, justify="left"), + style=background_style, + pad=not transparent_background, + new_lines=True, + ) + for syntax_line in syntax_lines: + yield from syntax_line + return + for line_no, line in enumerate(lines, self.start_line + line_offset): if self.word_wrap: wrapped_lines = console.render_lines( diff --git a/tests/test_syntax.py b/tests/test_syntax.py index f27227ea6a..857510124c 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -435,6 +435,29 @@ def test_padding_plus_wrap() -> None: assert output == expected +def test_word_wrap_without_line_numbers_with_line_range() -> None: + console = Console( + width=14, file=io.StringIO(), legacy_windows=False, record=True + ) + syntax = Syntax( + "first line should not appear\n" + "second line wraps around here\n" + "third line stays\n" + "fourth line should not appear", + lexer="text", + word_wrap=True, + line_numbers=False, + line_range=(2, 3), + ) + + console.print(syntax) + + assert ( + console.export_text() + == "second line \nwraps around \nhere \nthird line \nstays \n" + ) + + if __name__ == "__main__": syntax = Panel.fit( Syntax( From c651c4fb6a91ec4f5e061eb5c1094b8350a1e98d Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 04:32:50 -0500 Subject: [PATCH 2/6] Format syntax word wrap test --- tests/test_syntax.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_syntax.py b/tests/test_syntax.py index 857510124c..2f2d4bdb3d 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -436,9 +436,7 @@ def test_padding_plus_wrap() -> None: def test_word_wrap_without_line_numbers_with_line_range() -> None: - console = Console( - width=14, file=io.StringIO(), legacy_windows=False, record=True - ) + console = Console(width=14, file=io.StringIO(), legacy_windows=False, record=True) syntax = Syntax( "first line should not appear\n" "second line wraps around here\n" From dd6e32ea15b8de36a34c5633dcab42b8e4f1655c Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 10:56:36 -0500 Subject: [PATCH 3/6] perf(syntax): speed up line-numbered word wrap --- benchmarks/benchmarks.py | 16 ++++++++++++++++ rich/syntax.py | 26 ++++++++++++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py index 032eecf791..854ebb7359 100644 --- a/benchmarks/benchmarks.py +++ b/benchmarks/benchmarks.py @@ -94,6 +94,22 @@ def _print_with_width(self, width): self.console.print(self.syntax, width) +class SyntaxLineNumbersWrappingSuite: + def setup(self): + self.console = Console( + file=StringIO(), color_system="truecolor", legacy_windows=False + ) + self.syntax = Syntax( + code=snippets.PYTHON_SNIPPET * 120, + lexer="python", + word_wrap=True, + line_numbers=True, + ) + + def time_text_thin_terminal_heavy_wrapping(self): + self.console.print(self.syntax, width=30) + + class TableSuite: def time_table_no_wrapping(self): self._print_table(width=100) diff --git a/rich/syntax.py b/rich/syntax.py index e06b3f1b53..8c656f7609 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -758,12 +758,26 @@ def _get_syntax( for line_no, line in enumerate(lines, self.start_line + line_offset): if self.word_wrap: - wrapped_lines = console.render_lines( - line, - render_options.update(height=None, justify="left"), - style=background_style, - pad=not transparent_background, - ) + wrapped_lines = [ + _Segment.adjust_line_length( + list( + _Segment.apply_style( + wrapped_line.render(console), background_style + ) + ), + render_options.max_width, + style=background_style, + pad=not transparent_background, + ) + for wrapped_line in line.wrap( + console, + render_options.max_width, + justify="left", + overflow=render_options.overflow, + tab_size=self.tab_size, + no_wrap=render_options.no_wrap, + ) + ] else: segments = list(line.render(console, end="")) if options.no_wrap: From 6853707d5511b5ebde895a6f14b24381c184ba9d Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 11:01:05 -0500 Subject: [PATCH 4/6] perf(syntax): wrap syntax text directly --- benchmarks/benchmarks.py | 16 ++++++++++++++++ rich/syntax.py | 36 +++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py index 854ebb7359..60a7506ab1 100644 --- a/benchmarks/benchmarks.py +++ b/benchmarks/benchmarks.py @@ -110,6 +110,22 @@ def time_text_thin_terminal_heavy_wrapping(self): self.console.print(self.syntax, width=30) +class SyntaxLargeWrappingSuite: + def setup(self): + self.console = Console( + file=StringIO(), color_system="truecolor", legacy_windows=False + ) + self.syntax = Syntax( + code=snippets.PYTHON_SNIPPET * 120, + lexer="python", + word_wrap=True, + line_numbers=False, + ) + + def time_text_thin_terminal_heavy_wrapping(self): + self.console.print(self.syntax, width=30) + + class TableSuite: def time_table_no_wrapping(self): self._print_table(width=100) diff --git a/rich/syntax.py b/rich/syntax.py index 8c656f7609..17a666b157 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -745,15 +745,25 @@ def _get_syntax( if self.word_wrap and not self.line_numbers: text = Text("\n").join(lines) - syntax_lines = console.render_lines( - text, - render_options.update(height=None, justify="left"), - style=background_style, - pad=not transparent_background, - new_lines=True, - ) - for syntax_line in syntax_lines: - yield from syntax_line + for wrapped_text_line in text.wrap( + console, + render_options.max_width, + justify="left", + overflow=render_options.overflow, + tab_size=self.tab_size, + no_wrap=render_options.no_wrap, + ): + yield from _Segment.adjust_line_length( + list( + _Segment.apply_style( + wrapped_text_line.render(console), background_style + ) + ), + render_options.max_width, + style=background_style, + pad=not transparent_background, + ) + yield new_line return for line_no, line in enumerate(lines, self.start_line + line_offset): @@ -796,7 +806,7 @@ def _get_syntax( wrapped_line_left_pad = _Segment( " " * numbers_column_width + " ", background_style ) - for first, wrapped_line in loop_first(wrapped_lines): + for first, wrapped_segments in loop_first(wrapped_lines): if first: line_column = str(line_no).rjust(numbers_column_width - 2) + " " if highlight_line(line_no): @@ -807,11 +817,11 @@ def _get_syntax( yield _Segment(line_column, number_style) else: yield wrapped_line_left_pad - yield from wrapped_line + yield from wrapped_segments yield new_line else: - for wrapped_line in wrapped_lines: - yield from wrapped_line + for wrapped_segments in wrapped_lines: + yield from wrapped_segments yield new_line def _apply_stylized_ranges(self, text: Text) -> None: From 6d54cb8d98064001b4e78e23530ab41cc70e1ca2 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 11:31:08 -0500 Subject: [PATCH 5/6] perf(syntax): render no-wrap syntax directly --- benchmarks/benchmarks.py | 16 ++++++++++++++++ rich/syntax.py | 21 ++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py index 60a7506ab1..91623cda80 100644 --- a/benchmarks/benchmarks.py +++ b/benchmarks/benchmarks.py @@ -126,6 +126,22 @@ def time_text_thin_terminal_heavy_wrapping(self): self.console.print(self.syntax, width=30) +class SyntaxLargeNoWrapSuite: + def setup(self): + self.console = Console( + file=StringIO(), color_system="truecolor", legacy_windows=False + ) + self.syntax = Syntax( + code=snippets.PYTHON_SNIPPET * 120, + lexer="python", + word_wrap=False, + line_numbers=False, + ) + + def time_text_wide_terminal_no_wrapping(self): + self.console.print(self.syntax, width=80) + + class TableSuite: def time_table_no_wrapping(self): self._print_table(width=100) diff --git a/rich/syntax.py b/rich/syntax.py index 17a666b157..e78fcb7028 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -693,15 +693,18 @@ def _get_syntax( text, options=options.update(width=code_width) ) else: - syntax_lines = console.render_lines( - text, - options.update(width=code_width, height=None, justify="left"), - style=self.background_style, - pad=True, - new_lines=True, - ) - for syntax_line in syntax_lines: - yield from syntax_line + for line in text.split("\n", allow_blank=True): + line_style = console.get_style(line.style, default=Style.null()) + yield from Segment.adjust_line_length( + list( + Segment.apply_style( + line.render(console), self.background_style + ) + ), + code_width, + style=line_style, + ) + yield Segment.line() return start_line, end_line = self.line_range or (None, None) From 06406dd2c5bfdcbd9caaacd8b76df616fc12ecf3 Mon Sep 17 00:00:00 2001 From: Kevin Turcios Date: Mon, 29 Jun 2026 12:41:00 -0500 Subject: [PATCH 6/6] perf(syntax): skip line-range prefix text --- benchmarks/benchmarks.py | 17 +++++++++++++++++ rich/syntax.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py index 91623cda80..f7e7ac0a1d 100644 --- a/benchmarks/benchmarks.py +++ b/benchmarks/benchmarks.py @@ -142,6 +142,23 @@ def time_text_wide_terminal_no_wrapping(self): self.console.print(self.syntax, width=80) +class SyntaxLineRangeSuite: + def setup(self): + self.console = Console( + file=StringIO(), color_system="truecolor", legacy_windows=False + ) + self.syntax = Syntax( + code=snippets.PYTHON_SNIPPET * 120, + lexer="python", + word_wrap=False, + line_numbers=False, + line_range=(3000, 3060), + ) + + def time_text_wide_terminal_line_range(self): + self.console.print(self.syntax, width=80) + + class TableSuite: def time_table_no_wrapping(self): self._print_table(width=100) diff --git a/rich/syntax.py b/rich/syntax.py index e78fcb7028..4c2e3deed3 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -526,9 +526,10 @@ def tokens_to_spans() -> Iterable[Tuple[str, Optional[Style]]]: _token_type, token = next(tokens) except StopIteration: break - yield (token, None) if token.endswith("\n"): line_no += 1 + if line_no: + yield ("\n" * line_no, None) # Generate spans until line end for token_type, token in tokens: yield (token, _get_theme_style(token_type))