jupyter.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. from typing import Any, Dict, Iterable, List
  2. from . import get_console
  3. from .segment import Segment
  4. from .terminal_theme import DEFAULT_TERMINAL_THEME
  5. JUPYTER_HTML_FORMAT = """\
  6. <pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
  7. """
  8. class JupyterRenderable:
  9. """A shim to write html to Jupyter notebook."""
  10. def __init__(self, html: str, text: str) -> None:
  11. self.html = html
  12. self.text = text
  13. def _repr_mimebundle_(
  14. self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any
  15. ) -> Dict[str, str]:
  16. data = {"text/plain": self.text, "text/html": self.html}
  17. if include:
  18. data = {k: v for (k, v) in data.items() if k in include}
  19. if exclude:
  20. data = {k: v for (k, v) in data.items() if k not in exclude}
  21. return data
  22. class JupyterMixin:
  23. """Add to an Rich renderable to make it render in Jupyter notebook."""
  24. __slots__ = ()
  25. def _repr_mimebundle_(
  26. self, include: Iterable[str], exclude: Iterable[str], **kwargs: Any
  27. ) -> Dict[str, str]:
  28. console = get_console()
  29. segments = list(console.render(self, console.options)) # type: ignore
  30. html = _render_segments(segments)
  31. text = console._render_buffer(segments)
  32. data = {"text/plain": text, "text/html": html}
  33. if include:
  34. data = {k: v for (k, v) in data.items() if k in include}
  35. if exclude:
  36. data = {k: v for (k, v) in data.items() if k not in exclude}
  37. return data
  38. def _render_segments(segments: Iterable[Segment]) -> str:
  39. def escape(text: str) -> str:
  40. """Escape html."""
  41. return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
  42. fragments: List[str] = []
  43. append_fragment = fragments.append
  44. theme = DEFAULT_TERMINAL_THEME
  45. for text, style, control in Segment.simplify(segments):
  46. if control:
  47. continue
  48. text = escape(text)
  49. if style:
  50. rule = style.get_html_style(theme)
  51. text = f'<span style="{rule}">{text}</span>' if rule else text
  52. if style.link:
  53. text = f'<a href="{style.link}">{text}</a>'
  54. append_fragment(text)
  55. code = "".join(fragments)
  56. html = JUPYTER_HTML_FORMAT.format(code=code)
  57. return html
  58. def display(segments: Iterable[Segment], text: str) -> None:
  59. """Render segments to Jupyter."""
  60. html = _render_segments(segments)
  61. jupyter_renderable = JupyterRenderable(html, text)
  62. try:
  63. from IPython.display import display as ipython_display
  64. ipython_display(jupyter_renderable)
  65. except ModuleNotFoundError:
  66. # Handle the case where the Console has force_jupyter=True,
  67. # but IPython is not installed.
  68. pass
  69. def print(*args: Any, **kwargs: Any) -> None:
  70. """Proxy for Console print."""
  71. console = get_console()
  72. return console.print(*args, **kwargs)