Repository navigation
Render ==highlight== via delimiter pairing rewriter - #182
Draft
Vincent Peng (vincent-peng) wants to merge 8 commits into
Draft
Vincent Peng (vincent-peng) wants to merge 8 commits into
Vincent Peng (vincent-peng) wants to merge 8 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>
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
InlineDelimiterRewriter, a post-parse rewriter that pairs repeated-character delimiter runs inside paragraphs/headings/table cells and wraps the matched range inInlineAttributes, using the same attribute channel the inline HTML rewriter introduced.==highlight==renders as highlighted text (background color via the existinghighlightattribute).====x====nests instead of collapsing.a====b), and markers inside links/code spans stay literal and in order — the empty/rejected fallback paths emit pieces verbatim rather than reordering boundary remainders.Link/Imagechildren pass through a wrap unstyled but alive, so==a [x](u) b==keeps a live link andImageBlockRewriterstill sees paragraph-level images.InlineAttributescompose:==a <sup>b</sup> c==keeps both the highlight and the superscript by merging attribute keys into split runs (a wrap can't contain a nested attributes node).The engine is spec-driven — a
DelimiterSpectable declares each marker, its attribute key, inner-whitespace policy, and opener-blocking predecessor characters — so follow-on delimiters (superscript^x^is a stacked PR on top of this one) are a table entry plus tests.Depends on #181 (uses
InlineAttributes+ the attributed-string converter introduced there).Test plan
swiftlint --strict— cleanxcodebuild test— full suite green (26 dedicated delimiter tests: pairing, punctuation flanking, run nesting, empty pairs, link/image pass-through, nested attribute composition, constrained containers)make build-sample— buildsGenerated with Devin