fix(tui): remove trailing markdown block spacing (#2152)
This commit is contained in:
committed by
GitHub
parent
c9a3d14aa5
commit
962c12444d
@@ -282,7 +282,7 @@ export class Markdown implements Component {
|
|||||||
styledHeading = this.theme.heading(this.theme.bold(headingPrefix + headingText));
|
styledHeading = this.theme.heading(this.theme.bold(headingPrefix + headingText));
|
||||||
}
|
}
|
||||||
lines.push(styledHeading);
|
lines.push(styledHeading);
|
||||||
if (nextTokenType !== "space") {
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
lines.push(""); // Add spacing after headings (unless space token follows)
|
lines.push(""); // Add spacing after headings (unless space token follows)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -314,7 +314,7 @@ export class Markdown implements Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines.push(this.theme.codeBlockBorder("```"));
|
lines.push(this.theme.codeBlockBorder("```"));
|
||||||
if (nextTokenType !== "space") {
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
lines.push(""); // Add spacing after code blocks (unless space token follows)
|
lines.push(""); // Add spacing after code blocks (unless space token follows)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -329,7 +329,7 @@ export class Markdown implements Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "table": {
|
case "table": {
|
||||||
const tableLines = this.renderTable(token as any, width, styleContext);
|
const tableLines = this.renderTable(token as any, width, nextTokenType, styleContext);
|
||||||
lines.push(...tableLines);
|
lines.push(...tableLines);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@ export class Markdown implements Component {
|
|||||||
lines.push(this.theme.quoteBorder("│ ") + wrappedLine);
|
lines.push(this.theme.quoteBorder("│ ") + wrappedLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nextTokenType !== "space") {
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
lines.push(""); // Add spacing after blockquotes (unless space token follows)
|
lines.push(""); // Add spacing after blockquotes (unless space token follows)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -385,7 +385,7 @@ export class Markdown implements Component {
|
|||||||
|
|
||||||
case "hr":
|
case "hr":
|
||||||
lines.push(this.theme.hr("─".repeat(Math.min(width, 80))));
|
lines.push(this.theme.hr("─".repeat(Math.min(width, 80))));
|
||||||
if (nextTokenType !== "space") {
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
lines.push(""); // Add spacing after horizontal rules (unless space token follows)
|
lines.push(""); // Add spacing after horizontal rules (unless space token follows)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -638,6 +638,7 @@ export class Markdown implements Component {
|
|||||||
private renderTable(
|
private renderTable(
|
||||||
token: Token & { header: any[]; rows: any[][]; raw?: string },
|
token: Token & { header: any[]; rows: any[][]; raw?: string },
|
||||||
availableWidth: number,
|
availableWidth: number,
|
||||||
|
nextTokenType?: string,
|
||||||
styleContext?: InlineStyleContext,
|
styleContext?: InlineStyleContext,
|
||||||
): string[] {
|
): string[] {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
@@ -654,7 +655,9 @@ export class Markdown implements Component {
|
|||||||
if (availableForCells < numCols) {
|
if (availableForCells < numCols) {
|
||||||
// Too narrow to render a stable table. Fall back to raw markdown.
|
// Too narrow to render a stable table. Fall back to raw markdown.
|
||||||
const fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : [];
|
const fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : [];
|
||||||
fallbackLines.push("");
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
|
fallbackLines.push("");
|
||||||
|
}
|
||||||
return fallbackLines;
|
return fallbackLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,7 +803,9 @@ export class Markdown implements Component {
|
|||||||
const bottomBorderCells = columnWidths.map((w) => "─".repeat(w));
|
const bottomBorderCells = columnWidths.map((w) => "─".repeat(w));
|
||||||
lines.push(`└─${bottomBorderCells.join("─┴─")}─┘`);
|
lines.push(`└─${bottomBorderCells.join("─┴─")}─┘`);
|
||||||
|
|
||||||
lines.push(""); // Add spacing after table
|
if (nextTokenType && nextTokenType !== "space") {
|
||||||
|
lines.push(""); // Add spacing after table
|
||||||
|
}
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -447,6 +447,26 @@ describe("Markdown component", () => {
|
|||||||
const tableRow = plainLines.find((line) => line.includes("│"));
|
const tableRow = plainLines.find((line) => line.includes("│"));
|
||||||
assert.ok(tableRow?.startsWith(" "), "Table should have left padding");
|
assert.ok(tableRow?.startsWith(" "), "Table should have left padding");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not add a trailing blank line when table is the last rendered block", () => {
|
||||||
|
const markdown = new Markdown(
|
||||||
|
`| Name |
|
||||||
|
| --- |
|
||||||
|
| Alice |`,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
defaultMarkdownTheme,
|
||||||
|
);
|
||||||
|
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
plainLines.at(-1),
|
||||||
|
"",
|
||||||
|
`Expected table to end without a blank line: ${JSON.stringify(plainLines)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Combined features", () => {
|
describe("Combined features", () => {
|
||||||
@@ -601,6 +621,52 @@ again, hello world`,
|
|||||||
`Expected 1 empty line after code block, but found ${emptyLineCount}. Lines after backticks: ${JSON.stringify(afterBackticks.slice(0, 5))}`,
|
`Expected 1 empty line after code block, but found ${emptyLineCount}. Lines after backticks: ${JSON.stringify(afterBackticks.slice(0, 5))}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should normalize paragraph and code block spacing to one blank line", () => {
|
||||||
|
const cases = [
|
||||||
|
`hello this is text
|
||||||
|
\`\`\`
|
||||||
|
code block
|
||||||
|
\`\`\`
|
||||||
|
more text`,
|
||||||
|
`hello this is text
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
code block
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
more text`,
|
||||||
|
];
|
||||||
|
const expectedLines = ["hello this is text", "", "```", " code block", "```", "", "more text"];
|
||||||
|
|
||||||
|
for (const text of cases) {
|
||||||
|
const markdown = new Markdown(text, 0, 0, defaultMarkdownTheme);
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
plainLines,
|
||||||
|
expectedLines,
|
||||||
|
`Unexpected spacing for markdown: ${JSON.stringify(text)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add a trailing blank line when code block is the last rendered block", () => {
|
||||||
|
const cases = ["```js\nconst hello = 'world';\n```", "hello world\n\n```js\nconst hello = 'world';\n```"];
|
||||||
|
|
||||||
|
for (const text of cases) {
|
||||||
|
const markdown = new Markdown(text, 0, 0, defaultMarkdownTheme);
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
plainLines.at(-1),
|
||||||
|
"",
|
||||||
|
`Expected code block to end without a blank line: ${JSON.stringify(plainLines)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Spacing after dividers", () => {
|
describe("Spacing after dividers", () => {
|
||||||
@@ -631,6 +697,18 @@ again, hello world`,
|
|||||||
`Expected 1 empty line after divider, but found ${emptyLineCount}. Lines after divider: ${JSON.stringify(afterDivider.slice(0, 5))}`,
|
`Expected 1 empty line after divider, but found ${emptyLineCount}. Lines after divider: ${JSON.stringify(afterDivider.slice(0, 5))}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not add a trailing blank line when divider is the last rendered block", () => {
|
||||||
|
const markdown = new Markdown("---", 0, 0, defaultMarkdownTheme);
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
plainLines.at(-1),
|
||||||
|
"",
|
||||||
|
`Expected divider to end without a blank line: ${JSON.stringify(plainLines)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Spacing after headings", () => {
|
describe("Spacing after headings", () => {
|
||||||
@@ -659,6 +737,18 @@ This is a paragraph`,
|
|||||||
`Expected 1 empty line after heading, but found ${emptyLineCount}. Lines after heading: ${JSON.stringify(afterHeading.slice(0, 5))}`,
|
`Expected 1 empty line after heading, but found ${emptyLineCount}. Lines after heading: ${JSON.stringify(afterHeading.slice(0, 5))}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not add a trailing blank line when heading is the last rendered block", () => {
|
||||||
|
const markdown = new Markdown("# Hello", 0, 0, defaultMarkdownTheme);
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
plainLines.at(-1),
|
||||||
|
"",
|
||||||
|
`Expected heading to end without a blank line: ${JSON.stringify(plainLines)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Spacing after blockquotes", () => {
|
describe("Spacing after blockquotes", () => {
|
||||||
@@ -689,6 +779,18 @@ again, hello world`,
|
|||||||
`Expected 1 empty line after blockquote, but found ${emptyLineCount}. Lines after quote: ${JSON.stringify(afterQuote.slice(0, 5))}`,
|
`Expected 1 empty line after blockquote, but found ${emptyLineCount}. Lines after quote: ${JSON.stringify(afterQuote.slice(0, 5))}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not add a trailing blank line when blockquote is the last rendered block", () => {
|
||||||
|
const markdown = new Markdown("> This is a quote", 0, 0, defaultMarkdownTheme);
|
||||||
|
const lines = markdown.render(80);
|
||||||
|
const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
plainLines.at(-1),
|
||||||
|
"",
|
||||||
|
`Expected blockquote to end without a blank line: ${JSON.stringify(plainLines)}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Blockquotes with multiline content", () => {
|
describe("Blockquotes with multiline content", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user