refactor: replace cli-highlight with shiki
This commit is contained in:
parent
9cd8ccd4e2
commit
d822ba2141
4 changed files with 229 additions and 51 deletions
120
src/render/codeToANSI.ts
Normal file
120
src/render/codeToANSI.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/* The majority of this from @shikijs/cli, thanks guys.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Pine Wu
|
||||
Copyright (c) 2023 Anthony Fu <https://github.com/antfu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. */
|
||||
|
||||
import c from "chalk";
|
||||
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from "shiki";
|
||||
|
||||
// From @shikijs/vscode-textmate
|
||||
enum FontStyle {
|
||||
NotSet = -1,
|
||||
None = 0,
|
||||
Italic = 1,
|
||||
Bold = 2,
|
||||
Underline = 4,
|
||||
Strikethrough = 8,
|
||||
}
|
||||
|
||||
export function codeToANSI(
|
||||
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>,
|
||||
code: string,
|
||||
lang: BundledLanguage,
|
||||
theme: BundledTheme,
|
||||
): string {
|
||||
let output = "";
|
||||
|
||||
const lines = highlighter.codeToTokensBase(code, {
|
||||
lang,
|
||||
theme,
|
||||
});
|
||||
const themeReg = highlighter.getTheme(theme);
|
||||
|
||||
for (const line of lines) {
|
||||
for (const token of line) {
|
||||
let text = token.content;
|
||||
const color = token.color || themeReg.fg;
|
||||
if (color) text = c.hex(hexApplyAlpha(color, themeReg.type))(text);
|
||||
if (token.fontStyle) {
|
||||
if (token.fontStyle & FontStyle.Bold) text = c.bold(text);
|
||||
if (token.fontStyle & FontStyle.Italic) text = c.italic(text);
|
||||
if (token.fontStyle & FontStyle.Underline) text = c.underline(text);
|
||||
if (token.fontStyle & FontStyle.Strikethrough)
|
||||
text = c.strikethrough(text);
|
||||
}
|
||||
output += text;
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const RE_HASH = /#/;
|
||||
|
||||
function normalizeHex(hex: string): string {
|
||||
hex = hex.replace(RE_HASH, "");
|
||||
if (hex.length === 3 && hex[0])
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
if (hex.length === 4 && hex[0])
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
||||
if (hex.length === 6) hex = `${hex}ff`;
|
||||
return hex.toLowerCase();
|
||||
}
|
||||
|
||||
function hexToRgba(hex: string): {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
} {
|
||||
hex = normalizeHex(hex);
|
||||
const r = Number.parseInt(hex.slice(0, 2), 16);
|
||||
const g = Number.parseInt(hex.slice(2, 4), 16);
|
||||
const b = Number.parseInt(hex.slice(4, 6), 16);
|
||||
const a = Number.parseInt(hex.slice(6, 8), 16) / 255;
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
function RgbToHex(r: number, g: number, b: number): string {
|
||||
return [r, g, b]
|
||||
.map((x) => {
|
||||
const hex = x.toString(16);
|
||||
return hex.length === 1 ? `0${hex}` : hex;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
export function hexApplyAlpha(
|
||||
hex: string,
|
||||
type: "dark" | "light" = "dark",
|
||||
): string {
|
||||
const { r, g, b, a } = hexToRgba(hex);
|
||||
if (type === "dark") return RgbToHex(r * a, g * a, b * a);
|
||||
else
|
||||
return RgbToHex(
|
||||
r * a + 255 * (1 - a),
|
||||
g * a + 255 * (1 - a),
|
||||
b * a + 255 * (1 - a),
|
||||
);
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
import chalk from "chalk";
|
||||
import { highlight } from "cli-highlight";
|
||||
import CliTable3 from "cli-table3";
|
||||
import {
|
||||
type BundledLanguage,
|
||||
type BundledTheme,
|
||||
createHighlighter,
|
||||
type HighlighterGeneric,
|
||||
} from "shiki";
|
||||
import terminalLink from "terminal-link";
|
||||
import { codeToANSI } from "./codeToANSI";
|
||||
|
||||
const originalErr = console.error;
|
||||
console.error = (...data: unknown[]) => {
|
||||
if (data.join("").includes("Could not find the language")) return;
|
||||
originalErr(...data);
|
||||
};
|
||||
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | undefined;
|
||||
let highligherLangs = "";
|
||||
|
||||
export async function renderMarkdown(
|
||||
text: PromiseLike<string> | string,
|
||||
|
|
@ -15,6 +18,20 @@ export async function renderMarkdown(
|
|||
): Promise<string> {
|
||||
const md = await text;
|
||||
|
||||
const langs = md
|
||||
.match(/```[a-z]+\n/g)
|
||||
?.map((lang) => lang.replace("```", "").trimEnd());
|
||||
|
||||
if (langs && langs?.join() !== highligherLangs) {
|
||||
try {
|
||||
highligherLangs = langs.join();
|
||||
highlighter = await createHighlighter({
|
||||
langs,
|
||||
themes: ["kanagawa-wave", "kanagawa-lotus"],
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let currentTable = new CliTable3();
|
||||
|
||||
return Bun.markdown.render(md, {
|
||||
|
|
@ -41,20 +58,21 @@ export async function renderMarkdown(
|
|||
code: (children, meta) => {
|
||||
let toRender = children;
|
||||
|
||||
if (meta?.language) {
|
||||
if (highlighter && meta?.language) {
|
||||
try {
|
||||
toRender = highlight(children, {
|
||||
language: meta.language,
|
||||
ignoreIllegals: true,
|
||||
theme: { string: chalk.greenBright },
|
||||
});
|
||||
toRender = codeToANSI(
|
||||
highlighter,
|
||||
children,
|
||||
meta.language as BundledLanguage,
|
||||
"kanagawa-wave",
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return `\`\`\`${chalk.green(meta?.language)}\n${toRender}\n\`\`\`\n`;
|
||||
return `\`\`\`${chalk.blue(meta?.language)}\n${toRender}\n\`\`\`\n`;
|
||||
},
|
||||
list: (children, { depth }) =>
|
||||
`${depth > 0 ? "\n" : ""}${children}${depth === 0 ? "\n" : ""}`,
|
||||
`${depth > 0 ? "\n" : ""}${children.trimEnd()}${depth === 0 ? "\n\n" : ""}`,
|
||||
listItem: (children, { index, depth, ordered, start, checked }) => {
|
||||
const indent = " ".repeat(depth);
|
||||
let marker = "";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue