From f430dce00aa09d6620ebd72879e9fe3268a779ca Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 4 Mar 2026 21:30:54 +0100 Subject: [PATCH] fix(tui): isolate blockquote rendering from default text style --- packages/tui/src/components/markdown.ts | 53 +++++++++++++++++-------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index cb0cc1c9..7ab92689 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -260,14 +260,19 @@ export class Markdown implements Component { }; } - private renderToken(token: Token, width: number, nextTokenType?: string): string[] { + private renderToken( + token: Token, + width: number, + nextTokenType?: string, + styleContext?: InlineStyleContext, + ): string[] { const lines: string[] = []; switch (token.type) { case "heading": { const headingLevel = token.depth; const headingPrefix = `${"#".repeat(headingLevel)} `; - const headingText = this.renderInlineTokens(token.tokens || []); + const headingText = this.renderInlineTokens(token.tokens || [], styleContext); let styledHeading: string; if (headingLevel === 1) { styledHeading = this.theme.heading(this.theme.bold(this.theme.underline(headingText))); @@ -284,7 +289,7 @@ export class Markdown implements Component { } case "paragraph": { - const paragraphText = this.renderInlineTokens(token.tokens || []); + const paragraphText = this.renderInlineTokens(token.tokens || [], styleContext); lines.push(paragraphText); // Don't add spacing if next token is space or list if (nextTokenType && nextTokenType !== "list" && nextTokenType !== "space") { @@ -316,7 +321,7 @@ export class Markdown implements Component { } case "list": { - const listLines = this.renderList(token as any, 0); + const listLines = this.renderList(token as any, 0, styleContext); lines.push(...listLines); // Don't add spacing after lists if a space token follows // (the space token will handle it) @@ -324,7 +329,7 @@ export class Markdown implements Component { } case "table": { - const tableLines = this.renderTable(token as any, width); + const tableLines = this.renderTable(token as any, width, styleContext); lines.push(...tableLines); break; } @@ -345,12 +350,19 @@ export class Markdown implements Component { // Blockquotes contain block-level tokens (paragraph, list, code, etc.), so render // children with renderToken() instead of renderInlineTokens(). + // Default message style should not apply inside blockquotes. + const quoteInlineStyleContext: InlineStyleContext = { + applyText: (text: string) => text, + stylePrefix: "", + }; const quoteTokens = token.tokens || []; const renderedQuoteLines: string[] = []; for (let i = 0; i < quoteTokens.length; i++) { const quoteToken = quoteTokens[i]; const nextQuoteToken = quoteTokens[i + 1]; - renderedQuoteLines.push(...this.renderToken(quoteToken, quoteContentWidth, nextQuoteToken?.type)); + renderedQuoteLines.push( + ...this.renderToken(quoteToken, quoteContentWidth, nextQuoteToken?.type, quoteInlineStyleContext), + ); } // Avoid rendering an extra empty quote line before the outer blockquote spacing. @@ -490,7 +502,11 @@ export class Markdown implements Component { /** * Render a list with proper nesting support */ - private renderList(token: Token & { items: any[]; ordered: boolean; start?: number }, depth: number): string[] { + private renderList( + token: Token & { items: any[]; ordered: boolean; start?: number }, + depth: number, + styleContext?: InlineStyleContext, + ): string[] { const lines: string[] = []; const indent = " ".repeat(depth); // Use the list's start property (defaults to 1 for ordered lists) @@ -501,7 +517,7 @@ export class Markdown implements Component { const bullet = token.ordered ? `${startNumber + i}. ` : "- "; // Process item tokens to handle nested lists - const itemLines = this.renderListItem(item.tokens || [], depth); + const itemLines = this.renderListItem(item.tokens || [], depth, styleContext); if (itemLines.length > 0) { // First line - check if it's a nested list @@ -542,23 +558,25 @@ export class Markdown implements Component { * Render list item tokens, handling nested lists * Returns lines WITHOUT the parent indent (renderList will add it) */ - private renderListItem(tokens: Token[], parentDepth: number): string[] { + private renderListItem(tokens: Token[], parentDepth: number, styleContext?: InlineStyleContext): string[] { const lines: string[] = []; for (const token of tokens) { if (token.type === "list") { // Nested list - render with one additional indent level // These lines will have their own indent, so we just add them as-is - const nestedLines = this.renderList(token as any, parentDepth + 1); + const nestedLines = this.renderList(token as any, parentDepth + 1, styleContext); lines.push(...nestedLines); } else if (token.type === "text") { // Text content (may have inline tokens) const text = - token.tokens && token.tokens.length > 0 ? this.renderInlineTokens(token.tokens) : token.text || ""; + token.tokens && token.tokens.length > 0 + ? this.renderInlineTokens(token.tokens, styleContext) + : token.text || ""; lines.push(text); } else if (token.type === "paragraph") { // Paragraph in list item - const text = this.renderInlineTokens(token.tokens || []); + const text = this.renderInlineTokens(token.tokens || [], styleContext); lines.push(text); } else if (token.type === "code") { // Code block in list item @@ -578,7 +596,7 @@ export class Markdown implements Component { lines.push(this.theme.codeBlockBorder("```")); } else { // Other token types - try to render as inline - const text = this.renderInlineTokens([token]); + const text = this.renderInlineTokens([token], styleContext); if (text) { lines.push(text); } @@ -620,6 +638,7 @@ export class Markdown implements Component { private renderTable( token: Token & { header: any[]; rows: any[][]; raw?: string }, availableWidth: number, + styleContext?: InlineStyleContext, ): string[] { const lines: string[] = []; const numCols = token.header.length; @@ -645,13 +664,13 @@ export class Markdown implements Component { const naturalWidths: number[] = []; const minWordWidths: number[] = []; for (let i = 0; i < numCols; i++) { - const headerText = this.renderInlineTokens(token.header[i].tokens || []); + const headerText = this.renderInlineTokens(token.header[i].tokens || [], styleContext); naturalWidths[i] = visibleWidth(headerText); minWordWidths[i] = Math.max(1, this.getLongestWordWidth(headerText, maxUnbrokenWordWidth)); } for (const row of token.rows) { for (let i = 0; i < row.length; i++) { - const cellText = this.renderInlineTokens(row[i].tokens || []); + const cellText = this.renderInlineTokens(row[i].tokens || [], styleContext); naturalWidths[i] = Math.max(naturalWidths[i] || 0, visibleWidth(cellText)); minWordWidths[i] = Math.max( minWordWidths[i] || 1, @@ -736,7 +755,7 @@ export class Markdown implements Component { // Render header with wrapping const headerCellLines: string[][] = token.header.map((cell, i) => { - const text = this.renderInlineTokens(cell.tokens || []); + const text = this.renderInlineTokens(cell.tokens || [], styleContext); return this.wrapCellText(text, columnWidths[i]); }); const headerLineCount = Math.max(...headerCellLines.map((c) => c.length)); @@ -759,7 +778,7 @@ export class Markdown implements Component { for (let rowIndex = 0; rowIndex < token.rows.length; rowIndex++) { const row = token.rows[rowIndex]; const rowCellLines: string[][] = row.map((cell, i) => { - const text = this.renderInlineTokens(cell.tokens || []); + const text = this.renderInlineTokens(cell.tokens || [], styleContext); return this.wrapCellText(text, columnWidths[i]); }); const rowLineCount = Math.max(...rowCellLines.map((c) => c.length));