Skip to content

Recognize block-style heading attributes - #2250

Merged
mre merged 7 commits into
lycheeverse:masterfrom
sjh9714:fix-myst-block-heading-anchors
Sep 20, 2026
Merged

mre merged 7 commits into
lycheeverse:masterfrom
sjh9714:fix-myst-block-heading-anchors

Conversation

@sjh9714

@sjh9714 sjh9714 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2249.

--include-fragments already recognizes heading IDs written inline as ### Heading {#id}. This adds the same fragment recognition for MyST-style preceding block attributes such as:

{#block-id}
### Heading

The change is scoped to Markdown fragment extraction. A block attribute is only applied to the immediately following heading, and the normal generated heading fragment is still emitted.

Changes

  • Track a pending {#id} paragraph before a heading during Markdown fragment extraction.
  • Add regression coverage for the block attribute form while keeping generated heading IDs intact.

Testing

  • PATH="$HOME/.cargo/bin:$PATH" cargo test -p lychee-lib test_extract_fragments_from_preceding_heading_attributes -- --nocapture (failed before the fix, passed after)
  • PATH="$HOME/.cargo/bin:$PATH" cargo test -p lychee-lib extract::markdown -- --nocapture
  • PATH="$HOME/.cargo/bin:$PATH" cargo fmt --all -- --check
  • PATH="$HOME/.cargo/bin:$PATH" cargo clippy -p lychee-lib --all-targets --all-features -- -D warnings
  • PATH="$HOME/.cargo/bin:$PATH" cargo test -p lychee-lib
  • git diff --check

Note: I used Codex while preparing this change, reviewed the final diff, and ran the listed checks locally.

@mre mre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx. Took a quick look.

Comment thread lychee-lib/src/extract/markdown.rs
Comment thread lychee-lib/src/extract/markdown.rs Outdated
Comment thread lychee-lib/src/extract/markdown.rs

@katrinafyi katrinafyi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi :) I have a drive-by comment.

It would be nice to avoid the need to copy all of the paragraph text into a String, since it could be quite large. AFAICT, copying would only be needed if the parser returned the heading attribute across different Text events, which I think it would only do if it was split up by another tag (like bold, italic, etc). That shouldn't be the case here.

Separately, is there a spec for this markdown extension? I would also be curious about the handling of these edge cases and whether they act as expected according to the spec (if there is one). That said, some of these are quite mean and it's probably fine to overapproximate the fragment identifiers so maybe not a big deal.

{ #spaces-around }
# a

a {#text-before}
# a

{#text-after} a
# a

a
{#line-before}
# a

{#line-between}

# a

```
{#inside-code-block}
```
# a

`{#inside-code-inline}`
# a

*{#inside-bold}*
# a

> {#inside-quote}
> # a

{ #multiple-attributes } { #multiple-attributes2 }
# a

{} #between-empty-sets {}
# a

{{ #double-braced }}
# a

{{#double-braced-no-space}}
# a

{
#across-lines
}
# a

Comment thread lychee-lib/src/extract/markdown.rs Outdated
let mut in_paragraph = false;
let mut heading_text = String::new();
let mut heading_id: Option<CowStr<'_>> = None;
let mut heading_block_id: Option<String> = None;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a

@sjh9714

sjh9714 commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I pushed a follow-up that:

  • Adds the HeadingId wrapper.
  • Documents the heading-id vs. block-heading-id state.
  • Avoids collecting non-attribute paragraphs.
  • Adds negative unit coverage and a CLI fixture case.

Checks run locally:

  • cargo test -p lychee-lib extract::markdown
  • cargo test -p lychee --features check_example_domains test_fragments --test cli
  • cargo fmt --check

@mre

mre commented Jul 3, 2026

Copy link
Copy Markdown
Member

@sjh9714, going forward, please make sure all comments are cleaned up before posting. We prefer human commentary here, even if imperfect. It's more productive to hear your own thoughts so we can help you best.

@sjh9714
sjh9714 force-pushed the fix-myst-block-heading-anchors branch from 2ae134e to 6058863 Compare July 6, 2026 06:47
@sjh9714

sjh9714 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Got it, thanks for pointing this out. I’ll take more care with my comments going forward and make sure they’re in my own words before posting.

mre added 5 commits September 20, 2026 16:29
Parse supported preceding attributes for ATX and Setext headings while
rejecting malformed, nested, multiline, and non-heading attribute
groups.
Add focused tests for grouped, quoted, and escaped attributes.
@mre

mre commented Sep 20, 2026

Copy link
Copy Markdown
Member

@katrinafyi, the paragraph String buffer is now gone. :) Instead, we inspect borrowed source slices only, so we only allocate the extracted ID.

I also checked your examples against MyST’s attrs_block implementation in mdit-py-plugins and added unit and integration coverage. We now match its handling of multiple groups on one line: {#first} {#second} uses first and ignores the trailing group. We still reject nested braces inside the first group.

I also added tests for quoted attribute values, so things like title="100%" work without treating #text inside a value as an ID or a quoted } as the end of the group. Thanks for the thorough examples!

Overall, I'm still not 100% happy with that code. We made attribute parsing much harder to understand, but that's mainly because attribute parsing is hard. In the future, I'd like us to refactor that code. It's okay for now, though, I'd say.

@mre

mre commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

The failing test is unrelated. See #2294

@mre
mre enabled auto-merge (squash) September 20, 2026 15:43
@mre
mre disabled auto-merge September 20, 2026 15:43
@mre
mre merged commit 27b4d13 into lycheeverse:master Sep 20, 2026
6 of 7 checks passed
@mre

mre commented Sep 20, 2026

Copy link
Copy Markdown
Member

@sjh9714, thanks for the initial work on this.

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.

Recognize {#id} heading anchors in --include-fragments

3 participants