Skip to content

Commit 2eff724

Browse files
committed
fix(markdown): preserve list item wrappers for block math
1 parent e2bb038 commit 2eff724

2 files changed

Lines changed: 57 additions & 14 deletions

File tree

src/features/messages/utils/backslashMathScanner.test.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe("normalizeBackslashMathDelimiters", () => {
104104
expect(normalized).toBe("f$x$ + $n$th + a$x$b");
105105
});
106106

107-
it("preserves list and blockquote prefixes when converting block delimiters", () => {
107+
it("keeps list-marker prefix on opening fence and continuation prefix on closing fence", () => {
108108
const input = [
109109
"- \\[",
110110
" x+y",
@@ -118,7 +118,7 @@ describe("normalizeBackslashMathDelimiters", () => {
118118
const normalized = normalizeBackslashMathDelimiters(input);
119119

120120
expect(normalized).toBe([
121-
" $$",
121+
"- $$",
122122
" x+y",
123123
" $$",
124124
"",
@@ -128,7 +128,7 @@ describe("normalizeBackslashMathDelimiters", () => {
128128
].join("\n"));
129129
});
130130

131-
it("emits list-content fences (not list-marker fences) for ordered items", () => {
131+
it("keeps ordered-list marker on opening fence and continuation indentation on closing fence", () => {
132132
const input = [
133133
"1. \\[",
134134
" E = mc^2",
@@ -138,12 +138,28 @@ describe("normalizeBackslashMathDelimiters", () => {
138138
const normalized = normalizeBackslashMathDelimiters(input);
139139

140140
expect(normalized).toBe([
141-
" $$",
141+
"1. $$",
142142
" E = mc^2",
143143
" $$",
144144
].join("\n"));
145145
});
146146

147+
it("keeps quote+list marker on opening fence and continuation prefix on closing fence", () => {
148+
const input = [
149+
"> - \\[",
150+
"> x+y",
151+
"> \\]",
152+
].join("\n");
153+
154+
const normalized = normalizeBackslashMathDelimiters(input);
155+
156+
expect(normalized).toBe([
157+
"> - $$",
158+
"> x+y",
159+
"> $$",
160+
].join("\n"));
161+
});
162+
147163
it("is idempotent and keeps unbalanced delimiters literal", () => {
148164
const input = [
149165
"Start \\(x^2",
@@ -160,6 +176,20 @@ describe("normalizeBackslashMathDelimiters", () => {
160176
expect(once).toContain("Balanced $z$");
161177
expect(twice).toBe(once);
162178
});
179+
180+
it("keeps escaped block delimiters literal while converting real block delimiters", () => {
181+
const input = [
182+
String.raw`Escaped: \\[ literal \\]`,
183+
String.raw`\[`,
184+
"E=mc^2",
185+
String.raw`\]`,
186+
].join("\n");
187+
188+
const normalized = normalizeBackslashMathDelimiters(input);
189+
190+
expect(normalized).toContain(String.raw`Escaped: \\[ literal \\]`);
191+
expect(normalized).toContain(["$$", "E=mc^2", "$$"].join("\n"));
192+
});
163193
});
164194

165195
describe("normalizeBackslashMathDelimiters extraction-shaped coverage", () => {

src/features/messages/utils/backslashMathScanner.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -382,33 +382,41 @@ function convertBackslashBlockDelimiters(value: string) {
382382
const output: string[] = [];
383383
let collectingBlock = false;
384384
let blockLines: string[] = [];
385-
let activeFencePrefix = "";
385+
let activeStartFencePrefix = "";
386+
let activeContinuationFencePrefix = "";
386387
let activeQuoteDepth = 0;
387388
let activeOpenLine = "";
388389

389-
const toFencePrefix = (prefix: string) =>
390+
const toContinuationFencePrefix = (prefix: string) =>
390391
prefix.replace(LIST_MARKER_PREFIX_PATTERN, (marker) => " ".repeat(marker.length));
391392

392393
for (const line of lines) {
393394
const { prefix, content, quoteDepth } = parseLinePrefix(line);
395+
const normalizedContent = content.trimStart();
394396

395397
if (!collectingBlock) {
396-
const singleLineMatch = content.match(BLOCK_LATEX_SINGLE_LINE_PATTERN);
398+
const singleLineMatch = normalizedContent.match(BLOCK_LATEX_SINGLE_LINE_PATTERN);
397399
if (singleLineMatch) {
398400
const body = (singleLineMatch[1] ?? "").trim();
399401
if (!body) {
400402
output.push(line);
401403
continue;
402404
}
403-
const fencePrefix = toFencePrefix(prefix);
404-
output.push(`${fencePrefix}$$`, `${fencePrefix}${body}`, `${fencePrefix}$$`);
405+
const startFencePrefix = prefix;
406+
const continuationFencePrefix = toContinuationFencePrefix(prefix);
407+
output.push(
408+
`${startFencePrefix}$$`,
409+
`${continuationFencePrefix}${body}`,
410+
`${continuationFencePrefix}$$`,
411+
);
405412
continue;
406413
}
407414

408-
if (BLOCK_LATEX_OPEN_PATTERN.test(content)) {
415+
if (BLOCK_LATEX_OPEN_PATTERN.test(normalizedContent)) {
409416
collectingBlock = true;
410417
blockLines = [];
411-
activeFencePrefix = toFencePrefix(prefix);
418+
activeStartFencePrefix = prefix;
419+
activeContinuationFencePrefix = toContinuationFencePrefix(prefix);
412420
activeQuoteDepth = quoteDepth;
413421
activeOpenLine = line;
414422
continue;
@@ -418,15 +426,20 @@ function convertBackslashBlockDelimiters(value: string) {
418426
continue;
419427
}
420428

421-
if (BLOCK_LATEX_CLOSE_PATTERN.test(content) && quoteDepth === activeQuoteDepth) {
429+
if (BLOCK_LATEX_CLOSE_PATTERN.test(normalizedContent) && quoteDepth === activeQuoteDepth) {
422430
if (blockLines.some((bodyLine) => bodyLine.trim().length > 0)) {
423-
output.push(`${activeFencePrefix}$$`, ...blockLines, `${activeFencePrefix}$$`);
431+
output.push(
432+
`${activeStartFencePrefix}$$`,
433+
...blockLines,
434+
`${activeContinuationFencePrefix}$$`,
435+
);
424436
} else {
425437
output.push(activeOpenLine, ...blockLines, line);
426438
}
427439
collectingBlock = false;
428440
blockLines = [];
429-
activeFencePrefix = "";
441+
activeStartFencePrefix = "";
442+
activeContinuationFencePrefix = "";
430443
activeQuoteDepth = 0;
431444
activeOpenLine = "";
432445
continue;

0 commit comments

Comments
 (0)