@earendil-works/pi-coding-agent
Version:
Coding agent CLI with read, bash, edit, write tools and session management
80 lines • 4.1 kB
JavaScript
import { getCapabilities, Marked } from "@earendil-works/pi-tui";
import { render, toAnsi } from "lovely-mermaid";
const markdownParser = new Marked();
function isMermaid(token) {
return token.type === "code" && token.lang?.trim().split(/\s+/, 1)[0]?.toLowerCase() === "mermaid";
}
function codeSpan(line) {
// Encode each diagram row as inline code (` ... `) so Markdown preserves its spacing and
// box-drawing characters. Use a non-breaking space for blank rows because an
// empty code span has no visible height.
const content = line || "\u00a0";
// CommonMark code spans use matching backtick delimiters, so choose one
// longer than any backtick run in the content (``hel`lo`` -> <code>hel`lo</code>).
// If the content starts or ends with a backtick, separating it from the
// delimiter with a space keeps that backtick as content; CommonMark removes
// the padding when rendering (`` `edge` `` -> <code>`edge`</code>).
// Mermaid labels can preserve backticks, for example:
// `┌──────────────┐ ┌──────────────┐`
// ```│ plain ` tick ├───▶│ two `` ticks │```
// `└──────────────┘ └──────────────┘`
const longestBacktickRun = Math.max(0, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length));
const fence = "`".repeat(longestBacktickRun + 1);
const padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
return `${fence}${padding}${content}${padding}${fence}`;
}
function themedLines(art, theme) {
const foreground = (color) => theme.getFgAnsi(color).slice(2, -1);
const ansiTheme = {
border: foreground("borderMuted"),
text: foreground("text"),
edge: foreground("accent"),
edgeLabel: foreground("text"),
title: `1;${foreground("accent")}`,
};
const linkLabels = getCapabilities().hyperlinks;
const displayArt = {
...art,
// Only osc 8 the text, not the whole box.
styled: art.styled.map((row) => row.map((span) => {
if (span.href === undefined || (linkLabels && span.role === "text"))
return span;
const unlinked = { ...span };
delete unlinked.href;
return unlinked;
})),
};
return toAnsi(displayArt, ansiTheme);
}
/** Create a transformer that replaces top-level Mermaid code blocks with Unicode terminal diagrams. */
export function createMermaidMarkdownTransformer(options) {
return (markdown, context) => {
const mode = options.getMode();
// Diagrams render in user and assistant text and in model-written summaries, not in thinking or other surfaces.
if (mode === "off" ||
(context.messageType !== "user" && context.messageType !== "assistant" && context.messageType !== "summary") ||
(context.isStreaming && mode !== "streaming")) {
return markdown;
}
return markdownParser
.lexer(markdown)
.map((token) => {
if (!isMermaid(token))
return token.raw;
const art = render(token.text);
if (!art || art.width > context.availableWidth)
return token.raw;
if (!context.isStreaming && art.warnings.length > 0) {
const suffix = art.warnings.length > 1 ? ` (+${art.warnings.length - 1} more)` : "";
const warning = `Mermaid diagram not rendered: ${art.warnings[0]}${suffix}`;
const styledWarning = options.theme ? options.theme.fg("warning", warning) : warning;
return `${token.raw}\n${codeSpan(styledWarning)} \n`;
}
const lines = options.theme ? themedLines(art, options.theme) : art.plain;
// Markdown hard breaks keep every diagram row on its own line.
return `${lines.map(codeSpan).join(" \n")}\n`;
})
.join("");
};
}
//# sourceMappingURL=mermaid.js.map