"""
Defines render functions that render lexed markdown block tokens into urwid
representations
"""
import pygments
import pygments.formatters
import pygments.lexers
import pygments.styles
import mistune
import re
import shlex
import urwid
import lookatme.config as config
from lookatme.contrib import contrib_first
import lookatme.render.pygments as pygments_render
import lookatme.render.markdown_inline as markdown_inline_renderer
from lookatme.utils import *
from lookatme.widgets.clickable_text import ClickableText
def _meta(item):
if not hasattr(item, "meta"):
meta = {}
setattr(item, "meta", meta)
else:
meta = getattr(item, "meta")
return meta
def _set_is_list(item, level=1, ordered=False):
_meta(item).update({
"is_list": True,
"list_level": level,
"ordered": ordered,
"item_count": 0,
})
def _inc_item_count(item):
_meta(item)["item_count"] += 1
return _meta(item)["item_count"]
def _is_list(item):
return _meta(item).get("is_list", False)
def _list_level(item):
return _meta(item).get("list_level", 1)
[docs]@contrib_first
def render_newline(token, body, stack, loop):
"""Render a newline
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
return urwid.Divider()
[docs]@contrib_first
def render_hrule(token, body, stack, loop):
"""Render a newline
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
hrule_conf = config.STYLE["hrule"]
div = urwid.Divider(hrule_conf['char'], top=1, bottom=1)
return urwid.Pile([urwid.AttrMap(div, spec_from_style(hrule_conf['style']))])
[docs]@contrib_first
def render_heading(token, body, stack, loop):
"""Render markdown headings, using the defined styles for the styling and
prefix/suffix.
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
Below are the default stylings for headings:
.. code-block:: yaml
headings:
'1':
bg: default
fg: '#9fc,bold'
prefix: "██ "
suffix: ""
'2':
bg: default
fg: '#1cc,bold'
prefix: "▓▓▓ "
suffix: ""
'3':
bg: default
fg: '#29c,bold'
prefix: "▒▒▒▒ "
suffix: ""
'4':
bg: default
fg: '#66a,bold'
prefix: "░░░░░ "
suffix: ""
default:
bg: default
fg: '#579,bold'
prefix: "░░░░░ "
suffix: ""
:returns: A list of urwid Widgets or a single urwid Widget
"""
headings = config.STYLE["headings"]
level = token["level"]
style = config.STYLE["headings"].get(str(level), headings["default"])
prefix = styled_text(style["prefix"], style)
suffix = styled_text(style["suffix"], style)
rendered = render_text(text=token["text"])
if len(rendered) > 0:
rendered = rendered[0]
return [
urwid.Divider(),
ClickableText([prefix] + styled_text(rendered, style) + [suffix]),
urwid.Divider(),
]
[docs]@contrib_first
def render_table(token, body, stack, loop):
"""Renders a table using the :any:`Table` widget.
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
The table widget makes use of the styles below:
.. code-block:: yaml
table:
column_spacing: 3
header_divider: "─"
:returns: A list of urwid Widgets or a single urwid Widget
"""
from lookatme.widgets.table import Table
headers = token["header"]
aligns = token["align"]
cells = token["cells"]
table = Table(cells, headers=headers, aligns=aligns)
padding = urwid.Padding(table, width=table.total_width + 2, align="center")
def table_changed(*args, **kwargs):
padding.width = table.total_width + 2
urwid.connect_signal(table, "change", table_changed)
return padding
[docs]@contrib_first
def render_list_start(token, body, stack, loop):
"""Handles the indentation when starting rendering a new list. List items
themselves (with the bullets) are rendered by the
:any:`render_list_item_start` function.
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
res = urwid.Pile([])
in_list = _is_list(stack[-1])
list_level = 1
if in_list:
list_level = _list_level(stack[-1]) + 1
_set_is_list(res, list_level, ordered=token['ordered'])
_meta(res)['list_start_token'] = token
_meta(res)['max_list_marker_width'] = token.get('max_list_marker_width', 2)
stack.append(res)
widgets = []
if not in_list:
widgets.append(urwid.Divider())
widgets.append(urwid.Padding(res, left=2))
widgets.append(urwid.Divider())
return widgets
return res
[docs]@contrib_first
def render_list_end(token, body, stack, loop):
"""Pops the pushed ``urwid.Pile()`` from the stack (decreases indentation)
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
meta = _meta(stack[-1])
meta['list_start_token']['max_list_marker_width'] = meta['max_list_marker_width']
stack.pop()
def _list_item_start(token, body, stack, loop):
"""Render the start of a list item. This function makes use of two
different styles, one each for unordered lists (bullet styles) and ordered
lists (numbering styles):
.. code-block:: yaml
bullets:
'1': "•"
'2': "⁃"
'3': "◦"
default: "•"
numbering:
'1': "numeric"
'2': "alpha"
'3': "roman"
default: "numeric"
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
list_level = _list_level(stack[-1])
curr_count = _inc_item_count(stack[-1])
pile = urwid.Pile(urwid.SimpleFocusListWalker([]))
meta = _meta(stack[-1])
if meta["ordered"]:
numbering = config.STYLE["numbering"]
list_marker_type = numbering.get(str(list_level), numbering["default"])
sequence = {
"numeric": lambda x: str(x),
"alpha": lambda x: chr(ord("a") + x - 1),
"roman": lambda x: int_to_roman(x),
}[list_marker_type]
list_marker = sequence(curr_count) + "."
else:
bullets = config.STYLE["bullets"]
list_marker = bullets.get(str(list_level), bullets["default"])
marker_text = list_marker + " "
if len(marker_text) > meta["max_list_marker_width"]:
meta["max_list_marker_width"] = len(marker_text)
marker_col_width = meta['max_list_marker_width']
res = urwid.Text(("bold", marker_text))
res = urwid.Columns([
(marker_col_width, urwid.Text(("bold", marker_text))),
pile,
])
stack.append(pile)
return res
[docs]@contrib_first
def render_list_item_start(token, body, stack, loop):
"""Render the start of a list item. This function makes use of the styles:
.. code-block:: yaml
bullets:
'1': "•"
'2': "⁃"
'3': "◦"
default: "•"
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
return _list_item_start(token, body, stack, loop)
[docs]@contrib_first
def render_loose_item_start(token, body, stack, loop):
"""Render the start of a list item. This function makes use of the styles:
.. code-block:: yaml
bullets:
'1': "•"
'2': "⁃"
'3': "◦"
default: "•"
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
return _list_item_start(token, body, stack, loop)
[docs]@contrib_first
def render_list_item_end(token, body, stack, loop):
"""Pops the pushed ``urwid.Pile()`` from the stack (decreases indentation)
See :any:`lookatme.tui.SlideRenderer.do_render` for argument and return
value descriptions.
"""
stack.pop()
[docs]@contrib_first
def render_text(token=None, body=None, stack=None, loop=None, text=None):
"""Renders raw text. This function uses the inline markdown lexer
from mistune with the :py:mod:`lookatme.render.markdown_inline` render module
to render the lexed inline markup to a list composed of widgets or
`urwid Text markup <http://urwid.org/manual/displayattributes.html#text-markup>`_.
The created list of widgets/Text markup is then used to create and return
a list composed entirely of widgets and :any:`ClickableText` instances.
Many other functions call this function directly, passing in the extra
``text`` argument and leaving all other arguments blank.
See :any:`lookatme.tui.SlideRenderer.do_render` for additional argument and
return value descriptions.
"""
if text is None:
text = token["text"]
inline_lexer = mistune.InlineLexer(markdown_inline_renderer)
res = inline_lexer.output(text)
if len(res) == 0:
res = [""]
widget_list = []
curr_text_spec = []
for item in res:
if isinstance(item, urwid.Widget):
if len(curr_text_spec) > 0:
widget_list.append(ClickableText(curr_text_spec))
curr_text_spec = []
widget_list.append(item)
else:
curr_text_spec.append(item)
if len(curr_text_spec) > 0:
widget_list.append(ClickableText(curr_text_spec))
return widget_list
[docs]@contrib_first
def render_paragraph(token, body, stack, loop):
"""Renders the provided text with additional pre and post paddings.
See :any:`lookatme.tui.SlideRenderer.do_render` for additional argument and
return value descriptions.
"""
token["text"] = token["text"].replace("\r\n", " ").replace("\n", " ")
res = render_text(token, body, stack, loop)
return [urwid.Divider()] + res + [urwid.Divider()]
[docs]@contrib_first
def render_block_quote_start(token, body, stack, loop):
"""Begins rendering of a block quote. Pushes a new ``urwid.Pile()`` to the
stack that is indented, has styling applied, and has the quote markers
on the left.
This function makes use of the styles:
.. code-block:: yaml
quote:
top_corner: "┌"
bottom_corner: "└"
side: "╎"
style:
bg: default
fg: italics,#aaa
See :any:`lookatme.tui.SlideRenderer.do_render` for additional argument and
return value descriptions.
"""
pile = urwid.Pile([])
stack.append(pile)
styles = config.STYLE["quote"]
quote_side = styles["side"]
quote_top_corner = styles["top_corner"]
quote_bottom_corner = styles["bottom_corner"]
quote_style = styles["style"]
return [
urwid.Divider(),
urwid.LineBox(
urwid.AttrMap(
urwid.Padding(pile, left=2),
spec_from_style(quote_style),
),
lline=quote_side, rline="",
tline=" ", trcorner="", tlcorner=quote_top_corner,
bline=" ", brcorner="", blcorner=quote_bottom_corner,
),
urwid.Divider(),
]
[docs]@contrib_first
def render_block_quote_end(token, body, stack, loop):
"""Pops the block quote start ``urwid.Pile()`` from the stack, taking
future renderings out of the block quote styling.
See :any:`lookatme.tui.SlideRenderer.do_render` for additional argument and
return value descriptions.
"""
pile = stack.pop()
# remove leading/trailing divider if they were added to the pile
if isinstance(pile.contents[0][0], urwid.Divider):
pile.contents = pile.contents[1:]
if isinstance(pile.contents[-1][0], urwid.Divider):
pile.contents = pile.contents[:-1]
[docs]@contrib_first
def render_code(token, body, stack, loop):
"""Renders a code block using the Pygments library.
See :any:`lookatme.tui.SlideRenderer.do_render` for additional argument and
return value descriptions.
"""
lang = token.get("lang", "text") or "text"
text = token["text"]
res = pygments_render.render_text(text, lang=lang)
return [
urwid.Divider(),
res,
urwid.Divider()
]