From 55d00e1e9e70c21dbbadf2280a0cb0c8ba8fc07a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 12:44:25 +0100 Subject: [PATCH 1/9] Add padding to Syntax constructor --- rich/syntax.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rich/syntax.py b/rich/syntax.py index 6a337e407..27113955f 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -242,6 +242,7 @@ def __init__( word_wrap: bool = False, background_color: Optional[str] = None, indent_guides: bool = False, + padding: int = 0, ) -> None: self.code = code self._lexer = lexer @@ -258,6 +259,7 @@ def __init__( Style(bgcolor=background_color) if background_color else Style() ) self.indent_guides = indent_guides + self.padding = padding self._theme = self.get_theme(theme) From e24bc05fa127de0bce9e16bbfe1b93f42c0ca923 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 12:50:09 +0100 Subject: [PATCH 2/9] Add padding to from_path constructor, use correct type --- rich/syntax.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rich/syntax.py b/rich/syntax.py index 27113955f..c7560515f 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -23,6 +23,7 @@ from pygments.util import ClassNotFound from rich.containers import Lines +from rich.padding import PaddingDimensions from ._loop import loop_first from .color import Color, blend_rgb @@ -209,6 +210,7 @@ class Syntax(JupyterMixin): word_wrap (bool, optional): Enable word wrapping. background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. indent_guides (bool, optional): Show indent guides. Defaults to False. + padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding). """ _pygments_style_class: Type[PygmentsStyle] @@ -242,7 +244,7 @@ def __init__( word_wrap: bool = False, background_color: Optional[str] = None, indent_guides: bool = False, - padding: int = 0, + padding: PaddingDimensions = 0, ) -> None: self.code = code self._lexer = lexer @@ -280,6 +282,7 @@ def from_path( word_wrap: bool = False, background_color: Optional[str] = None, indent_guides: bool = False, + padding: PaddingDimensions = 0, ) -> "Syntax": """Construct a Syntax object from a file. @@ -298,6 +301,7 @@ def from_path( word_wrap (bool, optional): Enable word wrapping of code. background_color (str, optional): Optional background color, or None to use theme color. Defaults to None. indent_guides (bool, optional): Show indent guides. Defaults to False. + padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding). Returns: [Syntax]: A Syntax object that may be printed to the console @@ -322,6 +326,7 @@ def from_path( word_wrap=word_wrap, background_color=background_color, indent_guides=indent_guides, + padding=padding, ) @classmethod From dd1dd1f6f4057b8b42f70b4bd374d02581b41c79 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 13:36:48 +0100 Subject: [PATCH 3/9] Padding for `Syntax` - support vertical padding --- rich/syntax.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/rich/syntax.py b/rich/syntax.py index c7560515f..564ac0b44 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -23,7 +23,7 @@ from pygments.util import ClassNotFound from rich.containers import Lines -from rich.padding import PaddingDimensions +from rich.padding import Padding, PaddingDimensions from ._loop import loop_first from .color import Color, blend_rgb @@ -534,15 +534,15 @@ def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]: def __rich_measure__( self, console: "Console", options: "ConsoleOptions" ) -> "Measurement": + _, right, _, left = Padding.unpack(self.padding) if self.code_width is not None: - width = self.code_width + self._numbers_column_width + width = self.code_width + self._numbers_column_width + right + left return Measurement(self._numbers_column_width, width) return Measurement(self._numbers_column_width, options.max_width) def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: - transparent_background = self._get_base_style().transparent_background code_width = ( ( @@ -622,11 +622,17 @@ def __rich_console__( highlight_line = self.highlight_lines.__contains__ _Segment = Segment - padding = _Segment(" " * numbers_column_width + " ", background_style) + wrapped_line_left_pad = _Segment( + " " * numbers_column_width + " ", background_style + ) new_line = _Segment("\n") line_pointer = "> " if options.legacy_windows else "❱ " + top, right, bottom, left = Padding.unpack(self.padding) + yield from self._vertical_padding_segments( + top, code_width + left + right, style=background_style + ) for line_no, line in enumerate(lines, self.start_line + line_offset): if self.word_wrap: wrapped_lines = console.render_lines( @@ -635,7 +641,6 @@ def __rich_console__( style=background_style, pad=not transparent_background, ) - else: segments = list(line.render(console, end="")) if options.no_wrap: @@ -649,6 +654,7 @@ def __rich_console__( pad=not transparent_background, ) ] + if self.line_numbers: for first, wrapped_line in loop_first(wrapped_lines): if first: @@ -660,13 +666,23 @@ def __rich_console__( yield _Segment(" ", highlight_number_style) yield _Segment(line_column, number_style) else: - yield padding + yield wrapped_line_left_pad yield from wrapped_line yield new_line else: for wrapped_line in wrapped_lines: yield from wrapped_line yield new_line + yield from self._vertical_padding_segments( + top, code_width + left + right, background_style + ) + + def _vertical_padding_segments( + self, pad_amount: int, width: int, style: Style + ) -> Iterable[Segment]: + for _ in range(pad_amount): + yield Segment(" " * width, style) + yield Segment.line() if __name__ == "__main__": # pragma: no cover @@ -746,6 +762,9 @@ def __rich_console__( dest="lexer_name", help="Lexer name", ) + parser.add_argument( + "-p", "--padding", type=int, default=0, dest="padding", help="Padding" + ) args = parser.parse_args() from rich.console import Console @@ -762,6 +781,7 @@ def __rich_console__( theme=args.theme, background_color=args.background_color, indent_guides=args.indent_guides, + padding=args.padding, ) else: syntax = Syntax.from_path( @@ -772,5 +792,6 @@ def __rich_console__( theme=args.theme, background_color=args.background_color, indent_guides=args.indent_guides, + padding=args.padding, ) console.print(syntax, soft_wrap=args.soft_wrap) From 1d59550a3ebdcf50ca342cf73e44b281ece70fc9 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 15:33:45 +0100 Subject: [PATCH 4/9] Extract logic for create base syntax without padding --- rich/syntax.py | 54 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/rich/syntax.py b/rich/syntax.py index 564ac0b44..19a73ed20 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -30,7 +30,7 @@ from .console import Console, ConsoleOptions, JustifyMethod, RenderResult from .jupyter import JupyterMixin from .measure import Measurement -from .segment import Segment +from .segment import Segment, Segments from .style import Style from .text import Text @@ -101,6 +101,7 @@ } RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK} +NUMBERS_COLUMN_DEFAULT_PADDING = 2 class SyntaxTheme(ABC): @@ -505,7 +506,10 @@ def _numbers_column_width(self) -> int: """Get the number of characters used to render the numbers column.""" column_width = 0 if self.line_numbers: - column_width = len(str(self.start_line + self.code.count("\n"))) + 2 + column_width = ( + len(str(self.start_line + self.code.count("\n"))) + + NUMBERS_COLUMN_DEFAULT_PADDING + ) return column_width def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]: @@ -542,6 +546,29 @@ def __rich_measure__( def __rich_console__( self, console: Console, options: ConsoleOptions + ) -> RenderResult: + ( + background_style, + number_style, + highlight_number_style, + ) = self._get_number_styles(console) + segments = Segments( + self._get_syntax( + console, options, background_style, number_style, highlight_number_style + ) + ) + if self.padding: + yield Padding(segments, style=background_style, pad=self.padding) + else: + yield segments + + def _get_syntax( + self, + console: Console, + options: ConsoleOptions, + background_style, + number_style, + highlight_number_style, ) -> RenderResult: transparent_background = self._get_base_style().transparent_background code_width = ( @@ -560,12 +587,6 @@ def __rich_console__( code = code.expandtabs(self.tab_size) text = self.highlight(code, self.line_range) - ( - background_style, - number_style, - highlight_number_style, - ) = self._get_number_styles(console) - if not self.line_numbers and not self.word_wrap and not self.line_range: if not ends_on_nl: text.remove_suffix("\n") @@ -629,10 +650,6 @@ def __rich_console__( line_pointer = "> " if options.legacy_windows else "❱ " - top, right, bottom, left = Padding.unpack(self.padding) - yield from self._vertical_padding_segments( - top, code_width + left + right, style=background_style - ) for line_no, line in enumerate(lines, self.start_line + line_offset): if self.word_wrap: wrapped_lines = console.render_lines( @@ -673,13 +690,11 @@ def __rich_console__( for wrapped_line in wrapped_lines: yield from wrapped_line yield new_line - yield from self._vertical_padding_segments( - top, code_width + left + right, background_style - ) def _vertical_padding_segments( self, pad_amount: int, width: int, style: Style ) -> Iterable[Segment]: + """Yields Segments for the padding at the top and bottom of the Syntax""" for _ in range(pad_amount): yield Segment(" " * width, style) yield Segment.line() @@ -765,6 +780,13 @@ def _vertical_padding_segments( parser.add_argument( "-p", "--padding", type=int, default=0, dest="padding", help="Padding" ) + parser.add_argument( + "--highlight-line", + type=int, + default=None, + dest="highlight_line", + help="The line number (not index!) to highlight", + ) args = parser.parse_args() from rich.console import Console @@ -782,6 +804,7 @@ def _vertical_padding_segments( background_color=args.background_color, indent_guides=args.indent_guides, padding=args.padding, + highlight_lines={args.highlight_line}, ) else: syntax = Syntax.from_path( @@ -793,5 +816,6 @@ def _vertical_padding_segments( background_color=args.background_color, indent_guides=args.indent_guides, padding=args.padding, + highlight_lines={args.highlight_line}, ) console.print(syntax, soft_wrap=args.soft_wrap) From a8e8690d15e5eff6b254f2f17e53339db82da2e3 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 15:51:22 +0100 Subject: [PATCH 5/9] Add test for padded `Syntax` --- tests/test_syntax.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_syntax.py b/tests/test_syntax.py index e5d904f36..a88ce0a33 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -1,5 +1,5 @@ # coding=utf-8 - +import io import os import sys import tempfile @@ -303,6 +303,22 @@ def test_syntax_guess_lexer(): assert Syntax.guess_lexer("banana.html", "{{something|filter:3}}") == "html+django" +def test_syntax_padding(): + syntax = Syntax("x = 1", lexer="python", padding=(1, 3)) + console = Console( + width=20, + file=io.StringIO(), + color_system="truecolor", + legacy_windows=False, + record=True, + ) + console.print(syntax) + output = console.export_text() + assert ( + output == " \n x = 1 \n \n" + ) + + if __name__ == "__main__": syntax = Panel.fit( Syntax( From dfa7111098320eaf5e1c0d50f843977e648f5d4a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 16:02:51 +0100 Subject: [PATCH 6/9] Small refactor of Syntax padding --- rich/syntax.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/rich/syntax.py b/rich/syntax.py index 19a73ed20..d2bcb33a6 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -553,9 +553,7 @@ def __rich_console__( highlight_number_style, ) = self._get_number_styles(console) segments = Segments( - self._get_syntax( - console, options, background_style, number_style, highlight_number_style - ) + self._get_syntax(console, options, self._theme.get_background_style()) ) if self.padding: yield Padding(segments, style=background_style, pad=self.padding) @@ -566,10 +564,11 @@ def _get_syntax( self, console: Console, options: ConsoleOptions, - background_style, - number_style, - highlight_number_style, - ) -> RenderResult: + background_style: Style, + ) -> Iterable[Segment]: + """ + Get the Segments for the Syntax object, excluding any vertical/horizontal padding + """ transparent_background = self._get_base_style().transparent_background code_width = ( ( @@ -673,6 +672,11 @@ def _get_syntax( ] if self.line_numbers: + ( + background_style, + number_style, + highlight_number_style, + ) = self._get_number_styles(console) for first, wrapped_line in loop_first(wrapped_lines): if first: line_column = str(line_no).rjust(numbers_column_width - 2) + " " From 00c787f3d19b32a5746a6cf5186f43d3ad4e7aa1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 16:06:46 +0100 Subject: [PATCH 7/9] Small refactor of Syntax padding --- rich/syntax.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/rich/syntax.py b/rich/syntax.py index d2bcb33a6..b01a0c6d3 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -547,16 +547,11 @@ def __rich_measure__( def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: - ( - background_style, - number_style, - highlight_number_style, - ) = self._get_number_styles(console) - segments = Segments( - self._get_syntax(console, options, self._theme.get_background_style()) - ) + segments = Segments(self._get_syntax(console, options)) if self.padding: - yield Padding(segments, style=background_style, pad=self.padding) + yield Padding( + segments, style=self._theme.get_background_style(), pad=self.padding + ) else: yield segments @@ -564,7 +559,6 @@ def _get_syntax( self, console: Console, options: ConsoleOptions, - background_style: Style, ) -> Iterable[Segment]: """ Get the Segments for the Syntax object, excluding any vertical/horizontal padding @@ -642,13 +636,16 @@ def _get_syntax( highlight_line = self.highlight_lines.__contains__ _Segment = Segment - wrapped_line_left_pad = _Segment( - " " * numbers_column_width + " ", background_style - ) new_line = _Segment("\n") line_pointer = "> " if options.legacy_windows else "❱ " + ( + background_style, + number_style, + highlight_number_style, + ) = self._get_number_styles(console) + for line_no, line in enumerate(lines, self.start_line + line_offset): if self.word_wrap: wrapped_lines = console.render_lines( @@ -672,11 +669,9 @@ def _get_syntax( ] if self.line_numbers: - ( - background_style, - number_style, - highlight_number_style, - ) = self._get_number_styles(console) + wrapped_line_left_pad = _Segment( + " " * numbers_column_width + " ", background_style + ) for first, wrapped_line in loop_first(wrapped_lines): if first: line_column = str(line_no).rjust(numbers_column_width - 2) + " " From 5085c2a5bc454e8382ed691694585179791a1c1b Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 16:14:44 +0100 Subject: [PATCH 8/9] Remove some dead code --- rich/syntax.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rich/syntax.py b/rich/syntax.py index b01a0c6d3..cb34855ac 100644 --- a/rich/syntax.py +++ b/rich/syntax.py @@ -690,14 +690,6 @@ def _get_syntax( yield from wrapped_line yield new_line - def _vertical_padding_segments( - self, pad_amount: int, width: int, style: Style - ) -> Iterable[Segment]: - """Yields Segments for the padding at the top and bottom of the Syntax""" - for _ in range(pad_amount): - yield Segment(" " * width, style) - yield Segment.line() - if __name__ == "__main__": # pragma: no cover From 199a7917a12c9563ec003de783dd3d019676dcfd Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 3 May 2022 16:25:07 +0100 Subject: [PATCH 9/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb997933..7375485d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Change SVG export to create a simpler SVG +- Add `padding` to Syntax constructor https://github.com/Textualize/rich/pull/2247 ## [12.3.0] - 2022-04-26