Skip to content

Commit

Permalink
Codeblocks now have line numbers, highlighting, bg override (#189)
Browse files Browse the repository at this point in the history
Adds the ability to enable line numbers in code blocks and highlight lines

Specifically:
* Curly brace attributes on code blocks are now supported
* the theme can now be set in the YAML metadata:
  ```yaml
  ---
  theme: light
  ---
  ```
* a new `styles.code` YAML metadata value can be used:
  ```markdown
  ---
  styles:
    code:
      style: zenburn
      inline_lang: python
      bg_override: "#aabbcc"
  ---
  ```
* the `--style` CLI argument is removed (should be replaced by the feature described in #190)

See #164
See #89
  • Loading branch information
d0c-s4vage committed Dec 11, 2022
1 parent 6710dad commit b2b3d73
Show file tree
Hide file tree
Showing 16 changed files with 792 additions and 291 deletions.
11 changes: 2 additions & 9 deletions lookatme/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@
"--theme",
"theme",
type=click.Choice(["dark", "light"]),
default="dark",
)
@click.option(
"--style",
"code_style",
default=None,
type=click.Choice(list(pygments.styles.get_all_styles())),
)
@click.option(
"--dump-styles",
Expand Down Expand Up @@ -122,7 +116,6 @@ def main(
threads,
log_path,
theme,
code_style,
dump_styles,
input_files,
live_reload,
Expand Down Expand Up @@ -151,11 +144,12 @@ def main(
else:
tutors = [x.strip() for x in tutorial.split(",")]

if theme is None:
theme = "dark"
theme_mod = __import__("lookatme.themes." + theme, fromlist=[theme])
lookatme.config.set_global_style_with_precedence(
theme_mod,
{},
code_style,
)
tutorial_md = lookatme.tutorial.get_tutorial_md(tutors)
if tutorial_md is None:
Expand All @@ -169,7 +163,6 @@ def main(
pres = Presentation(
input_files[0],
theme,
code_style,
live_reload=live_reload,
single_slide=single_slide,
preload_extensions=preload_exts,
Expand Down
11 changes: 6 additions & 5 deletions lookatme/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def get_style() -> Dict:


def get_style_with_precedence(
theme_mod: ModuleType, direct_overrides: Dict, style_override: str
theme_mod: ModuleType,
direct_overrides: Dict,
) -> Dict[str, Any]:
"""Return the resulting style dict from the provided override values."""
# style override order:
Expand All @@ -37,20 +38,20 @@ def get_style_with_precedence(
# 2. inline styles from the presentation
dict_deep_update(styles, direct_overrides)
# 3. CLI style overrides
if style_override is not None:
styles["style"] = style_override # type: ignore
# TODO

return styles


def set_global_style_with_precedence(
theme_mod, direct_overrides, style_override
theme_mod,
direct_overrides,
) -> Dict[str, Any]:
"""Set the lookatme.config.STYLE value based on the provided override
values
"""
global STYLE
STYLE = get_style_with_precedence(theme_mod, direct_overrides, style_override)
STYLE = get_style_with_precedence(theme_mod, direct_overrides)

return STYLE

Expand Down
9 changes: 4 additions & 5 deletions lookatme/pres.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def __init__(
self,
input_stream,
theme,
style_override=None,
live_reload=False,
single_slide=False,
preload_extensions=None,
Expand All @@ -62,7 +61,6 @@ def __init__(
lookatme.config.SLIDE_SOURCE_DIR = os.path.dirname(input_stream.name)
self.input_filename = input_stream.name

self.style_override = style_override
self.live_reload = live_reload
self.tui = None
self.single_slide = single_slide
Expand All @@ -71,8 +69,7 @@ def __init__(
self.ignore_ext_failure = ignore_ext_failure
self.initial_load_complete = False
self.no_threads = no_threads

self.theme_mod = __import__("lookatme.themes." + theme, fromlist=[theme])
self.cli_theme = theme

if self.live_reload:
self.reload_thread = threading.Thread(target=self.reload_watcher)
Expand Down Expand Up @@ -133,10 +130,12 @@ def reload(self, data=None):
self.ignore_ext_failure,
)

theme = self.meta.get("theme", self.cli_theme or "dark")
self.theme_mod = __import__("lookatme.themes." + theme, fromlist=[theme])

self.styles = lookatme.config.set_global_style_with_precedence(
self.theme_mod,
self.meta.get("styles", {}),
self.style_override,
)

self.initial_load_complete = True
Expand Down
189 changes: 177 additions & 12 deletions lookatme/render/markdown_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
import copy
import pygments
import pygments.styles
import pygments.lexers
import re
import sys
from typing import Dict, List, Tuple, Optional, Union
from typing import Any, Dict, List, Tuple, Optional, Union

import urwid

import lookatme.config as config
import lookatme.render.markdown_inline as markdown_inline
import lookatme.render.pygments as pygments_render
from lookatme.contrib import contrib_first
from lookatme.render.context import Context
from lookatme.tutorial import tutor
from lookatme.widgets.fancy_box import FancyBox
import lookatme.widgets.codeblock as codeblock
import lookatme.utils as utils

THIS_MOD = sys.modules[__name__]
Expand Down Expand Up @@ -412,14 +413,131 @@ def render_blockquote_close(token: Dict, ctx: Context):
ctx.container_pop()


def _parse_hl_lines(values) -> List:
"""Parse comma-separated lists of line ranges to highlight"""
res = []
matches = re.finditer(
r"""
,?\s*
(
(?P<rangeStart>[0-9]+)
(\.\.|-)
(?P<rangeEnd>[0-9]+)
|
(?P<singleLine>[0-9]+)
)
\s*""",
values,
re.VERBOSE,
)

for match in matches:
info = match.groupdict()
if info["singleLine"]:
val = int(info["singleLine"])
res.append(range(val, val + 1))
elif info["rangeStart"]:
res.append(
range(
int(info["rangeStart"]),
int(info["rangeEnd"]) + 1,
)
)

return res


def _parse_curly_extra(data: str) -> Dict[str, Any]:
res = {}

matches = re.finditer(
r"""\s*
(
(?P<attr>[a-zA-Z-_]+)
\s*=\s*
(
"(?P<doubleQuoteVal>[^"]*)"
|
'(?P<singleQuoteVal>[^']*)'
|
(?P<plainVal>[a-zA-Z0-9-_,]+)
)
|
(?P<class>\.)?
(?P<id>\.)?
(?P<classOrIdName>[a-zA-Z0-9-_]+)
)
\s*
""",
data,
re.VERBOSE,
)

for match in matches:
info = match.groupdict()

if info["classOrIdName"]:
val = info["classOrIdName"].lower()
if val in codeblock.supported_langs():
res["lang"] = info["classOrIdName"]
elif val in (
"numberlines",
"number_lines",
"numbers",
"line_numbers",
"linenumbers",
"line_numbers",
):
res["line_numbers"] = True
elif info["attr"]:
attr = info["attr"].lower()
val = info["plainVal"] or info["doubleQuoteVal"] or info["singleQuoteVal"]
if attr in ("startfrom", "start_from", "line_numberstart", "startlineno"):
res["start_line_number"] = int(val)
elif attr in ("hl_lines", "hllines", "highlight", "highlight_lines"):
res["hl_lines"] = _parse_hl_lines(val)

return res


@tutor(
"markdown block",
"code blocks - extra attributes",
r"""
Code blocks can also have additional attributes defined by using curly braces.
Values within the curly brace are either css class names or ids (start with a `.`
or `#`), or have the form `key=value`.
The attributes below have specific meanings - all other attributes will be
ignored:
* `.language` - use `language` as the syntax highlighting language
* `.numberLines` - add line numbers
* `startFrom=X` - start the line numbers from the line `X`
* `hllines=ranges` - highlight the line ranges. This should be a comma separated
list of either single line numbers, or a line range (e.g. `4-5`).
<TUTOR:EXAMPLE>
```{.python .numberLines hllines=4-5,7 startFrom="3"}
def hello_world():
print("Hello, world!\n")
print("Hello, world!\n")
print("Hello, world!\n")
print("Hello, world!\n")
print("Hello, world!\n")
```
</TUTOR:EXAMPLE>
""",
)
@tutor(
"markdown block",
"code blocks",
r"""
Multi-line code blocks are either surrounded by "fences" (three in a row of
either `\`` or `~`), or are lines of text indented at least four spaces.
Fenced codeblocks let you specify the language of the code:
Fenced codeblocks let you specify the language of the code. (See the next
slide about additional attributes)
<TUTOR:EXAMPLE>
```python
Expand All @@ -431,16 +549,15 @@ def hello_world():
## Style
The syntax highlighting style used to highlight the code block can be
specified in the markdown metadata:
specified in the markdown metadata, as well as an override for the
background color, and the language to use for inline code.
<TUTOR:STYLE>style</TUTOR:STYLE>
<TUTOR:STYLE {{hllines=4,6}}>code</TUTOR:STYLE>
Valid values for the `style` field come directly from pygments. In the
version of pygments being used as you read this, the list of valid values is:
{pygments_values}
> **NOTE** This style name is confusing and will be renamed in lookatme v3.0+
""".format(
pygments_values=" ".join(pygments.styles.get_all_styles()),
),
Expand All @@ -455,12 +572,60 @@ def render_fence(token: Dict, ctx: Context):
ctx.ensure_new_block()

info = token.get("info", None) or "text"
lang = info.split()[0]
# TODO support line highlighting, etc?
text = token["content"]
res = pygments_render.render_text(text, lang=lang)

ctx.widget_add(res)
match = re.match(r"^(?P<lang>[^{\s]+)?\s*(\{(?P<curly_extra>[^{]+)\})?", info)
lang = "text"
line_numbers = False
start_line_number = 1
hl_lines = []

if match is not None:
full_info = match.groupdict()
if full_info["lang"] is not None:
lang = full_info["lang"]
if full_info["curly_extra"] is not None:
curly_extra = _parse_curly_extra(full_info["curly_extra"])
lang = curly_extra.get("lang", lang)
line_numbers = curly_extra.get("line_numbers", line_numbers)
start_line_number = curly_extra.get("start_line_number", start_line_number)
hl_lines = curly_extra.get("hl_lines", [])

curr_spec = ctx.spec_text
default_fg = "default"
bg_override = config.get_style()["code"]["bg_override"]
if curr_spec:
default_fg = (
default_fg
or utils.overwrite_style({"fg": curr_spec.foreground}, {"fg": default_fg})[
"fg"
]
)

code = codeblock.CodeBlock(
source=token["content"],
lang=lang,
style_name=config.get_style()["code"]["style"],
line_numbers=line_numbers,
start_line_number=start_line_number,
hl_lines=hl_lines,
default_fg=default_fg,
bg_override=bg_override,
)

ctx.widget_add(code)


#
# text = token["content"]
# res = pygments_render.render_text(
# text,
# lang=lang,
# line_numbers=line_numbers,
# start_line_number=start_line_number,
# hl_lines=hl_lines,
# )
#
# ctx.widget_add(res)


class TableTokenExtractor:
Expand Down
Loading

0 comments on commit b2b3d73

Please sign in to comment.