2323from pygments .util import ClassNotFound
2424
2525from rich .containers import Lines
26+ from rich .padding import Padding , PaddingDimensions
2627
2728from ._loop import loop_first
2829from .color import Color , blend_rgb
2930from .console import Console , ConsoleOptions , JustifyMethod , RenderResult
3031from .jupyter import JupyterMixin
3132from .measure import Measurement
32- from .segment import Segment
33+ from .segment import Segment , Segments
3334from .style import Style
3435from .text import Text
3536
100101}
101102
102103RICH_SYNTAX_THEMES = {"ansi_light" : ANSI_LIGHT , "ansi_dark" : ANSI_DARK }
104+ NUMBERS_COLUMN_DEFAULT_PADDING = 2
103105
104106
105107class SyntaxTheme (ABC ):
@@ -209,6 +211,7 @@ class Syntax(JupyterMixin):
209211 word_wrap (bool, optional): Enable word wrapping.
210212 background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
211213 indent_guides (bool, optional): Show indent guides. Defaults to False.
214+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
212215 """
213216
214217 _pygments_style_class : Type [PygmentsStyle ]
@@ -242,6 +245,7 @@ def __init__(
242245 word_wrap : bool = False ,
243246 background_color : Optional [str ] = None ,
244247 indent_guides : bool = False ,
248+ padding : PaddingDimensions = 0 ,
245249 ) -> None :
246250 self .code = code
247251 self ._lexer = lexer
@@ -258,6 +262,7 @@ def __init__(
258262 Style (bgcolor = background_color ) if background_color else Style ()
259263 )
260264 self .indent_guides = indent_guides
265+ self .padding = padding
261266
262267 self ._theme = self .get_theme (theme )
263268
@@ -278,6 +283,7 @@ def from_path(
278283 word_wrap : bool = False ,
279284 background_color : Optional [str ] = None ,
280285 indent_guides : bool = False ,
286+ padding : PaddingDimensions = 0 ,
281287 ) -> "Syntax" :
282288 """Construct a Syntax object from a file.
283289
@@ -296,6 +302,7 @@ def from_path(
296302 word_wrap (bool, optional): Enable word wrapping of code.
297303 background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
298304 indent_guides (bool, optional): Show indent guides. Defaults to False.
305+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
299306
300307 Returns:
301308 [Syntax]: A Syntax object that may be printed to the console
@@ -320,6 +327,7 @@ def from_path(
320327 word_wrap = word_wrap ,
321328 background_color = background_color ,
322329 indent_guides = indent_guides ,
330+ padding = padding ,
323331 )
324332
325333 @classmethod
@@ -498,7 +506,10 @@ def _numbers_column_width(self) -> int:
498506 """Get the number of characters used to render the numbers column."""
499507 column_width = 0
500508 if self .line_numbers :
501- column_width = len (str (self .start_line + self .code .count ("\n " ))) + 2
509+ column_width = (
510+ len (str (self .start_line + self .code .count ("\n " )))
511+ + NUMBERS_COLUMN_DEFAULT_PADDING
512+ )
502513 return column_width
503514
504515 def _get_number_styles (self , console : Console ) -> Tuple [Style , Style , Style ]:
@@ -527,15 +538,31 @@ def _get_number_styles(self, console: Console) -> Tuple[Style, Style, Style]:
527538 def __rich_measure__ (
528539 self , console : "Console" , options : "ConsoleOptions"
529540 ) -> "Measurement" :
541+ _ , right , _ , left = Padding .unpack (self .padding )
530542 if self .code_width is not None :
531- width = self .code_width + self ._numbers_column_width
543+ width = self .code_width + self ._numbers_column_width + right + left
532544 return Measurement (self ._numbers_column_width , width )
533545 return Measurement (self ._numbers_column_width , options .max_width )
534546
535547 def __rich_console__ (
536548 self , console : Console , options : ConsoleOptions
537549 ) -> RenderResult :
550+ segments = Segments (self ._get_syntax (console , options ))
551+ if self .padding :
552+ yield Padding (
553+ segments , style = self ._theme .get_background_style (), pad = self .padding
554+ )
555+ else :
556+ yield segments
538557
558+ def _get_syntax (
559+ self ,
560+ console : Console ,
561+ options : ConsoleOptions ,
562+ ) -> Iterable [Segment ]:
563+ """
564+ Get the Segments for the Syntax object, excluding any vertical/horizontal padding
565+ """
539566 transparent_background = self ._get_base_style ().transparent_background
540567 code_width = (
541568 (
@@ -553,12 +580,6 @@ def __rich_console__(
553580 code = code .expandtabs (self .tab_size )
554581 text = self .highlight (code , self .line_range )
555582
556- (
557- background_style ,
558- number_style ,
559- highlight_number_style ,
560- ) = self ._get_number_styles (console )
561-
562583 if not self .line_numbers and not self .word_wrap and not self .line_range :
563584 if not ends_on_nl :
564585 text .remove_suffix ("\n " )
@@ -615,11 +636,16 @@ def __rich_console__(
615636
616637 highlight_line = self .highlight_lines .__contains__
617638 _Segment = Segment
618- padding = _Segment (" " * numbers_column_width + " " , background_style )
619639 new_line = _Segment ("\n " )
620640
621641 line_pointer = "> " if options .legacy_windows else "❱ "
622642
643+ (
644+ background_style ,
645+ number_style ,
646+ highlight_number_style ,
647+ ) = self ._get_number_styles (console )
648+
623649 for line_no , line in enumerate (lines , self .start_line + line_offset ):
624650 if self .word_wrap :
625651 wrapped_lines = console .render_lines (
@@ -628,7 +654,6 @@ def __rich_console__(
628654 style = background_style ,
629655 pad = not transparent_background ,
630656 )
631-
632657 else :
633658 segments = list (line .render (console , end = "" ))
634659 if options .no_wrap :
@@ -642,7 +667,11 @@ def __rich_console__(
642667 pad = not transparent_background ,
643668 )
644669 ]
670+
645671 if self .line_numbers :
672+ wrapped_line_left_pad = _Segment (
673+ " " * numbers_column_width + " " , background_style
674+ )
646675 for first , wrapped_line in loop_first (wrapped_lines ):
647676 if first :
648677 line_column = str (line_no ).rjust (numbers_column_width - 2 ) + " "
@@ -653,7 +682,7 @@ def __rich_console__(
653682 yield _Segment (" " , highlight_number_style )
654683 yield _Segment (line_column , number_style )
655684 else :
656- yield padding
685+ yield wrapped_line_left_pad
657686 yield from wrapped_line
658687 yield new_line
659688 else :
@@ -739,6 +768,16 @@ def __rich_console__(
739768 dest = "lexer_name" ,
740769 help = "Lexer name" ,
741770 )
771+ parser .add_argument (
772+ "-p" , "--padding" , type = int , default = 0 , dest = "padding" , help = "Padding"
773+ )
774+ parser .add_argument (
775+ "--highlight-line" ,
776+ type = int ,
777+ default = None ,
778+ dest = "highlight_line" ,
779+ help = "The line number (not index!) to highlight" ,
780+ )
742781 args = parser .parse_args ()
743782
744783 from rich .console import Console
@@ -755,6 +794,8 @@ def __rich_console__(
755794 theme = args .theme ,
756795 background_color = args .background_color ,
757796 indent_guides = args .indent_guides ,
797+ padding = args .padding ,
798+ highlight_lines = {args .highlight_line },
758799 )
759800 else :
760801 syntax = Syntax .from_path (
@@ -765,5 +806,7 @@ def __rich_console__(
765806 theme = args .theme ,
766807 background_color = args .background_color ,
767808 indent_guides = args .indent_guides ,
809+ padding = args .padding ,
810+ highlight_lines = {args .highlight_line },
768811 )
769812 console .print (syntax , soft_wrap = args .soft_wrap )
0 commit comments