mirror of https://github.com/Textualize/rich.git
Add padding support to `Syntax` (#2247)
* 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
This commit is contained in:
parent
5a999f49f9
commit
79a41db38f
|
@ -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
|
||||
|
|
|
@ -23,13 +23,14 @@ from pygments.token import (
|
|||
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
|
||||
|
||||
|
@ -100,6 +101,7 @@ ANSI_DARK: Dict[TokenType, Style] = {
|
|||
}
|
||||
|
||||
RICH_SYNTAX_THEMES = {"ansi_light": ANSI_LIGHT, "ansi_dark": ANSI_DARK}
|
||||
NUMBERS_COLUMN_DEFAULT_PADDING = 2
|
||||
|
||||
|
||||
class SyntaxTheme(ABC):
|
||||
|
@ -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]
|
||||
|
@ -242,6 +245,7 @@ class Syntax(JupyterMixin):
|
|||
word_wrap: bool = False,
|
||||
background_color: Optional[str] = None,
|
||||
indent_guides: bool = False,
|
||||
padding: PaddingDimensions = 0,
|
||||
) -> None:
|
||||
self.code = code
|
||||
self._lexer = lexer
|
||||
|
@ -258,6 +262,7 @@ class Syntax(JupyterMixin):
|
|||
Style(bgcolor=background_color) if background_color else Style()
|
||||
)
|
||||
self.indent_guides = indent_guides
|
||||
self.padding = padding
|
||||
|
||||
self._theme = self.get_theme(theme)
|
||||
|
||||
|
@ -278,6 +283,7 @@ class Syntax(JupyterMixin):
|
|||
word_wrap: bool = False,
|
||||
background_color: Optional[str] = None,
|
||||
indent_guides: bool = False,
|
||||
padding: PaddingDimensions = 0,
|
||||
) -> "Syntax":
|
||||
"""Construct a Syntax object from a file.
|
||||
|
||||
|
@ -296,6 +302,7 @@ class Syntax(JupyterMixin):
|
|||
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
|
||||
|
@ -320,6 +327,7 @@ class Syntax(JupyterMixin):
|
|||
word_wrap=word_wrap,
|
||||
background_color=background_color,
|
||||
indent_guides=indent_guides,
|
||||
padding=padding,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -498,7 +506,10 @@ class Syntax(JupyterMixin):
|
|||
"""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]:
|
||||
|
@ -527,15 +538,31 @@ class Syntax(JupyterMixin):
|
|||
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 = (
|
||||
(
|
||||
|
@ -553,12 +580,6 @@ class Syntax(JupyterMixin):
|
|||
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")
|
||||
|
@ -615,11 +636,16 @@ class Syntax(JupyterMixin):
|
|||
|
||||
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(
|
||||
|
@ -628,7 +654,6 @@ class Syntax(JupyterMixin):
|
|||
style=background_style,
|
||||
pad=not transparent_background,
|
||||
)
|
||||
|
||||
else:
|
||||
segments = list(line.render(console, end=""))
|
||||
if options.no_wrap:
|
||||
|
@ -642,7 +667,11 @@ class Syntax(JupyterMixin):
|
|||
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) + " "
|
||||
|
@ -653,7 +682,7 @@ class Syntax(JupyterMixin):
|
|||
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:
|
||||
|
@ -739,6 +768,16 @@ if __name__ == "__main__": # pragma: no cover
|
|||
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
|
||||
|
@ -755,6 +794,8 @@ if __name__ == "__main__": # pragma: no cover
|
|||
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(
|
||||
|
@ -765,5 +806,7 @@ if __name__ == "__main__": # pragma: no cover
|
|||
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)
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue