mirror of https://github.com/Textualize/rich.git
inline code in Markdown
This commit is contained in:
parent
857d8e0b6c
commit
c50751c774
200
rich/markdown.py
200
rich/markdown.py
|
@ -18,7 +18,7 @@ from .panel import Panel
|
|||
from .rule import Rule
|
||||
from .style import Style, StyleStack
|
||||
from .syntax import Syntax
|
||||
from .text import Text
|
||||
from .text import Text, TextType
|
||||
|
||||
|
||||
class MarkdownElement:
|
||||
|
@ -45,7 +45,7 @@ class MarkdownElement:
|
|||
context (MarkdownContext): The markdown context.
|
||||
"""
|
||||
|
||||
def on_text(self, context: "MarkdownContext", text: str) -> None:
|
||||
def on_text(self, context: "MarkdownContext", text: TextType) -> None:
|
||||
"""Called when text is parsed.
|
||||
|
||||
Args:
|
||||
|
@ -99,8 +99,9 @@ class TextElement(MarkdownElement):
|
|||
self.style = context.enter_style(self.style_name)
|
||||
self.text = Text(justify="left")
|
||||
|
||||
def on_text(self, context: "MarkdownContext", text: str) -> None:
|
||||
self.text.append(text, context.current_style)
|
||||
def on_text(self, context: "MarkdownContext", text: TextType) -> None:
|
||||
|
||||
self.text.append(text, context.current_style if isinstance(text, str) else None)
|
||||
|
||||
def on_leave(self, context: "MarkdownContext") -> None:
|
||||
context.leave_style()
|
||||
|
@ -349,20 +350,36 @@ class ImageItem(TextElement):
|
|||
class MarkdownContext:
|
||||
"""Manages the console render state."""
|
||||
|
||||
def __init__(self, console: Console, options: ConsoleOptions, style: Style) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
console: Console,
|
||||
options: ConsoleOptions,
|
||||
style: Style,
|
||||
inline_code_theme: str = None,
|
||||
inline_code_lexer: str = "python",
|
||||
) -> None:
|
||||
self.console = console
|
||||
self.options = options
|
||||
self.style_stack: StyleStack = StyleStack(style)
|
||||
self.stack: Stack[MarkdownElement] = Stack()
|
||||
|
||||
self._syntax: Optional[Syntax] = None
|
||||
if inline_code_theme is not None:
|
||||
self._syntax = Syntax("", inline_code_lexer, theme=inline_code_theme)
|
||||
|
||||
@property
|
||||
def current_style(self) -> Style:
|
||||
"""Current style which is the product of all styles on the stack."""
|
||||
return self.style_stack.current
|
||||
|
||||
def on_text(self, text: str) -> None:
|
||||
def on_text(self, text: str, node_type: str) -> None:
|
||||
"""Called when the parser visits text."""
|
||||
self.stack.top.on_text(self, text)
|
||||
if node_type == "code" and self._syntax is not None:
|
||||
highlight_text = self._syntax.highlight(text)
|
||||
highlight_text.rstrip()
|
||||
self.stack.top.on_text(self, highlight_text)
|
||||
else:
|
||||
self.stack.top.on_text(self, text)
|
||||
|
||||
def enter_style(self, style_name: Union[str, Style]) -> Style:
|
||||
"""Enter a style context."""
|
||||
|
@ -406,6 +423,8 @@ class Markdown(JupyterMixin):
|
|||
justify: JustifyMethod = None,
|
||||
style: Union[str, Style] = "none",
|
||||
hyperlinks: bool = True,
|
||||
inline_code_theme: Optional[str] = "monokai",
|
||||
inline_code_lexer: str = "python",
|
||||
) -> None:
|
||||
self.markup = markup
|
||||
parser = Parser()
|
||||
|
@ -414,26 +433,34 @@ class Markdown(JupyterMixin):
|
|||
self.justify = justify
|
||||
self.style = style
|
||||
self.hyperlinks = hyperlinks
|
||||
self.inline_code_lexer = inline_code_lexer
|
||||
self.inline_code_theme = inline_code_theme
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
"""Render markdown to the console."""
|
||||
style = console.get_style(self.style, default="none")
|
||||
context = MarkdownContext(console, options, style)
|
||||
context = MarkdownContext(
|
||||
console,
|
||||
options,
|
||||
style,
|
||||
inline_code_theme=self.inline_code_theme,
|
||||
inline_code_lexer=self.inline_code_lexer,
|
||||
)
|
||||
nodes = self.parsed.walker()
|
||||
inlines = self.inlines
|
||||
new_line = False
|
||||
for current, entering in nodes:
|
||||
node_type = current.t
|
||||
if node_type in ("html_inline", "html_block", "text"):
|
||||
context.on_text(current.literal.replace("\n", " "))
|
||||
context.on_text(current.literal.replace("\n", " "), node_type)
|
||||
elif node_type == "linebreak":
|
||||
if entering:
|
||||
context.on_text("\n")
|
||||
context.on_text("\n", node_type)
|
||||
elif node_type == "softbreak":
|
||||
if entering:
|
||||
context.on_text(" ")
|
||||
context.on_text(" ", node_type)
|
||||
elif node_type == "link":
|
||||
if entering:
|
||||
link_style = console.get_style("markdown.link", default="none")
|
||||
|
@ -443,14 +470,14 @@ class Markdown(JupyterMixin):
|
|||
else:
|
||||
context.leave_style()
|
||||
if not self.hyperlinks:
|
||||
context.on_text(" (")
|
||||
context.on_text(" (", node_type)
|
||||
style = Style(underline=True) + console.get_style(
|
||||
"markdown.link_url", default="none"
|
||||
)
|
||||
context.enter_style(style)
|
||||
context.on_text(current.destination)
|
||||
context.on_text(current.destination, node_type)
|
||||
context.leave_style()
|
||||
context.on_text(")")
|
||||
context.on_text(")", node_type)
|
||||
elif node_type in inlines:
|
||||
if current.is_container():
|
||||
if entering:
|
||||
|
@ -460,7 +487,7 @@ class Markdown(JupyterMixin):
|
|||
else:
|
||||
context.enter_style(f"markdown.{node_type}")
|
||||
if current.literal:
|
||||
context.on_text(current.literal)
|
||||
context.on_text(current.literal, node_type)
|
||||
context.leave_style()
|
||||
else:
|
||||
element_class = self.elements.get(node_type) or UnknownElement
|
||||
|
@ -500,77 +527,84 @@ class Markdown(JupyterMixin):
|
|||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
import argparse
|
||||
test = """# Hello
|
||||
This is a test of inline code: `import this` , `for a in range(10):`"""
|
||||
from rich import print
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Render Markdown to the console with Rich"
|
||||
)
|
||||
parser.add_argument("path", metavar="PATH", help="path to markdown file")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--force-color",
|
||||
dest="force_color",
|
||||
action="store_true",
|
||||
default=None,
|
||||
help="force color for non-terminals",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--code-theme",
|
||||
dest="code_theme",
|
||||
default="monokai",
|
||||
help="pygments code theme",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--hyperlinks",
|
||||
dest="hyperlinks",
|
||||
action="store_true",
|
||||
help="enable hyperlinks",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w",
|
||||
"--width",
|
||||
type=int,
|
||||
dest="width",
|
||||
default=None,
|
||||
help="width of output (default will auto-detect)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--justify",
|
||||
dest="justify",
|
||||
action="store_true",
|
||||
help="enable full text justify",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--page",
|
||||
dest="page",
|
||||
action="store_true",
|
||||
help="use pager to scroll output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
print(Markdown(test, inline_code_theme="emacs"))
|
||||
|
||||
from rich.console import Console
|
||||
if 0:
|
||||
import argparse
|
||||
|
||||
with open(args.path, "rt", encoding="utf-8") as markdown_file:
|
||||
markdown = Markdown(
|
||||
markdown_file.read(),
|
||||
justify="full" if args.justify else "left",
|
||||
code_theme=args.code_theme,
|
||||
hyperlinks=args.hyperlinks,
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Render Markdown to the console with Rich"
|
||||
)
|
||||
if args.page:
|
||||
import pydoc
|
||||
import io
|
||||
|
||||
console = Console(
|
||||
file=io.StringIO(), force_terminal=args.force_color, width=args.width
|
||||
parser.add_argument("path", metavar="PATH", help="path to markdown file")
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--force-color",
|
||||
dest="force_color",
|
||||
action="store_true",
|
||||
default=None,
|
||||
help="force color for non-terminals",
|
||||
)
|
||||
console.print(markdown)
|
||||
pydoc.pager(console.file.getvalue()) # type: ignore
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--code-theme",
|
||||
dest="code_theme",
|
||||
default="monokai",
|
||||
help="pygments code theme",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-y",
|
||||
"--hyperlinks",
|
||||
dest="hyperlinks",
|
||||
action="store_true",
|
||||
help="enable hyperlinks",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w",
|
||||
"--width",
|
||||
type=int,
|
||||
dest="width",
|
||||
default=None,
|
||||
help="width of output (default will auto-detect)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--justify",
|
||||
dest="justify",
|
||||
action="store_true",
|
||||
help="enable full text justify",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--page",
|
||||
dest="page",
|
||||
action="store_true",
|
||||
help="use pager to scroll output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
else:
|
||||
console = Console(force_terminal=args.force_color, width=args.width)
|
||||
console.print(markdown)
|
||||
from rich.console import Console
|
||||
|
||||
with open(args.path, "rt", encoding="utf-8") as markdown_file:
|
||||
markdown = Markdown(
|
||||
markdown_file.read(),
|
||||
justify="full" if args.justify else "left",
|
||||
code_theme=args.code_theme,
|
||||
hyperlinks=args.hyperlinks,
|
||||
)
|
||||
if args.page:
|
||||
import pydoc
|
||||
import io
|
||||
|
||||
console = Console(
|
||||
file=io.StringIO(), force_terminal=args.force_color, width=args.width
|
||||
)
|
||||
console.print(markdown)
|
||||
pydoc.pager(console.file.getvalue()) # type: ignore
|
||||
|
||||
else:
|
||||
console = Console(force_terminal=args.force_color, width=args.width)
|
||||
console.print(markdown)
|
||||
|
|
|
@ -11,7 +11,7 @@ from pygments.util import ClassNotFound
|
|||
|
||||
from ._loop import loop_first
|
||||
from .color import Color, blend_rgb, parse_rgb_hex
|
||||
from .console import Console, ConsoleOptions, RenderResult, Segment
|
||||
from .console import Console, ConsoleOptions, JustifyMethod, RenderResult, Segment
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .style import Style
|
||||
|
@ -173,18 +173,25 @@ class Syntax(JupyterMixin):
|
|||
style = style + Style(bgcolor=self._pygments_style_class.background_color)
|
||||
return style
|
||||
|
||||
def _highlight(self, lexer_name: str) -> Text:
|
||||
def highlight(self, code: str = None) -> Text:
|
||||
"""Highlight a string with the given lexter and return a Text instance.
|
||||
|
||||
Returns:
|
||||
Text: A text instance containing highlights.
|
||||
"""
|
||||
if code is None:
|
||||
code = self.code
|
||||
default_style = self._get_default_style()
|
||||
try:
|
||||
lexer = get_lexer_by_name(lexer_name)
|
||||
lexer = get_lexer_by_name(self.lexer_name)
|
||||
except ClassNotFound:
|
||||
return Text(
|
||||
self.code, justify="left", style=default_style, tab_size=self.tab_size
|
||||
code, justify="left", style=default_style, tab_size=self.tab_size
|
||||
)
|
||||
text = Text(justify="left", style=default_style, tab_size=self.tab_size)
|
||||
append = text.append
|
||||
_get_theme_style = self._get_theme_style
|
||||
for token_type, token in lexer.get_tokens(self.code):
|
||||
for token_type, token in lexer.get_tokens(code):
|
||||
append(token, _get_theme_style(token_type))
|
||||
return text
|
||||
|
||||
|
@ -244,7 +251,7 @@ class Syntax(JupyterMixin):
|
|||
code = self.code
|
||||
if self.dedent:
|
||||
code = textwrap.dedent(code)
|
||||
text = self._highlight(self.lexer_name)
|
||||
text = self.highlight()
|
||||
if text.plain.endswith("\n"):
|
||||
text.plain = text.plain[:-1]
|
||||
if not self.line_numbers:
|
||||
|
|
Loading…
Reference in New Issue