Skip to content

Add opt-in Discord-style __underline__ rendering#10

Open
PiotrCzapla wants to merge 1 commit into
mainfrom
claude/discord-underline
Open

Add opt-in Discord-style __underline__ rendering#10
PiotrCzapla wants to merge 1 commit into
mainfrom
claude/discord-underline

Conversation

@PiotrCzapla

Copy link
Copy Markdown

What changed

New underline option (off by default). When enabled, Discord-style __x__ renders as <u>x</u> instead of <strong>x</strong>:

to_xhtml("__important__", underline=True)   # <p><u>important</u></p>

Also exposed as xhtmlmd --underline on the CLI and Options::underline in Rust. Callbacks get a new underline inline kind.

Behavior details

  • Only underscore runs of length 2 are affected; **x** stays <strong>, _x_ stays <em>.
  • All CommonMark underscore emphasis rules still apply: no intraword matching (intra__word__ stays literal), delimiter-run pairing is unchanged, and ___x___ nests as <em><u>x</u></em> — which matches Discord's underline-italics.
  • ____x____ flattens to a single <u> the same way nested empty-attr <strong> already flattens.
  • Trailing {.class} span attributes are not applied to __x__, consistent with existing **x**/_x_ behavior (bracketed spans are the attribute mechanism).

Why opt-in

CommonMark defines __x__ as strong emphasis, and the crate's dialect policy is Pandoc-closest. Making this the default would deviate from every emphasis spec example using __; as an option it costs nothing when off — the full conformance suite passes with no examples disabled.

Implementation

Inline::Underline is a new AST variant mirroring Strong; the underline flag threads from Options through delimiter processing, and the _-with-length-2 case in wrap_delimiters picks the variant. Renderer emits <u>, and the Python node/callback plumbing treats it like the other child-bearing inlines.

Testing

  • New tests in tests/test_focused.py: opt-in gating, coexistence with **/_, nesting/flattening/intraword rules, and the underline callback.
  • Full suite: 820 passed; the only failures are the two tests importing machine-local packages (fastpylight, math_core). tools/gen_docs.py --check is clean. README and docs/DIALECT.md updated.

🤖 Generated with Claude Code

With underline=True (Python kwarg, --underline CLI flag, or
Options::underline), __x__ renders as <u>x</u> instead of
<strong>x</strong>, following Discord's convention. All the usual
underscore emphasis rules apply: no intraword matching, ___x___
nests as <em><u>x</u></em>, and empty-attr nesting flattens like
strong. **x** is unaffected, and the default stays CommonMark
strong, so conformance is unchanged. Callbacks get a new
"underline" inline kind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jph00

jph00 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

I'm open to this, although my preference would be to use ++ instead, since that's a known markdown-it extension and doesn't require a new option. Other possibilities that work now are [x]{.underline} (Pandoc's HTML writer special-cases that class and emits <u>) or just Raw <u>x</u>

WDYT?

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.

2 participants