Skip to content

0.13.0 — Directive seam, per-marker thematic breaks, live table widths

Latest

Choose a tag to compare

@luca-chen198 luca-chen198 released this 20 Sep 12:46

The directive seam

Delimiters can only say so much. **bold** needs no configuration; a page break, a font size, a colour need a name and parameters, and Markdown has no syntax for that. The directive seam adds one, as an opt-in registry rather than as new Markdown.

A MarkdownDirective declares a name, a form — self-contained (@pagebreak) or container (@font(size: 18){text}) — and a parameter schema. Register instances via MarkdownEditorConfiguration.directives; unregistered names stay literal text, and the @ marker is configurable. Directives project into the AST as extension-shaped nodes under a reserved directive. namespace, so marker shrink, caret reveal, incremental restyle and rich copy apply unchanged — and a directive-free registry is byte-identical to before.

A container directive's style composes over the inherited font instead of replacing it, so @font(size: 18){**bold**} is bold and 18pt. FontDirective and ColorDirective ship as reference directives, off by default.

One limitation: a directive whose body holds a span claimed by an earlier pass — a code span or an escape — stays literal as a whole. Same-pass and later constructs ($…$, links, emphasis, nesting) compose normally.

Thematic breaks that differ by marker

CommonMark renders ---, *** and ___ identically, so a novel-style star divider on *** needed invented syntax. MarkdownEditorConfiguration.thematicBreak gives each marker its own look; all three default to nil, so existing embedders keep the rule they have. Presentation only — the source is untouched, the caret still reveals the raw ***, and it still exports as <hr>.

Marks are centred on their ink, not their layout box: * is drawn high in its em, and box-centring would let a larger mark climb toward the top of its line. Pick a mark whose glyphs exist in the fonts you ship — a missing glyph silently falls back to another typeface rather than drawing as tofu.

Tables follow the window while you drag it

A rendered table kept its old width for the whole of a live resize and snapped at mouse-up, because AppKit's nested event-tracking loop does not reliably service deferred blocks. Reflow — raster, attributes, height, overlay — now completes synchronously inside the resize callback. Fractional widths propagate too; dropping sub-point changes left a width-adaptive table one width behind.

rendersTablesDuringLiveResize = false opts out for embedders who would rather keep the drag cheap. Both modes apply the final width immediately, including one delivered late by the host's layout.

Block math is typeset as block math

$$ … $$ was rendered in SwiftMath's text mode like $ … $, so block formulas got inline typography — shrunken fractions, unscaled operators, limits beside \sum instead of above and below. The parser always knew the difference; it now reaches the renderer through a LatexRenderMode, and the mode is part of the cache key. No \displaystyle is injected, and existing LatexRenderer conformers stay source-compatible through a defaulted overload.

Changed

  • Ordered-list numbers no longer revert to the source digit under the caret or a selection. The painted number is positional, so in a run written 1./1./1. a click inside a marker flipped every number below an insertion back to whatever the file says.
  • NativeTextViewWrapper.onTextMutation reports exact, completed native edits, for embedders that keep their own source authority.
  • A link's revealed target is muted rather than drawn in body colour.
  • The span-density regression tests assert on counted work rather than elapsed time, so they run on CI again. InlineParseCost is a pure function of the input: linear measures 6.0× for 6× the spans, the pre-rewrite pairwise containment 33.9×.

Fixed

  • A table survives its own source: an empty table note no longer collapses, \| escapes survive a round trip, and <br> breaks a cell across lines.
  • NSRangeException crash in updateCodeBlockSelection after a document swap.
  • Inspector disclosures animate, and the scroll restore no longer fights the reader.

Performance

  • Scoped restyles inside a contiguous list parse and style only intersecting items instead of rebuilding the whole list block.

Known limitations

  • Table cells render no inline markup on the copy path: bold, explicit links and autolinked URLs inside a cell reach the pasteboard as raw Markdown text.

Full changelog: 0.12.0...0.13.0

Contributors

by @wildthink — the directive seam, parsing and registry (#120), directive styling and rich copy (#155), and counting the span-density cost instead of timing it (#146).
by @YishenTu — table widths synchronized during and after a window resize (#152), and scoped list restyles (#149).
by @Nicolas-Py — per-marker thematic breaks (#160).
by @manemajef — block LaTeX in display mode (#153).
by @wishworldbetter — the NSRangeException crash after a document swap (#151).

I was away for far longer than I meant to be, and every one of these was sitting here waiting on me. Sorry for going quiet.

Most of what is in this release is other people's work — an entire new seam among it. Thank you for that, and for the patience. This project is in much better shape for it.