Repository navigation
Render ^x^ superscript via the delimiter rewriter - #183
Draft
Vincent Peng (vincent-peng) wants to merge 10 commits into
Draft
Vincent Peng (vincent-peng) wants to merge 10 commits into
Vincent Peng (vincent-peng) wants to merge 10 commits into
Conversation
Inline raw-HTML nodes (InlineHTML) were previously dropped during inline conversion because they don't conform to InlineConvertible. This adds an AST-level InlineHTMLRewriter that maps a supported formatting subset onto equivalent Markdown nodes or InlineAttributes carriers before conversion: - <br> -> line break, <wbr> -> zero-width space - <b>/<strong> -> strong, <i>/<em> -> emphasis - <s>/<del>/<strike> -> strikethrough - <u>/<ins>, <mark>, <sub>/<sup> -> InlineAttributes, now rendered by a new InlineConvertible conformance (underline, highlight, baseline offset) - <code>/<kbd>/<samp>/<tt> -> inline code - <a href> -> link HTML comments are dropped. Unmatched, unsupported, or malformed tags keep their source as literal text so documents degrade gracefully. An open tag with no matching close applies to the rest of the inline container, which keeps mid-stream documents sensible while a tag is still arriving. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Follow-ups from review: - Bound tag-matching recursion at 64 pairs of nesting; deeper input emits literally instead of recursing quadratically / overflowing the stack. - Convert HTMLBlock nodes (standalone-line tags, cmark block type 7) to literal paragraphs so their text is never silently dropped. - Replace the href regex with a quote-aware scanner: matches only on a whitespace boundary (no data-href/xlink:href false positives), skips quoted attribute values, and accepts case-insensitive HREF. - Decode common named/numeric entities in href values (&, ", …). - Flatten InlineAttributes inside <a> to their children so the link still forms; split underline/highlight/sub/sup runs around non-recurring children (Link, Image) instead of going fully literal. - Use InlineCode.code when flattening nested inline code inside <code>. - Accept the full cmark whitespace set inside tags; map </br> to a line break like browsers do. - Require a key boundary in the InlineAttributes enabled-key regex. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- visitHTMLBlock now drops comment/PI/declaration blocks instead of surfacing them as visible text, matching inline comment handling. - Images inside <b>/<i>/<s> wrappers split the run so Image nodes reach paragraph level where ImageBlockRewriter can hoist them — previously an image inside a bold tag was silently dropped at conversion. - Link/Image/InlineAttributes children are filtered back to RecurringInlineMarkup (text-preserved fallback) so rewritten content stays within swift-markdown's structural rules. - Depth-cap fallback path now drops comments consistently. - enabledKey regex requires a word boundary after 'true'; README lists <strike> and clarifies comment handling. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Adds InlineDelimiterRewriter, a spec-driven post-parse pass that pairs '==' runs in Text nodes using markdown-it-mark flanking rules and wraps matched ranges in InlineAttributes (already rendered with a highlight background). Pairing spans sibling inlines so '==**b**==' works, nested pairs fold into the outer highlight, and unpaired markers stay literal — important for streamed documents where a closer may still be arriving. Link/Image/InlineAttributes containers are left alone to preserve their RecurringInlineMarkup invariants. Stacked on the inline-HTML branch for the InlineAttributes renderer. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A nested InlineAttributes child is not RecurringInlineMarkup, so the first version degraded it to plain text and lost the inner style — e.g. '==a <sup>b</sup>==' highlighted everything but dropped the superscript. attributeWrap now splits the run around nested attribute nodes and re-wraps each under the union of both key sets. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Bound a marker's close index by the enclosing emit range so cross-spec pairs can't consume pieces the outer level emits again. - Empty-inner pairs (a====b) emit literal markers instead of vanishing. - attributeWrap keeps Link/Image nodes alive between styled runs instead of degrading them to plain text. - Skip the inner whitespace scan for specs that allow it; dedupe merged attribute keys on exact entries. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- applyFlanking now uses the full left/right-flanking rules (punctuation on either side of a run), so a==(b)==c and x==hi!==y stay literal and x==a)==b== picks the same pair the reference renderer does. - pairedCloses skips an immediately adjacent opener, mirroring markdown-it's jump rule; ====x==== nests instead of each run's markers collapsing onto each other. - emit's boundary-remainder strip now only hoists remainders matching the spec's own marker char, and the empty/rejected literal paths emit in piece order instead of reordering remainders across the markers. - Corrected the stale doc comment: backslash-escaped \== stays literal because cmark emits the escape as a separate Text node. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ImageBlockRewriter can only hoist a paragraph-level Image. An Image or SymbolLink inside a wrapped range deeper in (e.g. inside **==..==**) would be silently dropped at conversion — degrade it to its text fallback so the alt text joins the styled run instead of vanishing. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Extends the delimiter rewriter with a '^' spec producing
{superscript:true}. Inner whitespace is rejected per Pandoc's rule so
x^2 and a^b c^d stay literal. Nested composition relies on the base
branch's attributeWrap merge.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
LLMs emit [^n]-style references; pairing across the bracketed text would eat [^1][^2] as superscript of '1]['. A per-spec openerBlockedAfter set stamps ^ non-opening when preceded by '[', leaving such text literal while later clean pairs still wrap. Also covers the cross-spec pairing cases from review: a closer outside its enclosing emit range stays literal, so crossing == and ^ pairs can no longer emit pieces twice. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Vincent Peng (vincent-peng)
force-pushed
the
superscript-delimiter
branch
from
October 8, 2026 16:02
11b9ef6 to
c159166
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
^x^superscript as a secondDelimiterSpecon the delimiter engine from the stacked highlight PR:x^2^renders2with raised baseline and reduced font size (reuses thesuperscriptattribute handling added by Render a supported subset of inline HTML tags instead of dropping them #181 for<sup>).a^b^c); unclosed or whitespace-containing ranges stay literal (a ^b c^ d,x^2unchanged).^cannot open when preceded by[: LLMs emit[^n]-style references, and pairing across bracketed text would eat[^1][^2]as superscript of1][. The per-specopenerBlockedAfterset keeps that text literal while a later clean pair ([^1] x^2^) still wraps — and it doesn't reach through foreign interior content, sox[=^y^still pairs.==a ^b== c^highlightsa ^band leavesc^literal instead of duplicating text;^a ==b^ c==stays fully literal because superscript rejects inner whitespace.^uses the same markdown-it flanking rules as==, sox^(n)^stays literal (punctuation can't open intraword) — literal output is always a safe failure for streamed text.Depends on the highlight PR (the delimiter engine and its hardening live there), which in turn depends on #181.
Test plan
swiftlint --strict— cleanxcodebuild test— full suite green (40 delimiter tests including multi-spec crossing, footnote-pattern, piece-order, and intraword cases)make build-sample— buildsGenerated with Devin