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 7 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
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Expand Up @@ -25,3 +25,4 @@ The following people have contributed to the development of Rich:
- [Tim Savage](https://github.com/timsavage)
- [Nicolas Simonds](https://github.com/0xDEC0DE)
- [Gabriele N. Tornetta](https://github.com/p403n1x87)
- [Patrick Arminio](https://github.com/patrick91)
33 changes: 22 additions & 11 deletions rich/syntax.py
Expand Up @@ -5,6 +5,7 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union

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 @@ -194,7 +195,7 @@ class Syntax(JupyterMixin):

Args:
code (str): Code to highlight.
lexer_name (str): Lexer to use (see https://pygments.org/docs/lexers/)
lexer (Lexer | str): Lexer 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,7 +227,7 @@ def get_theme(cls, name: Union[str, SyntaxTheme]) -> SyntaxTheme:
def __init__(
self,
code: str,
lexer_name: str,
lexer: Union[Lexer, str],
*,
theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
dedent: bool = False,
Expand All @@ -241,7 +242,7 @@ def __init__(
indent_guides: bool = False,
) -> None:
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 @@ -348,6 +349,20 @@ def _get_token_color(self, token_type: TokenType) -> Optional[Color]:
style = self._theme.get_style_for_token(token_type)
return style.color

@property
def lexer(self) -> Optional[Lexer]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Docstring please!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, hope it is good!

if isinstance(self._lexer, Lexer):
return self._lexer
try:
return get_lexer_by_name(
self._lexer,
stripnl=False,
ensurenl=True,
tabsize=self.tab_size,
)
except ClassNotFound:
return None

def highlight(
self, code: str, line_range: Optional[Tuple[int, int]] = None
) -> Text:
Expand All @@ -373,14 +388,10 @@ def highlight(
no_wrap=not self.word_wrap,
)
_get_theme_style = self._theme.get_style_for_token
try:
lexer = get_lexer_by_name(
self.lexer_name,
stripnl=False,
ensurenl=True,
tabsize=self.tab_size,
)
except ClassNotFound:

lexer = self.lexer

if lexer is None:
text.append(code)
else:
if line_range:
Expand Down
44 changes: 31 additions & 13 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 @@ -30,7 +32,7 @@ def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]:
def test_blank_lines():
code = "\n\nimport this\n\n"
syntax = Syntax(
code, lexer_name="python", theme="ascii_light", code_width=30, line_numbers=True
code, lexer="python", theme="ascii_light", code_width=30, line_numbers=True
)
result = render(syntax)
print(repr(result))
Expand All @@ -44,7 +46,7 @@ def test_python_render():
syntax = Panel.fit(
Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme="foo",
Expand All @@ -62,7 +64,22 @@ def test_python_render():
def test_python_render_simple():
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=False,
theme="foo",
code_width=60,
word_wrap=False,
)
rendered_syntax = render(syntax)
print(repr(rendered_syntax))
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_passing_lexer_instance():
syntax = Syntax(
CODE,
lexer=PythonLexer(),
line_numbers=False,
theme="foo",
code_width=60,
Expand All @@ -77,7 +94,7 @@ def test_python_render_simple():
def test_python_render_simple_indent_guides():
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=False,
theme="ansi_light",
code_width=60,
Expand All @@ -93,7 +110,7 @@ def test_python_render_simple_indent_guides():
def test_python_render_line_range_indent_guides():
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=False,
theme="ansi_light",
code_width=60,
Expand All @@ -111,7 +128,7 @@ def test_python_render_indent_guides():
syntax = Panel.fit(
Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme="foo",
Expand Down Expand Up @@ -144,7 +161,7 @@ def test_get_line_color_none():
style._background_style = Style(bgcolor=None)
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme=style,
Expand All @@ -158,7 +175,7 @@ def test_get_line_color_none():
def test_highlight_background_color():
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme="foo",
Expand Down Expand Up @@ -189,7 +206,7 @@ def test_get_style_for_token():
style._style_cache = style_dict
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme=style,
Expand All @@ -203,7 +220,7 @@ def test_get_style_for_token():
def test_option_no_wrap():
syntax = Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
code_width=60,
Expand All @@ -230,7 +247,8 @@ def test_from_file():
try:
os.write(fh, b"import this\n")
syntax = Syntax.from_path(path)
assert syntax.lexer_name == "Python"
assert syntax.lexer
assert syntax.lexer.name == "Python"
assert syntax.code == "import this\n"
finally:
os.remove(path)
Expand All @@ -242,7 +260,7 @@ def test_from_file_unknown_lexer():
try:
os.write(fh, b"import this\n")
syntax = Syntax.from_path(path)
assert syntax.lexer_name == "default"
assert syntax.lexer is None
assert syntax.code == "import this\n"
finally:
os.remove(path)
Expand All @@ -252,7 +270,7 @@ def test_from_file_unknown_lexer():
syntax = Panel.fit(
Syntax(
CODE,
lexer_name="python",
lexer="python",
line_numbers=True,
line_range=(2, 10),
theme="foo",
Expand Down