diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index aa2166277..9194c2251 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -596,8 +596,21 @@ export class Markdown implements Component { return quoteStyle(lineWithReappliedStyle); }; + // With no room for another border ("│ " plus at least one content column), render the + // nested content without one. Otherwise each extra level re-wraps every child line at + // width 1 and the output grows exponentially with nesting depth. + if (width < 3) { + const quoteTokens = token.tokens || []; + for (let i = 0; i < quoteTokens.length; i++) { + for (const line of this.renderToken(quoteTokens[i], width, quoteTokens[i + 1]?.type, styleContext)) { + lines.push(line); + } + } + break; + } + // Calculate available width for quote content (subtract border "│ " = 2 chars) - const quoteContentWidth = Math.max(1, width - 2); + const quoteContentWidth = width - 2; // Blockquotes contain block-level tokens (paragraph, list, code, etc.), so render // children with renderToken() instead of renderInlineTokens(). @@ -611,9 +624,14 @@ export class Markdown implements Component { 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, quoteInlineStyleContext), - ); + for (const line of this.renderToken( + quoteToken, + quoteContentWidth, + nextQuoteToken?.type, + quoteInlineStyleContext, + )) { + renderedQuoteLines.push(line); + } } // Avoid rendering an extra empty quote line before the outer blockquote spacing. diff --git a/packages/tui/test/markdown.test.ts b/packages/tui/test/markdown.test.ts index 1cc43381b..38e6ceb5a 100644 --- a/packages/tui/test/markdown.test.ts +++ b/packages/tui/test/markdown.test.ts @@ -1367,6 +1367,18 @@ again, hello world`, }); }); + describe("Deeply nested blockquotes", () => { + // Regression test for https://github.com/earendil-works/pi/issues/11272 + it("should render quotes nested deeper than the width allows without exploding", () => { + const markdown = new Markdown(`${">".repeat(60)}x`, 1, 0, defaultMarkdownTheme); + const lines = markdown.render(80); + const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd()); + + assert.strictEqual(plainLines.length, 1, `Expected a single line: ${JSON.stringify(plainLines)}`); + assert.ok(plainLines[0].endsWith("x"), `Expected content to be kept: ${JSON.stringify(plainLines)}`); + }); + }); + describe("Blockquotes with multiline content", () => { it("should apply consistent styling to all lines in lazy continuation blockquote", () => { // Markdown "lazy continuation" - second line without > is still part of the quote