fix(tui): isolate blockquote rendering from default text style

This commit is contained in:
Mario Zechner 2026-03-04 21:30:54 +01:00
parent 3e611c135b
commit f430dce00a

View file

@ -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));