Skip to content

Commit 06e97c3

Browse files
committed
Merge branch 'master' into v12.4.0
2 parents 6d526ac + 857e0a0 commit 06e97c3

File tree

6 files changed

+107
-41
lines changed

6 files changed

+107
-41
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111

1212
- Change SVG export to create a simpler SVG
13+
- Fix render_lines crash when render height was negative https://github.com/Textualize/rich/pull/2246
14+
15+
### Added
16+
17+
- Add `padding` to Syntax constructor https://github.com/Textualize/rich/pull/2247
1318

1419
## [12.3.0] - 2022-04-26
1520

README.md

+8-27
Original file line numberDiff line numberDiff line change
@@ -427,35 +427,16 @@ See also [Rich CLI](https://github.com/textualize/rich-cli) for a command line a
427427

428428
![Rich CLI](https://raw.githubusercontent.com/Textualize/rich-cli/main/imgs/rich-cli-splash.jpg)
429429

430+
# Textual
431+
432+
See also Rich's sister project, [Textual](https://github.com/Textualize/textual), which you can use to build sophisticated User Interfaces in the terminal.
433+
434+
![Textual screenshot](https://raw.githubusercontent.com/Textualize/textual/main/imgs/textual.png)
430435

431436
# Projects using Rich
432437

433-
Here are a few projects using Rich:
434-
435-
- [BrancoLab/BrainRender](https://github.com/BrancoLab/BrainRender)
436-
a python package for the visualization of three dimensional neuro-anatomical data
437-
- [Ciphey/Ciphey](https://github.com/Ciphey/Ciphey)
438-
Automated decryption tool
439-
- [emeryberger/scalene](https://github.com/emeryberger/scalene)
440-
a high-performance, high-precision CPU and memory profiler for Python
441-
- [hedythedev/StarCli](https://github.com/hedythedev/starcli)
442-
Browse GitHub trending projects from your command line
443-
- [intel/cve-bin-tool](https://github.com/intel/cve-bin-tool)
444-
This tool scans for a number of common, vulnerable components (openssl, libpng, libxml2, expat and a few others) to let you know if your system includes common libraries with known vulnerabilities.
445-
- [nf-core/tools](https://github.com/nf-core/tools)
446-
Python package with helper tools for the nf-core community.
447-
- [cansarigol/pdbr](https://github.com/cansarigol/pdbr)
448-
pdb + Rich library for enhanced debugging
449-
- [plant99/felicette](https://github.com/plant99/felicette)
450-
Satellite imagery for dummies.
451-
- [seleniumbase/SeleniumBase](https://github.com/seleniumbase/SeleniumBase)
452-
Automate & test 10x faster with Selenium & pytest. Batteries included.
453-
- [smacke/ffsubsync](https://github.com/smacke/ffsubsync)
454-
Automagically synchronize subtitles with video.
455-
- [tryolabs/norfair](https://github.com/tryolabs/norfair)
456-
Lightweight Python library for adding real-time 2D object tracking to any detector.
457-
- [ansible/ansible-lint](https://github.com/ansible/ansible-lint) Ansible-lint checks playbooks for practices and behaviour that could potentially be improved
458-
- [ansible-community/molecule](https://github.com/ansible-community/molecule) Ansible Molecule testing framework
459-
- +[Many more](https://github.com/willmcgugan/rich/network/dependents)!
438+
For some examples of projects using Rich, see the [Rich Gallery](https://www.textualize.io/rich/gallery) on [Textualize.io](https://www.textualize.io).
439+
440+
Would you like to add your own project to the gallery? You can! Follow [these instructions](https://www.textualize.io/gallery-instructions).
460441

461442
<!-- This is a test, no need to translate -->

rich/console.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,11 @@ def render_lines(
13271327
_rendered = self.render(renderable, render_options)
13281328
if style:
13291329
_rendered = Segment.apply_style(_rendered, style)
1330+
1331+
render_height = render_options.height
1332+
if render_height is not None:
1333+
render_height = max(0, render_height)
1334+
13301335
lines = list(
13311336
islice(
13321337
Segment.split_and_crop_lines(
@@ -1336,7 +1341,7 @@ def render_lines(
13361341
pad=pad,
13371342
),
13381343
None,
1339-
render_options.height,
1344+
render_height,
13401345
)
13411346
)
13421347
if render_options.height is not None:

rich/syntax.py

+55-12
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323
from pygments.util import ClassNotFound
2424

2525
from rich.containers import Lines
26+
from rich.padding import Padding, PaddingDimensions
2627

2728
from ._loop import loop_first
2829
from .color import Color, blend_rgb
2930
from .console import Console, ConsoleOptions, JustifyMethod, RenderResult
3031
from .jupyter import JupyterMixin
3132
from .measure import Measurement
32-
from .segment import Segment
33+
from .segment import Segment, Segments
3334
from .style import Style
3435
from .text import Text
3536

@@ -100,6 +101,7 @@
100101
}
101102

102103
RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}
104+
NUMBERS_COLUMN_DEFAULT_PADDING = 2
103105

104106

105107
class 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)

tests/test_console.py

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
)
2121
from rich.control import Control
2222
from rich.measure import measure_renderables
23+
from rich.padding import Padding
2324
from rich.pager import SystemPager
2425
from rich.panel import Panel
2526
from rich.region import Region
@@ -862,3 +863,18 @@ def __rich_console__(self, console, options):
862863
expected = "╭──────────────────╮\n│ ╭──────────────╮ │\n│ │ foo │ │\n│ ╰──────────────╯ │\n│ ╭──────────────╮ │\n│ │ bar │ │\n│ ╰──────────────╯ │\n│ │\n│ │\n│ │\n│ │\n╰──────────────────╯\n"
863864

864865
assert result == expected
866+
867+
868+
def test_render_lines_height_minus_vertical_pad_is_negative():
869+
# https://github.com/Textualize/textual/issues/389
870+
console = Console(
871+
force_terminal=True,
872+
color_system="truecolor",
873+
width=20,
874+
height=40,
875+
legacy_windows=False,
876+
)
877+
options = console.options.update_height(1)
878+
879+
# Ensuring that no exception is raised...
880+
console.render_lines(Padding("hello", pad=(1, 0)), options=options)

tests/test_syntax.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# coding=utf-8
2-
2+
import io
33
import os
44
import sys
55
import tempfile
@@ -303,6 +303,22 @@ def test_syntax_guess_lexer():
303303
assert Syntax.guess_lexer("banana.html", "{{something|filter:3}}") == "html+django"
304304

305305

306+
def test_syntax_padding():
307+
syntax = Syntax("x = 1", lexer="python", padding=(1, 3))
308+
console = Console(
309+
width=20,
310+
file=io.StringIO(),
311+
color_system="truecolor",
312+
legacy_windows=False,
313+
record=True,
314+
)
315+
console.print(syntax)
316+
output = console.export_text()
317+
assert (
318+
output == " \n x = 1 \n \n"
319+
)
320+
321+
306322
if __name__ == "__main__":
307323
syntax = Panel.fit(
308324
Syntax(

0 commit comments

Comments
 (0)