23
23
from pygments .util import ClassNotFound
24
24
25
25
from rich .containers import Lines
26
+ from rich .padding import Padding , PaddingDimensions
26
27
27
28
from ._loop import loop_first
28
29
from .color import Color , blend_rgb
29
30
from .console import Console , ConsoleOptions , JustifyMethod , RenderResult
30
31
from .jupyter import JupyterMixin
31
32
from .measure import Measurement
32
- from .segment import Segment
33
+ from .segment import Segment , Segments
33
34
from .style import Style
34
35
from .text import Text
35
36
100
101
}
101
102
102
103
RICH_SYNTAX_THEMES = {"ansi_light" : ANSI_LIGHT , "ansi_dark" : ANSI_DARK }
104
+ NUMBERS_COLUMN_DEFAULT_PADDING = 2
103
105
104
106
105
107
class SyntaxTheme (ABC ):
@@ -209,6 +211,7 @@ class Syntax(JupyterMixin):
209
211
word_wrap (bool, optional): Enable word wrapping.
210
212
background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
211
213
indent_guides (bool, optional): Show indent guides. Defaults to False.
214
+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
212
215
"""
213
216
214
217
_pygments_style_class : Type [PygmentsStyle ]
@@ -242,6 +245,7 @@ def __init__(
242
245
word_wrap : bool = False ,
243
246
background_color : Optional [str ] = None ,
244
247
indent_guides : bool = False ,
248
+ padding : PaddingDimensions = 0 ,
245
249
) -> None :
246
250
self .code = code
247
251
self ._lexer = lexer
@@ -258,6 +262,7 @@ def __init__(
258
262
Style (bgcolor = background_color ) if background_color else Style ()
259
263
)
260
264
self .indent_guides = indent_guides
265
+ self .padding = padding
261
266
262
267
self ._theme = self .get_theme (theme )
263
268
@@ -278,6 +283,7 @@ def from_path(
278
283
word_wrap : bool = False ,
279
284
background_color : Optional [str ] = None ,
280
285
indent_guides : bool = False ,
286
+ padding : PaddingDimensions = 0 ,
281
287
) -> "Syntax" :
282
288
"""Construct a Syntax object from a file.
283
289
@@ -296,6 +302,7 @@ def from_path(
296
302
word_wrap (bool, optional): Enable word wrapping of code.
297
303
background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
298
304
indent_guides (bool, optional): Show indent guides. Defaults to False.
305
+ padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
299
306
300
307
Returns:
301
308
[Syntax]: A Syntax object that may be printed to the console
@@ -320,6 +327,7 @@ def from_path(
320
327
word_wrap = word_wrap ,
321
328
background_color = background_color ,
322
329
indent_guides = indent_guides ,
330
+ padding = padding ,
323
331
)
324
332
325
333
@classmethod
@@ -498,7 +506,10 @@ def _numbers_column_width(self) -> int:
498
506
"""Get the number of characters used to render the numbers column."""
499
507
column_width = 0
500
508
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
+ )
502
513
return column_width
503
514
504
515
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]:
527
538
def __rich_measure__ (
528
539
self , console : "Console" , options : "ConsoleOptions"
529
540
) -> "Measurement" :
541
+ _ , right , _ , left = Padding .unpack (self .padding )
530
542
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
532
544
return Measurement (self ._numbers_column_width , width )
533
545
return Measurement (self ._numbers_column_width , options .max_width )
534
546
535
547
def __rich_console__ (
536
548
self , console : Console , options : ConsoleOptions
537
549
) -> 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
538
557
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
+ """
539
566
transparent_background = self ._get_base_style ().transparent_background
540
567
code_width = (
541
568
(
@@ -553,12 +580,6 @@ def __rich_console__(
553
580
code = code .expandtabs (self .tab_size )
554
581
text = self .highlight (code , self .line_range )
555
582
556
- (
557
- background_style ,
558
- number_style ,
559
- highlight_number_style ,
560
- ) = self ._get_number_styles (console )
561
-
562
583
if not self .line_numbers and not self .word_wrap and not self .line_range :
563
584
if not ends_on_nl :
564
585
text .remove_suffix ("\n " )
@@ -615,11 +636,16 @@ def __rich_console__(
615
636
616
637
highlight_line = self .highlight_lines .__contains__
617
638
_Segment = Segment
618
- padding = _Segment (" " * numbers_column_width + " " , background_style )
619
639
new_line = _Segment ("\n " )
620
640
621
641
line_pointer = "> " if options .legacy_windows else "❱ "
622
642
643
+ (
644
+ background_style ,
645
+ number_style ,
646
+ highlight_number_style ,
647
+ ) = self ._get_number_styles (console )
648
+
623
649
for line_no , line in enumerate (lines , self .start_line + line_offset ):
624
650
if self .word_wrap :
625
651
wrapped_lines = console .render_lines (
@@ -628,7 +654,6 @@ def __rich_console__(
628
654
style = background_style ,
629
655
pad = not transparent_background ,
630
656
)
631
-
632
657
else :
633
658
segments = list (line .render (console , end = "" ))
634
659
if options .no_wrap :
@@ -642,7 +667,11 @@ def __rich_console__(
642
667
pad = not transparent_background ,
643
668
)
644
669
]
670
+
645
671
if self .line_numbers :
672
+ wrapped_line_left_pad = _Segment (
673
+ " " * numbers_column_width + " " , background_style
674
+ )
646
675
for first , wrapped_line in loop_first (wrapped_lines ):
647
676
if first :
648
677
line_column = str (line_no ).rjust (numbers_column_width - 2 ) + " "
@@ -653,7 +682,7 @@ def __rich_console__(
653
682
yield _Segment (" " , highlight_number_style )
654
683
yield _Segment (line_column , number_style )
655
684
else :
656
- yield padding
685
+ yield wrapped_line_left_pad
657
686
yield from wrapped_line
658
687
yield new_line
659
688
else :
@@ -739,6 +768,16 @@ def __rich_console__(
739
768
dest = "lexer_name" ,
740
769
help = "Lexer name" ,
741
770
)
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
+ )
742
781
args = parser .parse_args ()
743
782
744
783
from rich .console import Console
@@ -755,6 +794,8 @@ def __rich_console__(
755
794
theme = args .theme ,
756
795
background_color = args .background_color ,
757
796
indent_guides = args .indent_guides ,
797
+ padding = args .padding ,
798
+ highlight_lines = {args .highlight_line },
758
799
)
759
800
else :
760
801
syntax = Syntax .from_path (
@@ -765,5 +806,7 @@ def __rich_console__(
765
806
theme = args .theme ,
766
807
background_color = args .background_color ,
767
808
indent_guides = args .indent_guides ,
809
+ padding = args .padding ,
810
+ highlight_lines = {args .highlight_line },
768
811
)
769
812
console .print (syntax , soft_wrap = args .soft_wrap )
0 commit comments