fix: improve markdown rendering with proper wide character support

This commit is contained in:
mitchell 2026-05-12 14:16:07 -04:00
parent 91175c70b6
commit af52e89928
2 changed files with 29 additions and 12 deletions

View file

@ -48,10 +48,12 @@ export default class Renderer {
private checkContainment(text: string, pipe: WriteStream = process.stdout) { private checkContainment(text: string, pipe: WriteStream = process.stdout) {
const [columns, rows] = pipe.getWindowSize(); const [columns, rows] = pipe.getWindowSize();
this.charsSinceNL += text.length; this.charsSinceNL += Bun.stringWidth(text);
if (text.includes("\n")) { if (text.includes("\n")) {
this.lineCount += text.match(/\n/g)?.length ?? 1; this.lineCount += text.match(/\n/g)?.length ?? 1;
this.charsSinceNL = text.length - text.lastIndexOf("\n") - 1; this.charsSinceNL =
Bun.stringWidth(text) -
Bun.stringWidth(text.slice(0, text.lastIndexOf("\n")));
} else if (this.charsSinceNL > columns) { } else if (this.charsSinceNL > columns) {
this.lineCount++; this.lineCount++;
this.charsSinceNL = this.charsSinceNL - columns; this.charsSinceNL = this.charsSinceNL - columns;

View file

@ -1,4 +1,4 @@
import { Table } from "@cliffy/table"; import { Cell, Table } from "@cliffy/table";
import chalk from "chalk"; import chalk from "chalk";
import { import {
type BundledLanguage, type BundledLanguage,
@ -12,6 +12,12 @@ import { codeToANSI } from "./codeToANSI";
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | undefined; let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | undefined;
let highligherLangs = ""; let highligherLangs = "";
class SWCell extends Cell {
public override get length() {
return Bun.stringWidth(this.toString(), { ambiguousIsNarrow: true });
}
}
export async function renderMarkdown( export async function renderMarkdown(
text: PromiseLike<string> | string, text: PromiseLike<string> | string,
columns: number, columns: number,
@ -32,8 +38,8 @@ export async function renderMarkdown(
} catch {} } catch {}
} }
let header: string[] = []; let header: SWCell[] = [];
let body: string[][] = []; let body: SWCell[][] = [];
const rendered = Bun.markdown.render(md, { const rendered = Bun.markdown.render(md, {
heading: (children, { level }) => { heading: (children, { level }) => {
@ -58,7 +64,8 @@ export async function renderMarkdown(
const lines = Bun.wrapAnsi(`\n${children.trim()}`, columns - 2, { const lines = Bun.wrapAnsi(`\n${children.trim()}`, columns - 2, {
trim: false, trim: false,
}) })
.replace(/\n([a-zA-Z-_]+)\n/g, "\n$1") .replace(/\n(\s?[a-zA-Z_-]+\s?)\n/g, "\n$1")
.replace(/\n+/g, "\n")
.concat("\n") .concat("\n")
.split("\n"); .split("\n");
const toRender = lines const toRender = lines
@ -86,7 +93,7 @@ export async function renderMarkdown(
list: (children, { depth }) => { list: (children, { depth }) => {
const trimmed = children const trimmed = children
.replace(/\n+/g, "\n") .replace(/\n+/g, "\n")
.replace(/\n([a-zA-Z-_]+)\n/g, "\n$1"); .replace(/\n(\s?[a-zA-Z_-]+\s?)\n/g, "\n$1");
return depth === 0 ? `${trimmed}\n` : `\n${trimmed}`; return depth === 0 ? `${trimmed}\n` : `\n${trimmed}`;
}, },
listItem: (children, { index, depth, ordered, start, checked }) => { listItem: (children, { index, depth, ordered, start, checked }) => {
@ -113,20 +120,28 @@ export async function renderMarkdown(
.header(header) .header(header)
.body(body) .body(body)
.border() .border()
.maxColWidth(Math.floor(columns / header.length) - header.length) .maxColWidth(
Math.floor(columns / header.length) - Math.floor(header.length / 2),
)
.toString(); .toString();
header = []; header = [];
body = []; body = [];
return `${table}\n\n`; return `${table}\n\n`;
}, },
tr: (children) => { tr: (children) => {
if (children) body.push(children.split(":tablesep:").slice(1)); if (!children) return;
return ""; body.push(
children
.split(":tablesep:")
.slice(1)
.map((value) => new SWCell(value)),
);
return null;
}, },
td: (children) => `:tablesep:${children}`, td: (children) => `:tablesep:${children}`,
th: (children) => { th: (children) => {
header.push(chalk.red(children)); header.push(new SWCell(chalk.red(children)));
return ""; return null;
}, },
strong: (children) => chalk.bold(children), strong: (children) => chalk.bold(children),
emphasis: (children) => chalk.italic(children), emphasis: (children) => chalk.italic(children),