Skip to content

Highlighting

Syntax highlighting is data, not code: each rule is a pure function from one line of text to style spans. Rules run in registration order and later spans merge over earlier ones; link anchors additionally skip overlaps, so a file.py:10 inside a URL stays one link. Lines over 10,000 characters skip highlighting entirely (mega-line guard).

Add a language without touching the viewer:

from ansi_text_viewer.highlight import HighlightRule, Span

class TodoRule:
    def spans(self, text):
        i = text.find("TODO")
        if i >= 0:
            yield Span(i, i + 4, bold=True)

viewer.add_highlight_rule(TodoRule())
viewer.remove_highlight_rule(rule)  # symmetrical removal

Built-in rules

  • LogLevelRule[ERROR] / WARN:-style markers in bold level colors. Colors are injected through the constructor, so custom palettes compose: LogLevelRule({"ERROR": QColor(255, 0, 0)}).
  • TracebackRule — whole-line red bold for Python Traceback headers.
  • JsonKeyRule — blue "key": names.
  • CMakeRuleCMake Error (red) vs Warning (orange), -- Configuring… status lines in blue, [ 50%] Built target in green. Error wins over status on the same line.
  • JustRule# comments (gray italic), recipe: names (blue bold), NAME := assignments (purple bold), {{vars}} (magenta). Comment lines suppress the other justfile patterns.
  • UrlRulehttp(s):// anchors, blue underlined.
  • FileRulepath/to/file.py:line anchors; the :line suffix is stripped from the href so the OS opens the file. Skips anything containing :// so URLs keep a single anchor.

A Span carries start/end offsets plus optional foreground, bold, italic, underline, and anchor_href. Spans are frozen dataclasses, so rules stay trivially unit-testable without a widget.

ansi_text_viewer.highlight.Span dataclass

Span(
    start,
    end,
    foreground=None,
    bold=False,
    italic=False,
    underline=False,
    anchor_href=None,
)

A styled range inside one line (offsets are 0-based).

ansi_text_viewer.highlight.HighlightRule

Bases: Protocol

Structural type for pluggable highlight rules.

spans

spans(text)

Yield style spans for one line of text.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield style spans for one line of *text*."""
    ...

ansi_text_viewer.highlight.LogLevelRule

LogLevelRule(colors=None)

Colors [ERROR]/WARN:-style level markers bold.

Create the rule, optionally overriding level colors.

Parameters:

Name Type Description Default
colors dict[str, QColor] | None

Level name to color, defaults to built-ins.

None
Source code in ansi_text_viewer/highlight.py
def __init__(self, colors: dict[str, QColor] | None = None):
    """Create the rule, optionally overriding level colors.

    Args:
        colors: Level name to color, defaults to built-ins.
    """
    self.colors = dict(colors) if colors else dict(_DEFAULT_LOG_LEVEL_COLORS)

colors instance-attribute

colors = (
    dict(colors)
    if colors
    else dict(_DEFAULT_LOG_LEVEL_COLORS)
)

spans

spans(text)

Yield a bold span per level marker in text.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield a bold span per level marker in *text*."""
    for m in _LOG_LEVEL_RE.finditer(text):
        level = next(g for g in m.groups() if g)
        color = self.colors.get(level, QColor(160, 0, 0))
        yield Span(m.start(), m.end(), foreground=color, bold=True)

ansi_text_viewer.highlight.TracebackRule

Marks Python Traceback lines red and bold.

spans

spans(text)

Yield a whole-line span when text is a traceback header.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield a whole-line span when *text* is a traceback header."""
    if "Traceback" in text:
        yield Span(0, len(text), foreground=QColor(180, 0, 0), bold=True)

ansi_text_viewer.highlight.JsonKeyRule

Colors JSON object keys blue.

spans

spans(text)

Yield a span per "key": name in text.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield a span per ``"key":`` name in *text*."""
    for m in _JSON_KEY_RE.finditer(text):
        yield Span(m.start(1), m.end(1), foreground=QColor(0, 90, 180))

ansi_text_viewer.highlight.CMakeRule

Colors CMake errors, status lines and build progress.

spans

spans(text)

Yield spans for CMake output in text (error wins over status).

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield spans for CMake output in *text* (error wins over status)."""
    m = _CMAKE_ERR_RE.search(text)
    if m:
        if "error" in m.group(1).lower():
            color = QColor(200, 0, 0)
        else:
            color = QColor(180, 120, 0)
        yield Span(m.start(), m.end(), foreground=color, bold=True)
        return
    if _CMAKE_STATUS_RE.search(text):
        yield Span(0, len(text), foreground=QColor(0, 90, 180))
        return
    m = _CMAKE_BUILD_RE.search(text)
    if m:
        if "Built target" in m.group(0):
            color = QColor(0, 130, 0)
        else:
            color = QColor(90, 90, 90)
        yield Span(m.start(), m.end(), foreground=color)

ansi_text_viewer.highlight.JustRule

Colors justfile comments, recipes, assignments and variables.

spans

spans(text)

Yield spans for justfile syntax in text.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield spans for justfile syntax in *text*."""
    if _JUST_COMMENT_RE.match(text):
        yield Span(0, len(text), foreground=QColor(120, 120, 120), italic=True)
        return
    m = _JUST_RECIPE_RE.match(text)
    if m:
        yield Span(m.start(1), m.end(1), foreground=QColor(0, 90, 180), bold=True)
    else:
        m = _JUST_ASSIGN_RE.match(text)
        if m:
            yield Span(
                m.start(1), m.end(1), foreground=QColor(130, 0, 130), bold=True
            )
    for v in _JUST_VAR_RE.finditer(text):
        yield Span(v.start(), v.end(), foreground=QColor(160, 0, 160))

ansi_text_viewer.highlight.UrlRule

Turns http(s):// URLs into anchors.

spans

spans(text)

Yield an underlined blue anchor span per URL in text.

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield an underlined blue anchor span per URL in *text*."""
    for m in _URL_RE.finditer(text):
        url = m.group(0)
        yield Span(
            m.start(),
            m.end(),
            foreground=QColor(0, 80, 200),
            underline=True,
            anchor_href=url,
        )

ansi_text_viewer.highlight.FileRule

Turns path/to/file.py:line references into anchors.

spans

spans(text)

Yield an anchor span per file reference (href drops :line).

Source code in ansi_text_viewer/highlight.py
def spans(self, text: str) -> Iterable[Span]:
    """Yield an anchor span per file reference (href drops ``:line``)."""
    for m in _FILE_RE.finditer(text):
        raw = m.group(0).rstrip(".,;:")
        if not raw or "://" in raw:
            continue
        file_part = re.split(r":\d+", raw, maxsplit=1)[0]
        yield Span(
            m.start(),
            m.start() + len(raw),
            foreground=QColor(0, 80, 200),
            underline=True,
            anchor_href=file_part,
        )