feat: statically render the jet ansi text
This commit is contained in:
parent
e6a9b1a111
commit
5cbe032c23
7 changed files with 74 additions and 51 deletions
132
vite-plugin-ansi.ts
Normal file
132
vite-plugin-ansi.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import Anser, { type AnserJsonEntry } from "anser";
|
||||
import { escapeCarriageReturn } from "escape-carriage";
|
||||
import fs from "node:fs";
|
||||
import type { Plugin } from "vite";
|
||||
|
||||
const colorMap: Record<string, string> = {
|
||||
"ansi-black": "text-[var(--black)]",
|
||||
"ansi-red": "text-[var(--red)]",
|
||||
"ansi-green": "text-[var(--green)]",
|
||||
"ansi-yellow": "text-[var(--brown)]",
|
||||
"ansi-blue": "text-[var(--blue)]",
|
||||
"ansi-magenta": "text-[var(--magenta)]",
|
||||
"ansi-cyan": "text-[var(--cyan)]",
|
||||
"ansi-white": "text-[var(--light-gray)]",
|
||||
"ansi-bright-black": "text-[var(--dark-gray)]",
|
||||
"ansi-bright-red": "text-[var(--light-red)]",
|
||||
"ansi-bright-green": "text-[var(--light-green)]",
|
||||
"ansi-bright-yellow": "text-[var(--yellow)]",
|
||||
"ansi-bright-blue": "text-[var(--light-blue)]",
|
||||
"ansi-bright-magenta": "text-[var(--light-magenta)]",
|
||||
"ansi-bright-cyan": "text-[var(--light-cyan)]",
|
||||
"ansi-bright-white": "text-[var(--white)]",
|
||||
};
|
||||
|
||||
const bgColorMap: Record<string, string> = {
|
||||
"ansi-black": "bg-transparent",
|
||||
"ansi-red": "bg-[var(--red)]",
|
||||
"ansi-green": "bg-[var(--green)]",
|
||||
"ansi-yellow": "bg-[var(--brown)]",
|
||||
"ansi-blue": "bg-[var(--blue)]",
|
||||
"ansi-magenta": "bg-[var(--magenta)]",
|
||||
"ansi-cyan": "bg-[var(--cyan)]",
|
||||
"ansi-white": "bg-[var(--light-gray)]",
|
||||
"ansi-bright-black": "bg-[var(--dark-gray)]",
|
||||
"ansi-bright-red": "bg-[var(--light-red)]",
|
||||
"ansi-bright-green": "bg-[var(--light-green)]",
|
||||
"ansi-bright-yellow": "bg-[var(--yellow)]",
|
||||
"ansi-bright-blue": "bg-[var(--light-blue)]",
|
||||
"ansi-bright-magenta": "bg-[var(--light-magenta)]",
|
||||
"ansi-bright-cyan": "bg-[var(--light-cyan)]",
|
||||
"ansi-bright-white": "bg-[var(--white)]",
|
||||
};
|
||||
|
||||
const decorationMap: Record<string, string> = {
|
||||
bold: "font-bold",
|
||||
dim: "opacity-50",
|
||||
italic: "italic",
|
||||
hidden: "invisible",
|
||||
strikethrough: "line-through",
|
||||
underline: "underline",
|
||||
blink: "animate-pulse",
|
||||
};
|
||||
|
||||
function fixBackspace(txt: string): string {
|
||||
let tmp = txt;
|
||||
do {
|
||||
txt = tmp;
|
||||
tmp = txt.replace(/[^\n]\x08/gm, "");
|
||||
} while (tmp.length < txt.length);
|
||||
return txt;
|
||||
}
|
||||
|
||||
function createClass(bundle: AnserJsonEntry): string | null {
|
||||
const classes: string[] = [];
|
||||
if (bundle.bg && bgColorMap[bundle.bg]) {
|
||||
classes.push(bgColorMap[bundle.bg]!);
|
||||
}
|
||||
if (bundle.fg && colorMap[bundle.fg]) {
|
||||
classes.push(colorMap[bundle.fg]!);
|
||||
}
|
||||
if (bundle.decoration && decorationMap[bundle.decoration]) {
|
||||
classes.push(decorationMap[bundle.decoration]!);
|
||||
}
|
||||
return classes.length ? classes.join(" ") : null;
|
||||
}
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function renderAnsiToHtml(raw: string): string {
|
||||
const input = escapeCarriageReturn(fixBackspace(raw));
|
||||
const bundles = Anser.ansiToJson(input, {
|
||||
json: true,
|
||||
remove_empty: true,
|
||||
use_classes: true,
|
||||
});
|
||||
|
||||
const spans = bundles
|
||||
.map((bundle) => {
|
||||
const cls = createClass(bundle);
|
||||
const content = escapeHtml(bundle.content);
|
||||
if (cls) {
|
||||
return `<span class="${cls}">${content}</span>`;
|
||||
}
|
||||
return `<span>${content}</span>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return `<div class="flex justify-center"><pre style="text-align:left"><code>${spans}</code></pre></div>`;
|
||||
}
|
||||
|
||||
const ANSI_SUFFIX = "?ansi";
|
||||
|
||||
export default function ansiPlugin(): Plugin {
|
||||
return {
|
||||
name: "vite-plugin-ansi",
|
||||
enforce: "pre",
|
||||
async resolveId(source, importer, options) {
|
||||
if (!source.endsWith(ANSI_SUFFIX)) return;
|
||||
const bare = source.slice(0, -ANSI_SUFFIX.length);
|
||||
const resolved = await this.resolve(bare, importer, {
|
||||
...options,
|
||||
skipSelf: true,
|
||||
});
|
||||
if (resolved) {
|
||||
return resolved.id + ANSI_SUFFIX;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (!id.endsWith(ANSI_SUFFIX)) return;
|
||||
const filePath = id.slice(0, -ANSI_SUFFIX.length);
|
||||
const raw = fs.readFileSync(filePath, "utf-8");
|
||||
const html = renderAnsiToHtml(raw);
|
||||
return `export default ${JSON.stringify(html)};`;
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue