From af52e89928d73a90dc04fa5ecae75d3beef8eb07 Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 12 May 2026 14:16:07 -0400 Subject: [PATCH] fix: improve markdown rendering with proper wide character support --- src/render/index.ts | 6 ++++-- src/render/markdown.ts | 35 +++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/render/index.ts b/src/render/index.ts index 4d1028c..af24b89 100644 --- a/src/render/index.ts +++ b/src/render/index.ts @@ -48,10 +48,12 @@ export default class Renderer { private checkContainment(text: string, pipe: WriteStream = process.stdout) { const [columns, rows] = pipe.getWindowSize(); - this.charsSinceNL += text.length; + this.charsSinceNL += Bun.stringWidth(text); if (text.includes("\n")) { 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) { this.lineCount++; this.charsSinceNL = this.charsSinceNL - columns; diff --git a/src/render/markdown.ts b/src/render/markdown.ts index 7fbd70e..beaf244 100644 --- a/src/render/markdown.ts +++ b/src/render/markdown.ts @@ -1,4 +1,4 @@ -import { Table } from "@cliffy/table"; +import { Cell, Table } from "@cliffy/table"; import chalk from "chalk"; import { type BundledLanguage, @@ -12,6 +12,12 @@ import { codeToANSI } from "./codeToANSI"; let highlighter: HighlighterGeneric | undefined; let highligherLangs = ""; +class SWCell extends Cell { + public override get length() { + return Bun.stringWidth(this.toString(), { ambiguousIsNarrow: true }); + } +} + export async function renderMarkdown( text: PromiseLike | string, columns: number, @@ -32,8 +38,8 @@ export async function renderMarkdown( } catch {} } - let header: string[] = []; - let body: string[][] = []; + let header: SWCell[] = []; + let body: SWCell[][] = []; const rendered = Bun.markdown.render(md, { heading: (children, { level }) => { @@ -58,7 +64,8 @@ export async function renderMarkdown( const lines = Bun.wrapAnsi(`\n${children.trim()}`, columns - 2, { 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") .split("\n"); const toRender = lines @@ -86,7 +93,7 @@ export async function renderMarkdown( list: (children, { depth }) => { const trimmed = children .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}`; }, listItem: (children, { index, depth, ordered, start, checked }) => { @@ -113,20 +120,28 @@ export async function renderMarkdown( .header(header) .body(body) .border() - .maxColWidth(Math.floor(columns / header.length) - header.length) + .maxColWidth( + Math.floor(columns / header.length) - Math.floor(header.length / 2), + ) .toString(); header = []; body = []; return `${table}\n\n`; }, tr: (children) => { - if (children) body.push(children.split(":tablesep:").slice(1)); - return ""; + if (!children) return; + body.push( + children + .split(":tablesep:") + .slice(1) + .map((value) => new SWCell(value)), + ); + return null; }, td: (children) => `:tablesep:${children}`, th: (children) => { - header.push(chalk.red(children)); - return ""; + header.push(new SWCell(chalk.red(children))); + return null; }, strong: (children) => chalk.bold(children), emphasis: (children) => chalk.italic(children),