From 2bf445daca4a7d1e5e6cb66d566b517a2a3ab94d Mon Sep 17 00:00:00 2001 From: testikun <320479488+testikun@users.noreply.github.com> Date: Sat, 5 Sep 2026 13:37:01 +0800 Subject: [PATCH] fix(user-input-fold): match fenced code delimiters --- extensions/user-input-fold/index.test.ts | 22 ++++++++++++++++++++++ extensions/user-input-fold/index.ts | 22 +++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/extensions/user-input-fold/index.test.ts b/extensions/user-input-fold/index.test.ts index 48844996..ba4973c5 100644 --- a/extensions/user-input-fold/index.test.ts +++ b/extensions/user-input-fold/index.test.ts @@ -141,6 +141,28 @@ test("multiple fenced blocks fold per block while prose shares one budget", () = ); }); +test("nested fences require the matching marker length and support tildes", () => { + const message = [ + "````markdown", + "before", + "```js", + "nested", + "```", + ...Array.from({ length: 20 }, (_, i) => `line-${i}`), + "````", + "~~~text", + ...Array.from({ length: 10 }, (_, i) => `tilde-${i}`), + "~~~", + ].join("\n"); + const out = foldUserMessage(message); + assert.ok(out.includes("```js")); + assert.ok(out.includes("nested")); + assert.ok(out.includes("````markdown")); + assert.ok(out.includes("~~~text")); + assert.ok(out.includes("~~~")); + assert.equal((out.match(/````/g) ?? []).length % 2, 0); +}); + test("CRLF messages fold without losing their line endings", () => { const message = Array.from( { length: 21 }, diff --git a/extensions/user-input-fold/index.ts b/extensions/user-input-fold/index.ts index 3edbafa7..69dd26f8 100644 --- a/extensions/user-input-fold/index.ts +++ b/extensions/user-input-fold/index.ts @@ -40,8 +40,8 @@ type Segment = | { kind: "prose"; lines: string[] } | { kind: "code"; open: string; content: string[]; close: string }; -const FENCE_OPEN = /^ {0,3}`{3,}/; -const FENCE_CLOSE = /^ {0,3}`{3,}[ \t]*$/; +const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/; +const FENCE_CLOSE = /^ {0,3}([`~]+)[ \t]*$/; function countLines(markdown: string) { const parts = markdown.split("\n"); @@ -60,7 +60,8 @@ function parseSegments(lines: string[]): Segment[] { let prose: string[] = []; let i = 0; while (i < lines.length) { - if (!FENCE_OPEN.test(lines[i])) { + const opening = lines[i].match(FENCE_OPEN); + if (!opening) { prose.push(lines[i]); i += 1; continue; @@ -70,12 +71,23 @@ function parseSegments(lines: string[]): Segment[] { prose = []; } const open = lines[i]; + const fence = opening[1]; + const fenceChar = fence[0]; + const fenceLength = fence.length; const content: string[] = []; let close: string | undefined; let j = i + 1; while (j < lines.length && close === undefined) { - if (FENCE_CLOSE.test(lines[j])) close = lines[j]; - else content.push(lines[j]); + const closing = lines[j].match(FENCE_CLOSE)?.[1]; + if ( + closing && + closing[0] === fenceChar && + closing.length >= fenceLength + ) { + close = lines[j]; + } else { + content.push(lines[j]); + } j += 1; } if (close === undefined) {