@@ -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 .
6570function 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