Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/toad/line_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ class OutgoingRole(LineRole):
colour = SuccessColour


class GreenTextRole(LineRole):
"""A quoted line, drawn as greentext."""

colour = SuccessColour


class NoticeRole(LineRole):
colour = WarningColour

Expand Down
42 changes: 40 additions & 2 deletions src/toad/markdown_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import re
from abc import abstractmethod
from dataclasses import dataclass
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from threading import local
Expand Down Expand Up @@ -77,10 +77,28 @@ def matches(self, text: str):
return sorted(found)


class JoinedLines:
"""Paragraph source lines join with a space, as Markdown draws them."""

@staticmethod
def softbreak(token: Token) -> Token:
return token


class QuotedLines(JoinedLines):
"""Every quoted source line stays its own line, as greentext."""

@staticmethod
def softbreak(token: Token) -> Token:
return Token("hardbreak", "br", 0)


@dataclass(slots=True)
class _ProjectTokens:
links: LinkContext
linked: int = 0
# The innermost open block decides how a source newline inside it is drawn.
lines: list[type[JoinedLines]] = field(default_factory=lambda: [JoinedLines])


class ProjectTokenRule(DeclaredFamily, affix="TokenRule"):
Expand All @@ -93,10 +111,30 @@ class InlineTokenRule(ProjectTokenRule):
@classmethod
def resolve(cls, renderer: ProjectTokenRenderer, token: Token, context: _ProjectTokens):
if token.children:
token.children = renderer.resolve(token.children, _ProjectTokens(context.links))
token.children = renderer.resolve(token.children, _ProjectTokens(context.links, lines=context.lines))
return [token]


class BlockquoteOpenTokenRule(ProjectTokenRule):
@classmethod
def resolve(cls, renderer: ProjectTokenRenderer, token: Token, context: _ProjectTokens):
context.lines.append(QuotedLines)
return [token]


class BlockquoteCloseTokenRule(ProjectTokenRule):
@classmethod
def resolve(cls, renderer: ProjectTokenRenderer, token: Token, context: _ProjectTokens):
context.lines.pop()
return [token]


class SoftbreakTokenRule(ProjectTokenRule):
@classmethod
def resolve(cls, renderer: ProjectTokenRenderer, token: Token, context: _ProjectTokens):
return [context.lines[-1].softbreak(token)]


class LinkOpenTokenRule(ProjectTokenRule):
@classmethod
def resolve(cls, renderer: ProjectTokenRenderer, token: Token, context: _ProjectTokens):
Expand Down
25 changes: 23 additions & 2 deletions src/toad/markdown_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

from markdown_it.token import Token
from rich.console import Console
from rich.markdown import CodeBlock, Markdown
from rich.markdown import BlockQuote, CodeBlock, Markdown
from rich.padding import Padding
from rich.segment import Segment
from rich.syntax import Syntax
from toad.line_blocks import GreenTextRole
from toad.markdown_links import MessageMarkdownParser, parse_markdown_syntax


Expand All @@ -24,17 +26,36 @@ def __rich_console__(self, console, options):
yield Padding(self.text, 1, style=self.text.style)


class GreenText(BlockQuote):
"""A quoted line keeps its `>` and reads in the greentext role's colour."""

def on_enter(self, context):
self.style = context.enter_style(context.console.line_theme.style(GreenTextRole))

def __rich_console__(self, console, options):
marker, new_line = Segment("> ", self.style), Segment("\n")
for line in console.render_lines(self.elements, options.update(width=options.max_width - 2), style=self.style):
yield marker
yield from line
yield new_line


class MarkdownStyleConsole(Console):
"""Rich builds its styled element tree; width-dependent rendering runs later."""

def __init__(self, line_theme, **options):
super().__init__(color_system=line_theme.color_system, **options)
self.line_theme = line_theme

def render(self, renderable, options=None):
self.parts.append(renderable)
return iter(())

def prepare(self, text, context):
self.parts = []
markdown = Markdown("")
markdown.elements = {**markdown.elements, "fence": StyledCodeBlock, "code_block": StyledCodeBlock}
markdown.elements = {**markdown.elements, "fence": StyledCodeBlock, "code_block": StyledCodeBlock,
"blockquote_open": GreenText}
markdown.parsed = MessageMarkdownParser(context).parse(text)
for segment in markdown.__rich_console__(self, self.options):
self.parts.append(segment)
Expand Down
4 changes: 1 addition & 3 deletions src/toad/widgets/transcript_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,9 +729,7 @@ def body(self, block: Body) -> LineBlock:
return block.prepare(self.markdown)

def markdown(self, block: Body) -> StyledBody:
console = MarkdownStyleConsole(
force_terminal=True, file=io.StringIO(), color_system=self.preparation.theme.color_system
)
console = MarkdownStyleConsole(self.preparation.theme, force_terminal=True, file=io.StringIO())
return StyledBody(block, console.prepare(block.text, self.context))

@handles(WireBody)
Expand Down
Loading