Skip to content

Commit b94133e

Browse files
steipeteYigtwxx
andauthored
fix(discord): preserve limits when fences cannot fit
Co-authored-by: Yigtwxx <[email protected]>
1 parent 02da4bf commit b94133e

2 files changed

Lines changed: 44 additions & 11 deletions

File tree

extensions/discord/src/chunk.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ describe("chunkDiscordText", () => {
2727
"first",
2828
"second",
2929
]);
30+
expect(chunkDiscordText(`${"x".repeat(35)}\nz`, { maxChars: 30, maxLines: 1 })).toEqual([
31+
"x".repeat(30),
32+
"x".repeat(5),
33+
"z",
34+
]);
3035
});
3136

3237
it("uses default chunk limits for non-finite options", () => {
@@ -142,6 +147,19 @@ describe("chunkDiscordText", () => {
142147
expect(chunks.every((chunk) => chunk.length <= 30)).toBe(true);
143148
});
144149

150+
it("keeps the hard size limit when synthetic fence balancing cannot fit", () => {
151+
const cases = [
152+
{ text: "```\nabcdefghij\n```", maxChars: 8 },
153+
{ text: "~~~~~~~~\nabcdefghij\n~~~~~~~~", maxChars: 18 },
154+
];
155+
156+
for (const { text, maxChars } of cases) {
157+
const chunks = chunkDiscordText(text, { maxChars, maxLines: 50 });
158+
expect(chunks.length).toBeGreaterThan(1);
159+
expect(chunks.every((chunk) => chunk.length <= maxChars)).toBe(true);
160+
}
161+
});
162+
145163
it("preserves whitespace when splitting long lines", () => {
146164
const text = Array.from({ length: 40 }, () => "word").join(" ");
147165
const chunks = chunkDiscordText(text, { maxChars: 20, maxLines: 50 });

extensions/discord/src/chunk.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,30 @@ function closeFenceLine(openFence: OpenFence) {
5757
return `${openFence.indent}${openFence.markerChar.repeat(openFence.markerLen)}`;
5858
}
5959

60+
function canBalanceFence(openFence: OpenFence, maxChars: number) {
61+
const markerLength = closeFenceLine(openFence).length;
62+
return markerLength * 2 + 3 <= maxChars;
63+
}
64+
6065
// Continuation chunks reopen the fence so Discord keeps rendering the code block. Prefer the full
6166
// opening line (keeps the language for highlighting); degrade to a bare marker when it would not
62-
// leave room for the closing marker plus at least one delimiter+char of body, since a near-limit
63-
// opening line would otherwise starve continuation chunks and overflow maxChars. Discord only needs
64-
// the language on the first fence; continuation messages are separate.
67+
// leave room for the closing marker plus at least one delimiter+char of body. When even the bare
68+
// pair cannot fit, preserve the hard transport limit and emit the continuation without synthetic
69+
// fences; the original fence text is still retained in its own chunks.
6570
function reopenFenceLine(openFence: OpenFence, maxChars: number) {
6671
const bareMarker = closeFenceLine(openFence);
72+
if (!canBalanceFence(openFence, maxChars)) {
73+
return null;
74+
}
6775
// openLine + closing marker (bareMarker + newline) + one delimiter + one body char must all fit.
6876
if (openFence.openLine.length + bareMarker.length + 3 <= maxChars) {
6977
return openFence.openLine;
7078
}
7179
return bareMarker;
7280
}
7381

74-
function closeFenceIfNeeded(text: string, openFence: OpenFence | null) {
75-
if (!openFence) {
82+
function closeFenceIfNeeded(text: string, openFence: OpenFence | null, maxChars: number) {
83+
if (!openFence || !canBalanceFence(openFence, maxChars)) {
7684
return text;
7785
}
7886
const closeLine = closeFenceLine(openFence);
@@ -195,15 +203,18 @@ function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): string
195203
if (!current) {
196204
return;
197205
}
198-
const payload = closeFenceIfNeeded(current, openFence);
206+
const payload = closeFenceIfNeeded(current, openFence, maxChars);
199207
if (payload.trim().length) {
200208
chunks.push(payload);
201209
}
202210
current = "";
203211
currentLines = 0;
204212
if (openFence) {
205-
current = reopenFenceLine(openFence, maxChars);
206-
currentLines = 1;
213+
const reopenLine = reopenFenceLine(openFence, maxChars);
214+
if (reopenLine) {
215+
current = reopenLine;
216+
currentLines = 1;
217+
}
207218
}
208219
};
209220

@@ -225,14 +236,18 @@ function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): string
225236
// A flush can fire mid-line, before `openFence` advances to `nextOpenFence` below, so it closes
226237
// against the still-open `openFence`. A fence-closing line that also carries trailing text would
227238
// otherwise reserve 0 yet still get a closing fence appended on flush, overflowing maxChars.
228-
const fenceToReserve = nextOpenFence ?? openFence;
239+
const candidateFence = nextOpenFence ?? openFence;
240+
const fenceToReserve =
241+
candidateFence && canBalanceFence(candidateFence, maxChars) ? candidateFence : null;
229242
const reserveChars = fenceToReserve ? closeFenceLine(fenceToReserve).length + 1 : 0;
230243
const reserveLines = fenceToReserve ? 1 : 0;
231244
const effectiveMaxChars = maxChars - reserveChars;
232245
const effectiveMaxLines = maxLines - reserveLines;
233246
const charLimit = effectiveMaxChars > 0 ? effectiveMaxChars : maxChars;
234247
const lineLimit = effectiveMaxLines > 0 ? effectiveMaxLines : maxLines;
235-
const reopenPrefixLen = fenceToReserve ? reopenFenceLine(fenceToReserve, maxChars).length : 0;
248+
const reopenPrefixLen = fenceToReserve
249+
? (reopenFenceLine(fenceToReserve, maxChars)?.length ?? 0)
250+
: 0;
236251
const prefixLen = current.length > 0 ? current.length + 1 : 0;
237252
// A mid-line flush swaps `current` to the reopen prefix; size segments against whichever prefix
238253
// is larger so the reopened chunk (prefix + segment + closing marker) still fits maxChars.
@@ -276,7 +291,7 @@ function chunkDiscordText(text: string, opts: ChunkDiscordTextOpts = {}): string
276291
}
277292

278293
if (current.length) {
279-
const payload = closeFenceIfNeeded(current, openFence);
294+
const payload = closeFenceIfNeeded(current, openFence, maxChars);
280295
if (payload.trim().length) {
281296
chunks.push(payload);
282297
}

0 commit comments

Comments
 (0)