Skip to content

feat(table): render ~~strikethrough~~ and ==highlight== in table cells#225

Merged
mtskf merged 4 commits into
mainfrom
feat/table-cell-strike-highlight-parity
Jul 16, 2026
Merged

feat(table): render ~~strikethrough~~ and ==highlight== in table cells#225
mtskf merged 4 commits into
mainfrom
feat/table-cell-strike-highlight-parity

Conversation

@mtskf

@mtskf mtskf commented Jul 16, 2026

Copy link
Copy Markdown
Owner

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/markdown parser 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.

Note: an initial greedy nearest-closer scan was replaced during review — it diverged from the editor for nested/triple/interleaving marks and was O(n²) on adversarial cell content. The delimiter-stack rewrite fixes both.

Changes

  • inline-emphasis.ts: DelimiterChar / WrapTag types; ~/= added to the delimiter stack; matched pairs wrap into del/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 / mark styling reusing the shared --quoll-highlight-bg token to match the editor's own token paint.
  • Tests: interleaving/nesting/triple parity for both ~~ 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

Test Plan

  • pnpm compile (type-check, all 4 tsconfigs) passes
  • pnpm test:unit (webview + markdown) — 2954 pass, incl. the new parity cases
  • pnpm lint clean
  • CI build green on the merge candidate
  • Manual smoke: a .md table with | ~~x~~ | and | ==x== | — first cell struck, second highlighted

mtskf added 4 commits July 16, 2026 20:04
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.
@mtskf
mtskf merged commit f27844d into main Jul 16, 2026
1 check passed
@mtskf
mtskf deleted the feat/table-cell-strike-highlight-parity branch July 16, 2026 20:31
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.

1 participant