feat(table): render ~~strikethrough~~ and ==highlight== in table cells#225
Merged
Conversation
The block-table widget's inline renderer had its own IR/tokenizer that handled code/emphasis/link/image/autolink but not strikethrough or highlight, so `| ~~x~~ |` and `| ==x== |` leaked their raw delimiters inside an otherwise-rendered table while the same text rendered formatted everywhere else. Add two paired-inline-mark CellLeaf kinds (strikethrough, highlight) with a flanking-aware tokenizer arm mirroring the source parsers' open/close rules, and <del>/<mark> render arms whose content is re-parsed for nested inline. Style both under .quoll-table-block to match the editor's own token paint. Display-only: byte-identical round-trip is preserved.
Review (Codex + code-quality) found the initial greedy nearest-closer scan for ~~/== both (a) diverged from the editor's @lezer/markdown delimiter stack for nested / triple / emphasis-crossing marks (e.g. *a~~b*c~~d* rendered a strikethrough the editor never shows) and (b) was O(n^2) on adversarial cell content (unmatched ~/= runs re-scan to end on every opener — a webview main-thread freeze on pasted logs). Both share one root cause: the marks were resolved in an isolated pre-pass instead of the same stack the * / _ path uses. Emit ~~/== as fixed-length delimiter runs into resolveInline's existing delimiter stack, pairing them into del/mark wraps that interleave with emphasis exactly as the editor does. This is a single O(n) pass and deletes the leaf/re-parse machinery entirely; <del>/<mark> now ride the existing emphasis render/flatten arms. Adds parity regression tests (~~a ~~b~~ c~~, ===x===, *a~~b*c~~d*), unsafe-URL-in-mark + alt-flatten coverage, a styles-contract pin for the del/mark rules, and corrects a styles.css comment that misattributed the editor's coordsAtPos rationale to the display-only table widget.
Cycle-2 review (2 MEDIUM): the interleave/nesting parity tests exercised only ~~, and Delimiter.ch stayed `string` — discarding the DelimiterChar union at the ch->WrapTag decision point. Add == vs *, == self-nesting, and cross-type (highlight-in-strikethrough) cases so a future edit that broke only the mark branch cannot pass on ~~ alone; narrow Delimiter.ch to DelimiterChar so the ch->tag ternary type-narrows. No behaviour change.
…e-highlight-parity
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
The block-table widget's inline renderer rendered code/emphasis/link/image/autolink but not
~~strikethrough~~or==highlight==, so| ~~x~~ |and| ==x== |leaked their raw delimiters inside an otherwise-rendered table while the same text renders formatted everywhere else. This adds strikethrough + highlight parity to the table-cell renderer.Approach
~~/==are emitted as fixed-length delimiter runs into the same delimiter stack the inline resolver already runs for*/_(resolveInline), pairing them into<del>/<mark>wrap nodes. Sharing one stack means a mark interleaves with emphasis exactly as the editor's@lezer/markdownparser resolves it — e.g.*a~~b*c~~d*renders<em>a~~b</em>c~~d*(the crossing~~left inert), not a strikethrough the editor never shows. This is a single O(n) pass;<del>/<mark>ride the existing emphasis render/flatten arms.Changes
inline-emphasis.ts:DelimiterChar/WrapTagtypes;~/=added to the delimiter stack; matched pairs wrap intodel/mark.inline-ir.ts: tokenizer emits~~/==as delimiter runs (exactly-two, third-char-reject,*-style flanking — mirrors GFM Strikethrough +highlight-mark.ts).cell-render.ts:<del>/<mark>render via the existing emphasis arm (createElement(node.tag)).styles.css:.quoll-table-block del/markstyling reusing the shared--quoll-highlight-bgtoken to match the editor's own token paint.~~and==(verified against@lezer/markdown), cross-type nesting, unsafe-URL-in-mark gate, alt-text flatten, and a styles-contract pin.Display-only — byte-identical round-trip is preserved (edits still ride parent dispatch).
Related
==highlight==(PR feat(highlight): Obsidian-style ==highlight== inline mark syntax #222) added a second instance of it.Test Plan
pnpm compile(type-check, all 4 tsconfigs) passespnpm test:unit(webview + markdown) — 2954 pass, incl. the new parity casespnpm lintcleanbuildgreen on the merge candidate.mdtable with| ~~x~~ |and| ==x== |— first cell struck, second highlighted