Directives (3/4): draw a self-contained call as a glyph - #158
Open
wildthink wants to merge 1 commit into
Open
Conversation
Phase 3 of the directive seam. A self-contained call has no body, so until now it parsed, claimed its span, and then rendered as its own literal source — `@pagebreak` looked exactly like the text `@pagebreak`. This gives it something to draw. A directive returns a `DirectivePresentation` — an SF Symbol, replacement text, or an NSImage — and the styler collapses the source behind it. The mechanism is the one inline LaTeX already uses, not a new one: the characters stay in the storage, the first carries the image plus enough kern to occupy its width, the rest collapse to zero width via clear colour and the shrunk marker font. `MarkdownTextLayoutFragment` draws it. That is what keeps "markers shrink, they don't disappear" true here. Selection, find, copy, and undo all still see the real characters, and the caret entering the call reveals the source muted — the same flip every other construct does. Failure is visible rather than silent: `.literal`, or a symbol name the system doesn't know, leaves the source on screen instead of collapsing it to a gap the user can neither see nor fix. Rasterised glyphs are cached in an NSCache keyed by everything that determines the pixels — presentation re-runs for every visible directive on every keystroke, and rasterising text each time is the one part of this path expensive enough to matter. The parser is untouched: this is styling only, and the 4000-input corpus fingerprint is unchanged. `DirectiveScanner`'s diff is comment-only — it already emitted the geometry this needs, and its comments pointed forward to this change. DirectiveStylingTests asserted that a self-contained call renders as literal text with nothing collapsing it. That was Phase 1 stating its own limit, and it is exactly what this changes, so it becomes a collapse/reveal pair rather than being deleted. `FontDirective` and `ColorDirective` are both containers and draw no glyph, so the engine still ships no self-contained directive. Demo/ gains `@icon`, `@flag`, `@emoji`, and `@pagebreak` as embedder-side examples — curated data and print semantics are app concerns. `@flag` computes its glyph from regional-indicator scalars and carries no dataset. 463 tests pass, demo builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.
Phase 3 of the directive seam, on top of #120 and #155. Styling only — the parser is untouched.
The gap this closes
A self-contained call (
@pagebreak) has no body. It parses, claims its span, and then renders as its own literal source, so@pagebreaklooks exactly like the text@pagebreak. #155's README says as much:A directive now returns a
DirectivePresentation— an SF Symbol, replacement text, or anNSImage— and the styler draws it in place of the source.How it draws
The mechanism is the one inline LaTeX already uses, not a new one. The characters stay in the storage: the first carries the image plus enough kern to occupy the glyph's width, the rest collapse to zero width via clear colour and the shrunk marker font.
MarkdownTextLayoutFragmentdraws it.That is what keeps "markers shrink, they don't disappear" true here. Selection, find, copy, and undo all still see the real characters, and the caret entering the call reveals the source muted — the same flip every other construct performs.
Failure is visible rather than silent:
.literal, or a symbol name the system doesn't know, leaves the source on screen rather than collapsing it to a gap the user can neither see nor fix.Rasterised glyphs are cached in an
NSCachekeyed by everything that determines the pixels. Presentation re-runs for every visible directive on every keystroke, and rasterising text each time is the one part of this path expensive enough to matter.Scope
MarkdownDirective.swiftDirectivePresentation+ thepresentationrequirement, defaulting to.literalMarkdownASTStyler+Directives.swiftDirectiveScanner.swiftBuiltinDirectives.swiftpresentationis "not yet consulted by the styler"Autocomplete is deliberately not here — it's Phase 4, and after your note on #120 about shipping a directive that parses but doesn't render, I'd rather not repeat the shape in reverse.
One test changes meaning
DirectiveStylingTests.selfContainedStaysVisibleasserted that a self-contained call renders as literal text with nothing collapsing it. That was Phase 1 pinning its own limit, and it is precisely what this PR changes — so it becomes a collapse/reveal pair rather than being quietly deleted, and the suite is no longer named "Phase 1 styling". Flagging it explicitly since a green diff would otherwise hide a deliberate behaviour change.Reference directives
FontDirectiveandColorDirectiveare both containers and draw no glyph, so the engine still ships no self-contained directive.Demo/gains@icon,@flag,@emoji, and@pagebreakas embedder-side examples, 30–60 lines each — curated data and print semantics are app concerns, not engine primitives.@flagcomputes its glyph from the code's regional-indicator scalars and carries no dataset.Verification
Phase 4 (name and argument-value autocomplete) is ready behind this and I'll open it once this lands. #154 is still mine to take whenever you'd like it.
🤖 Generated with Claude Code