Skip to content

Close fenced div on CRLF input (fixes #113) - #149

Merged
jgm merged 1 commit into
jgm:mainfrom
youdie006:fix/113-crlf-div-close
Aug 19, 2026
Merged

jgm merged 1 commit into
jgm:mainfrom
youdie006:fix/113-crlf-div-close

Conversation

@youdie006

Copy link
Copy Markdown
Contributor

Fixes #113.

Problem

With CRLF line endings, a fenced div's closing ::: fence is not detected, so the div never closes and all following content is swallowed inside it.

renderHTML(parse(":::\r\nhello\r\n:::\r\nafter\r\n"))

Buggy CRLF output ("after" trapped inside an unclosed div):

<div>
<p>hello

after</p>
</div>

Correct output (the LF equivalent already produces this):

<div>
<p>hello</p>
</div>
<p>after</p>

Root cause

In the fenced_div continue callback (src/block.ts), when the closing fence matches, the parser sets this.pos = m.endpos. The fence pattern (::::*)[ \t]*\r?\n matches through the \n, so on CRLF input m.endpos lands on the \n -- one position past the \r that getEol() records as starteol. The main loop then evaluates the blank-line check isBlank = (pos === starteol) as false, so the fence line is mistaken for a lazy paragraph continuation and the div is never closed. On LF input m.endpos and starteol coincide, so the check passes and it works by coincidence.

Fix

Set this.pos = this.starteol, the position just before the line ending, which is correct for both LF and CRLF. This mirrors the sibling code_block close path and djot.js's \r?\n CRLF handling throughout the lexer. LF output is byte-identical; CRLF now closes the div correctly.

Tests

Added a regression test in src/html.spec.ts asserting the CRLF input closes the div (byte-identical to the LF output, which is also asserted unchanged). All existing tests pass (tsc + jest, 417 total).

Thanks to @iorizu for the clear report.


This change was prepared with AI assistance and reviewed by me before submission.

With CRLF line endings, a fenced div's closing ::: fence is not detected, so the
div never closes and all following content is swallowed inside it. In the
fenced_div continue callback (src/block.ts), the parser sets this.pos = m.endpos;
the fence pattern (::::*)[ \t]*\r?\n matches through the \n, so on CRLF input
m.endpos lands on the \n, one past the \r that getEol() records as starteol. The
main loop then evaluates isBlank = (pos === starteol) as false, mistakes the
fence line for a lazy paragraph continuation, and never closes the div. On LF the
two positions coincide so it works by coincidence.

Set this.pos = this.starteol, the position just before the line ending, correct
for both LF and CRLF, mirroring the sibling code_block close path. LF output is
byte-identical.

Fixes jgm#113.
@jgm
jgm merged commit b815ecf into jgm:main Aug 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EventParser's div End Detection is Broken When Source is in CRLF

2 participants