Skip to content

fix(md): Fix nested list termination on unindented paragraph#671

Merged
JeanMertz merged 1 commit into
mainfrom
prr239
May 28, 2026
Merged

fix(md): Fix nested list termination on unindented paragraph#671
JeanMertz merged 1 commit into
mainfrom
prr239

Conversation

@JeanMertz
Copy link
Copy Markdown
Collaborator

When a nested list item was followed by a blank line and then a paragraph at column 0, the outer InList handler was folding that paragraph into the outer item as a lazy continuation instead of terminating the list.

The root cause was two-fold. First, flush_list_segment drained the exact bytes it emitted into the Block, which consumed the trailing blank line that carried the prev_blank=true termination signal. The parent state therefore never saw the blank and treated the following content as a continuation. Second, prev_blank was always initialised to false at the start of handle_in_list, even when a leading blank had already been stripped from the buffer head — so a child scope that consumed the blank during its own flush left the parent unaware.

The fix splits flush_list_segment's single flush_pos parameter into two: content_end (how many bytes go into the Block) and drain_end (how many bytes are removed from the buffer). For the Terminator branch, content_end = scan (includes trailing blanks, so the renderer preserves the visual separator) while drain_end = last_content_end (stops before the blanks, leaving them in the buffer for the popped-to parent state to pick up as prev_blank=true). For SiblingMarker and NestedContainer branches the two values are identical, preserving existing behaviour. prev_blank is now seeded from leading_blank > 0 so re-entry after a child scope consumed a blank fires the terminator check on the very first line.

When a nested list item was followed by a blank line and then a
paragraph at column 0, the outer `InList` handler was folding that
paragraph into the outer item as a lazy continuation instead of
terminating the list.

The root cause was two-fold. First, `flush_list_segment` drained the
exact bytes it emitted into the Block, which consumed the trailing blank
line that carried the `prev_blank=true` termination signal. The parent
state therefore never saw the blank and treated the following content as
a continuation. Second, `prev_blank` was always initialised to `false`
at the start of `handle_in_list`, even when a leading blank had already
been stripped from the buffer head — so a child scope that consumed the
blank during its own flush left the parent unaware.

The fix splits `flush_list_segment`'s single `flush_pos` parameter into
two: `content_end` (how many bytes go into the Block) and `drain_end`
(how many bytes are removed from the buffer). For the `Terminator`
branch, `content_end = scan` (includes trailing blanks, so the renderer
preserves the visual separator) while `drain_end = last_content_end`
(stops before the blanks, leaving them in the buffer for the popped-to
parent state to pick up as `prev_blank=true`). For `SiblingMarker` and
`NestedContainer` branches the two values are identical, preserving
existing behaviour. `prev_blank` is now seeded from `leading_blank > 0`
so re-entry after a child scope consumed a blank fires the terminator
check on the very first line.

Signed-off-by: Jean Mertz <git@jeanmertz.com>
@JeanMertz JeanMertz merged commit 7673368 into main May 28, 2026
22 of 25 checks passed
@JeanMertz JeanMertz deleted the prr239 branch May 28, 2026 16:45
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.

1 participant