Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lexer parameter to Syntax #1748

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 11 additions & 4 deletions rich/syntax.py
Expand Up @@ -3,8 +3,9 @@
from rich.containers import Lines
import textwrap
from abc import ABC, abstractmethod
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union, cast

from pygments.lexer import Lexer
from pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pygments.style import Style as PygmentsStyle
from pygments.styles import get_style_by_name
Expand Down Expand Up @@ -195,6 +196,7 @@ class Syntax(JupyterMixin):
Args:
code (str): Code to highlight.
lexer_name (str): Lexer to use (see https://pygments.org/docs/lexers/)
lexer (Lexer): Lexer instance to use (see https://pygments.org/docs/lexers/)
theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
Expand Down Expand Up @@ -226,8 +228,9 @@ def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:
def __init__(
self,
code: str,
lexer_name: str,
lexer_name: Optional[str] = None,
*,
lexer: Optional[Lexer] = None,
theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
dedent: bool = False,
line_numbers: bool = False,
Expand All @@ -240,8 +243,12 @@ def __init__(
background_color: Optional[str] = None,
indent_guides: bool = False,
) -> None:
if all((lexer_name, lexer)):
patrick91 marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("Cannot pass both lexer_name and lexer")

self.code = code
self.lexer_name = lexer_name
self.lexer = lexer
self.dedent = dedent
self.line_numbers = line_numbers
self.start_line = start_line
Expand Down Expand Up @@ -374,8 +381,8 @@ def highlight(
)
_get_theme_style = self._theme.get_style_for_token
try:
lexer = get_lexer_by_name(
self.lexer_name,
lexer = self.lexer or get_lexer_by_name(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking this in to an if rather than an expression would allow test coverage to span both branches, and avoid that cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda prefer the previous version 馃槉 ideally the if would live outside the try/except, but there's logic using the lexer inside the else statement, so I've kept it there. We still need a cast (or an asser as I've done now) in this case :)

cast(str, self.lexer_name),
stripnl=False,
ensurenl=True,
tabsize=self.tab_size,
Expand Down
30 changes: 30 additions & 0 deletions tests/test_syntax.py
Expand Up @@ -10,6 +10,8 @@
from rich.style import Style
from rich.syntax import Syntax, ANSISyntaxTheme, PygmentsSyntaxTheme, Color, Console

from pygments.lexers import PythonLexer


CODE = '''\
def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
Expand All @@ -27,6 +29,19 @@ def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
yield first, True, previous_value'''


def test_cannot_pass_both_lexer_name_and_lexer():
code = "\n\nimport this\n\n"
with pytest.raises(ValueError, match="Cannot pass both lexer_name and lexer"):
Syntax(
code,
lexer_name="python",
lexer=PythonLexer,
theme="ascii_light",
code_width=30,
line_numbers=True,
)


def test_blank_lines():
code = "\n\nimport this\n\n"
syntax = Syntax(
Expand Down Expand Up @@ -74,6 +89,21 @@ def test_python_render_simple():
assert rendered_syntax == expected


def test_python_render_simple_passing_lexer_instance():
syntax = Syntax(
CODE,
lexer=PythonLexer(),
line_numbers=False,
theme="foo",
code_width=60,
word_wrap=False,
)
rendered_syntax = render(syntax)
print(repr(rendered_syntax))
patrick91 marked this conversation as resolved.
Show resolved Hide resolved
expected = '\x1b[1;38;2;0;128;0;48;2;248;248;248mdef\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;255;48;2;248;248;248mloop_first_last\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mIterable\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mT\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m]\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m-\x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m>\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mIterable\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mTuple\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m[\x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mb\x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[3;38;2;186;33;33;48;2;248;248;248m"""Iterate and generate a tuple with a flag for first an\x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248miter\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalues\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mtry\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;128;0;48;2;248;248;248mnext\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m(\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m)\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mexcept\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;210;65;58;48;2;248;248;248mStopIteration\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mreturn\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mfor\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;170;34;255;48;2;248;248;248min\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248miter_values\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m:\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mFalse\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;102;102;102;48;2;248;248;248m=\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mvalue\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248myield\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mfirst\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[1;38;2;0;128;0;48;2;248;248;248mTrue\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m,\x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248m \x1b[0m\x1b[38;2;0;0;0;48;2;248;248;248mprevious_value\x1b[0m\x1b[48;2;248;248;248m \x1b[0m\n'
assert rendered_syntax == expected


def test_python_render_simple_indent_guides():
syntax = Syntax(
CODE,
Expand Down