6from abc
import ABC, abstractmethod
7from pathlib
import Path
43from ._loop
import loop_first
44from .cells
import cell_len
45from .color
import Color, blend_rgb
46from .console
import Console, ConsoleOptions, JustifyMethod, RenderResult
47from .jupyter
import JupyterMixin
48from .measure
import Measurement
49from .segment
import Segment, Segments
50from .style
import Style, StyleType
53TokenType = Tuple[str, ...]
56DEFAULT_THEME =
"monokai"
61ANSI_LIGHT: Dict[TokenType, Style] = {
63 Whitespace:
Style(color=
"white"),
64 Comment:
Style(dim=
True),
66 Keyword:
Style(color=
"blue"),
79 String:
Style(color=
"yellow"),
80 Number:
Style(color=
"blue"),
87 Error:
Style(color=
"red", underline=
True),
90ANSI_DARK: Dict[TokenType, Style] = {
92 Whitespace:
Style(color=
"bright_black"),
93 Comment:
Style(dim=
True),
95 Keyword:
Style(color=
"bright_blue"),
108 String:
Style(color=
"yellow"),
109 Number:
Style(color=
"bright_blue"),
116 Error:
Style(color=
"red", underline=
True),
119RICH_SYNTAX_THEMES = {
"ansi_light": ANSI_LIGHT,
"ansi_dark": ANSI_DARK}
120NUMBERS_COLUMN_DEFAULT_PADDING = 2
124 """Base class for a syntax theme."""
128 """Get a style for a given Pygments token."""
129 raise NotImplementedError
133 """Get the background color."""
134 raise NotImplementedError
138 """Syntax theme that delegates to Pygments theme."""
140 def __init__(self, theme: Union[str, Type[PygmentsStyle]]) ->
None:
141 self._style_cache: Dict[TokenType, Style] = {}
145 except ClassNotFound:
154 """Get a style from a Pygments class."""
156 return self._style_cache[token_type]
163 color = pygments_style[
"color"]
164 bgcolor = pygments_style[
"bgcolor"]
166 color=
"#" + color
if color
else "#000000",
168 bold=pygments_style[
"bold"],
169 italic=pygments_style[
"italic"],
170 underline=pygments_style[
"underline"],
172 self._style_cache[token_type] = style
180 """Syntax theme to use standard colors."""
182 def __init__(self, style_map: Dict[TokenType, Style]) ->
None:
186 self._style_cache: Dict[TokenType, Style] = {}
189 """Look up style in the style map."""
191 return self._style_cache[token_type]
197 token = tuple(token_type)
200 _style = get_style(token)
201 if _style
is not None:
205 self._style_cache[token_type] = style
212SyntaxPosition = Tuple[int, int]
217 A range to highlight in a Syntax object.
218 `start` and `end` are 2-integers tuples, where the first integer is the line number
219 (starting from 1) and the second integer is the column index (starting from 0).
223 start: SyntaxPosition
228 """Construct a Syntax object to render syntax highlighted code.
231 code (str): Code to highlight.
232 lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/)
233 theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".
234 dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
235 line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
236 start_line (int, optional): Starting number for line numbers. Defaults to 1.
237 line_range (Tuple[int | None, int | None], optional): If given should be a tuple of the start and end line to render.
238 A value of None in the tuple indicates the range is open in that direction.
239 highlight_lines (Set[int]): A set of line numbers to highlight.
240 code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
241 tab_size (int, optional): Size of tabs. Defaults to 4.
242 word_wrap (bool, optional): Enable word wrapping.
243 background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
244 indent_guides (bool, optional): Show indent guides. Defaults to False.
245 padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
248 _pygments_style_class: Type[PygmentsStyle]
252 def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:
253 """Get a syntax theme instance."""
257 if name
in RICH_SYNTAX_THEMES:
266 lexer: Union[Lexer, str],
268 theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
269 dedent: bool =
False,
270 line_numbers: bool =
False,
272 line_range: Optional[Tuple[Optional[int], Optional[int]]] =
None,
273 highlight_lines: Optional[Set[int]] =
None,
274 code_width: Optional[int] =
None,
276 word_wrap: bool =
False,
277 background_color: Optional[str] =
None,
278 indent_guides: bool =
False,
279 padding: PaddingDimensions = 0,
293 Style(bgcolor=background_color)
if background_color
else Style()
299 self._stylized_ranges: List[_SyntaxHighlightRange] = []
305 encoding: str =
"utf-8",
306 lexer: Optional[Union[Lexer, str]] =
None,
307 theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
308 dedent: bool =
False,
309 line_numbers: bool =
False,
310 line_range: Optional[Tuple[int, int]] =
None,
312 highlight_lines: Optional[Set[int]] =
None,
313 code_width: Optional[int] =
None,
315 word_wrap: bool =
False,
316 background_color: Optional[str] =
None,
317 indent_guides: bool =
False,
318 padding: PaddingDimensions = 0,
320 """Construct a Syntax object from a file.
323 path (str): Path to file to highlight.
324 encoding (str): Encoding of file.
325 lexer (str | Lexer, optional): Lexer to use. If None, lexer will be auto-detected from path/file content.
326 theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "emacs".
327 dedent (bool, optional): Enable stripping of initial whitespace. Defaults to True.
328 line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
329 start_line (int, optional): Starting number for line numbers. Defaults to 1.
330 line_range (Tuple[int, int], optional): If given should be a tuple of the start and end line to render.
331 highlight_lines (Set[int]): A set of line numbers to highlight.
332 code_width: Width of code to render (not including line numbers), or ``None`` to use all available width.
333 tab_size (int, optional): Size of tabs. Defaults to 4.
334 word_wrap (bool, optional): Enable word wrapping of code.
335 background_color (str, optional): Optional background color, or None to use theme color. Defaults to None.
336 indent_guides (bool, optional): Show indent guides. Defaults to False.
337 padding (PaddingDimensions): Padding to apply around the syntax. Defaults to 0 (no padding).
340 [Syntax]: A Syntax object that may be printed to the console
342 code = Path(path).read_text(encoding=encoding)
352 line_numbers=line_numbers,
353 line_range=line_range,
354 start_line=start_line,
355 highlight_lines=highlight_lines,
356 code_width=code_width,
359 background_color=background_color,
360 indent_guides=indent_guides,
365 def guess_lexer(cls, path: str, code: Optional[str] =
None) -> str:
366 """Guess the alias of the Pygments lexer to use based on a path and an optional string of code.
367 If code is supplied, it will use a combination of the code and the filename to determine the
368 best lexer to use. For example, if the file is ``index.html`` and the file contains Django
369 templating syntax, then "html+django" will be returned. If the file is ``index.html``, and no
370 templating language is used, the "html" lexer will be used. If no string of code
371 is supplied, the lexer will be chosen based on the file extension..
374 path (AnyStr): The path to the file containing the code you wish to know the lexer for.
375 code (str, optional): Optional string of code that will be used as a fallback if no lexer
376 is found for the supplied path.
379 str: The name of the Pygments lexer that best matches the supplied path/code.
381 lexer: Optional[Lexer] =
None
382 lexer_name =
"default"
385 lexer = guess_lexer_for_filename(path, code)
386 except ClassNotFound:
394 lexer = get_lexer_by_name(extension)
395 except ClassNotFound:
407 """Get the base style."""
412 """Get a color (if any) for the given token.
415 token_type (TokenType): A token type tuple from Pygments.
418 Optional[Color]: Color from theme, or None for no color.
420 style = self.
_theme_theme.get_style_for_token(token_type)
425 """The lexer for this syntax, or None if no lexer was found.
427 Tries to find the lexer by name if a string was passed to the constructor.
433 return get_lexer_by_name(
439 except ClassNotFound:
445 line_range: Optional[Tuple[Optional[int], Optional[int]]] =
None,
447 """Highlight code and return a Text instance.
450 code (str): Code to highlight.
451 line_range(Tuple[int, int], optional): Optional line range to highlight.
454 Text: A text instance containing highlighted syntax.
458 justify: JustifyMethod = (
468 _get_theme_style = self.
_theme_theme.get_style_for_token
478 line_start, line_end = line_range
481 """Split tokens to one per line."""
487 yield token_type, line_token + new_line
490 """Convert tokens to spans."""
493 _line_start = line_start - 1
if line_start
else 0
496 while line_no < _line_start:
498 _token_type, token = next(tokens)
499 except StopIteration:
505 for token_type, token
in tokens:
509 if line_end
and line_no >= line_end:
522 if self._stylized_ranges:
528 self, style: StyleType, start: SyntaxPosition, end: SyntaxPosition
531 Adds a custom style on a part of the code, that will be applied to the syntax display when it's rendered.
532 Line numbers are 1-based, while column indexes are 0-based.
535 style (StyleType): The style to apply.
536 start (Tuple[int, int]): The start of the range, in the form `[line number, column index]`.
537 end (Tuple[int, int]): The end of the range, in the form `[line number, column index]`.
549 new_color = blend_rgb(
558 """Get the number of characters used to render the numbers column."""
563 + NUMBERS_COLUMN_DEFAULT_PADDING
568 """Get background, number, and highlight styles for line numbers."""
586 number_style = background_style +
Style(dim=
True)
587 highlight_number_style = background_style +
Style(dim=
False)
588 return background_style, number_style, highlight_number_style
591 self, console:
"Console", options:
"ConsoleOptions"
594 padding = left + right
602 + (max(cell_len(line)
for line
in lines)
if lines
else 0)
609 self, console: Console, options: ConsoleOptions
622 options: ConsoleOptions,
623 ) -> Iterable[Segment]:
625 Get the Segments for the Syntax object, excluding any vertical/horizontal padding
666 for syntax_line
in syntax_lines:
667 yield from syntax_line
670 start_line, end_line = self.
line_range or (
None,
None)
673 line_offset = max(0, start_line - 1)
674 lines: Union[List[Text], Lines] =
text.split(
"\n", allow_blank=ends_on_nl)
676 if line_offset >
len(lines):
678 lines = lines[line_offset:end_line]
690 .with_indent_guides(self.
tab_size, style=style +
Style(italic=
False))
691 .split(
"\n", allow_blank=
True)
706 highlight_number_style,
714 style=background_style,
715 pad=
not transparent_background,
720 wrapped_lines = [segments]
726 style=background_style,
727 pad=
not transparent_background,
733 " " * numbers_column_width +
" ", background_style
735 for first, wrapped_line
in loop_first(wrapped_lines):
737 line_column = str(line_no).
rjust(numbers_column_width - 2) +
" "
740 yield _Segment(line_column, highlight_number_style)
742 yield _Segment(
" ", highlight_number_style)
743 yield _Segment(line_column, number_style)
745 yield wrapped_line_left_pad
746 yield from wrapped_line
749 for wrapped_line
in wrapped_lines:
750 yield from wrapped_line
755 Apply stylized ranges to a text instance,
756 using the given code to determine the right portion to apply the style to.
759 text (Text): Text instance to apply the style to.
773 for stylized_range
in self._stylized_ranges:
780 if start
is not None and end
is not None:
785 Applies various processing to a raw code string
786 (normalises it so it always ends with a line return, dedents it if necessary, etc.)
789 code (str): The raw code string to process
792 Tuple[bool, str]: the boolean indicates whether the raw code ends with a line return,
793 while the string is the processed code.
796 processed_code = code
if ends_on_nl
else code +
"\n"
801 return ends_on_nl, processed_code
805 newlines_offsets: Sequence[int], position: SyntaxPosition
808 Returns the index of the code string for the given positions.
811 newlines_offsets (Sequence[int]): The offset of each newline character found in the code snippet.
812 position (SyntaxPosition): The position to search for.
815 Optional[int]: The index of the code string for this position, or `None`
816 if the given position's line number is out of range (if it's the column that is out of range
817 we silently clamp its value so that it reaches the end of the line)
819 lines_count =
len(newlines_offsets)
821 line_number, column_index = position
822 if line_number > lines_count
or len(newlines_offsets) < (line_number + 1):
824 line_index = line_number - 1
825 line_length = newlines_offsets[line_index + 1] - newlines_offsets[line_index] - 1
827 column_index = min(line_length, column_index)
828 return newlines_offsets[line_index] + column_index
831if __name__ ==
"__main__":
836 description=
"Render syntax to the console with Rich"
841 help=
"path to file, or - for stdin",
849 help=
"force color for non-terminals",
854 dest=
"indent_guides",
857 help=
"display indent guides",
864 help=
"render line numbers",
872 help=
"width of output (default will auto-detect)",
880 help=
"word wrap long lines",
888 help=
"enable soft wrapping mode",
891 "-t",
"--theme", dest=
"theme", default=
"monokai", help=
"pygments theme"
895 "--background-color",
896 dest=
"background_color",
898 help=
"Override background color",
908 "-p",
"--padding", type=int, default=0, dest=
"padding", help=
"Padding"
914 dest=
"highlight_line",
915 help=
"The line number (not index!) to highlight",
Style get_background_style(self)
None __init__(self, Dict[TokenType, Style] style_map)
Style get_style_for_token(self, TokenType token_type)
Style get_background_style(self)
Style get_style_for_token(self, TokenType token_type)
None __init__(self, Union[str, Type[PygmentsStyle]] theme)
Style get_background_style(self)
Style get_style_for_token(self, TokenType token_type)
Style _get_base_style(self)
Optional[Lexer] lexer(self)
None __init__(self, str code, Union[Lexer, str] lexer, *Union[str, SyntaxTheme] theme=DEFAULT_THEME, bool dedent=False, bool line_numbers=False, int start_line=1, Optional[Tuple[Optional[int], Optional[int]]] line_range=None, Optional[Set[int]] highlight_lines=None, Optional[int] code_width=None, int tab_size=4, bool word_wrap=False, Optional[str] background_color=None, bool indent_guides=False, PaddingDimensions padding=0)
"Measurement" __rich_measure__(self, "Console" console, "ConsoleOptions" options)
str guess_lexer(cls, str path, Optional[str] code=None)
Optional[Color] _get_token_color(self, TokenType token_type)
int _numbers_column_width(self)
RenderResult __rich_console__(self, Console console, ConsoleOptions options)
Text highlight(self, str code, Optional[Tuple[Optional[int], Optional[int]]] line_range=None)
SyntaxTheme get_theme(cls, Union[str, SyntaxTheme] name)
Tuple[Style, Style, Style] _get_number_styles(self, Console console)
None _apply_stylized_ranges(self, Text text)
None stylize_range(self, StyleType style, SyntaxPosition start, SyntaxPosition end)
Iterable[Segment] _get_syntax(self, Console console, ConsoleOptions options)
Color _get_line_numbers_color(self, float blend=0.3)
"Syntax" from_path(cls, str path, str encoding="utf-8", Optional[Union[Lexer, str]] lexer=None, Union[str, SyntaxTheme] theme=DEFAULT_THEME, bool dedent=False, bool line_numbers=False, Optional[Tuple[int, int]] line_range=None, int start_line=1, Optional[Set[int]] highlight_lines=None, Optional[int] code_width=None, int tab_size=4, bool word_wrap=False, Optional[str] background_color=None, bool indent_guides=False, PaddingDimensions padding=0)
Tuple[bool, str] _process_code(self, str code)
Optional[int] _get_code_index_for_syntax_position(Sequence[int] newlines_offsets, SyntaxPosition position)