Skip to content

Commit

Permalink
Add padding support to Syntax (#2247)
Browse files Browse the repository at this point in the history
* Add padding to Syntax constructor

* Add padding to from_path constructor, use correct type

* Padding for `Syntax` - support vertical padding

* Extract logic for create base syntax without padding

* Add test for padded `Syntax`

* Small refactor of Syntax padding

* Small refactor of Syntax padding

* Remove some dead code

* Update changelog
  • Loading branch information
darrenburns authored May 3, 2022
1 parent 5a999f4 commit 79a41db
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Change SVG export to create a simpler SVG
- Fix render_lines crash when render height was negative https://github.com/Textualize/rich/pull/2246

### Added

- Add `padding` to Syntax constructor https://github.com/Textualize/rich/pull/2247

## [12.3.0] - 2022-04-26

### Added
Expand Down
67 changes: 55 additions & 12 deletions rich/syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
from pygments.util import ClassNotFound

from rich.containers import Lines
from rich.padding import Padding, PaddingDimensions

from ._loop import loop_first
from .color import Color, blend_rgb
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

Expand Down Expand Up @@ -100,6 +101,7 @@
}

RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}
NUMBERS_COLUMN_DEFAULT_PADDING = 2


class SyntaxTheme(ABC):
Expand Down Expand Up @@ -209,6 +211,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]
Expand Down Expand Up @@ -242,6 +245,7 @@ def __init__(
word_wrap: bool = False,
background_color: Optional[str] = None,
indent_guides: bool = False,
padding: PaddingDimensions = 0,
) -> None:
self.code = code
self._lexer = lexer
Expand All @@ -258,6 +262,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)

Expand All @@ -278,6 +283,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.
Expand All @@ -296,6 +302,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
Expand All @@ -320,6 +327,7 @@ def from_path(
word_wrap=word_wrap,
background_color=background_color,
indent_guides=indent_guides,
padding=padding,
)

@classmethod
Expand Down Expand Up @@ -498,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]:
Expand Down Expand Up @@ -527,15 +538,31 @@ 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:
segments = Segments(self._get_syntax(console, options))
if self.padding:
yield Padding(
segments, style=self._theme.get_background_style(), pad=self.padding
)
else:
yield segments

def _get_syntax(
self,
console: Console,
options: ConsoleOptions,
) -> Iterable[Segment]:
"""
Get the Segments for the Syntax object, excluding any vertical/horizontal padding
"""
transparent_background = self._get_base_style().transparent_background
code_width = (
(
Expand All @@ -553,12 +580,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")
Expand Down Expand Up @@ -615,11 +636,16 @@ def __rich_console__(

highlight_line = self.highlight_lines.__contains__
_Segment = Segment
padding = _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(
Expand All @@ -628,7 +654,6 @@ def __rich_console__(
style=background_style,
pad=not transparent_background,
)

else:
segments = list(line.render(console, end=""))
if options.no_wrap:
Expand All @@ -642,7 +667,11 @@ def __rich_console__(
pad=not transparent_background,
)
]

if self.line_numbers:
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) + " "
Expand All @@ -653,7 +682,7 @@ 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:
Expand Down Expand Up @@ -739,6 +768,16 @@ def __rich_console__(
dest="lexer_name",
help="Lexer name",
)
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
Expand All @@ -755,6 +794,8 @@ def __rich_console__(
theme=args.theme,
background_color=args.background_color,
indent_guides=args.indent_guides,
padding=args.padding,
highlight_lines={args.highlight_line},
)
else:
syntax = Syntax.from_path(
Expand All @@ -765,5 +806,7 @@ def __rich_console__(
theme=args.theme,
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)
18 changes: 17 additions & 1 deletion tests/test_syntax.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# coding=utf-8

import io
import os
import sys
import tempfile
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 79a41db

Please sign in to comment.