Repository navigation
Add ImageEmbedStyle.keepsIDInSource - #186
Open
xVanTuring wants to merge 2 commits into
Open
xVanTuring wants to merge 2 commits into
xVanTuring wants to merge 2 commits into
Conversation
added 2 commits
September 22, 2026 14:22
A link or image embed lives in the buffer as its DISPLAY form (`[[Name]]`, `![[Name]]`); the `|id` suffix sits on the `.wikiLinkID` side-channel and `makeStorageState` re-attaches it on every writeback. Three ordinary edits took the attribute away with nothing left to read it from, and the id then left the document for good — for an image embed that means the picture is gone and its stored bytes are orphaned, with no way to tell which image the reference used to name. - `makeStorageState` probed `.wikiLinkID` only at `contentRange.location`. Typed text takes the base typing attributes, so a character inserted at the START of a name carried no id and the probe read nil — even though every surviving character of the name still had it. Scan the run instead. - Clicking a rendered embed selects its whole name, so the next keystroke replaces every character the attribute sat on and there is nothing left to scan. `shouldChangeTextIn` now captures the id when the replaced text was covered by one name run and the replacement carries no link grammar, and `textDidChange` re-applies it. `![[` / `]]` markers never carry the attribute, so replacing a whole embed — or typing a fresh one over it — cannot inherit a stale id. - `copy` serialized the buffer verbatim, so the clipboard held `![[Name]]` with no id: pasting it back created a dead reference, and pasting it over the original destroyed the only copy of the id (cut/paste goes through the same path). The private raw-markdown flavor now carries the storage form, rebuilt from the selection's own attributes; the plain-text, HTML and RTF flavors keep the display form, so no id leaks into another app.
The previous commit stopped three edits from taking an embed's id away with them, but the id still lives on a text attribute, and an attribute only survives while the text around it still parses as an embed. Delete one bracket and the paragraph restyle has no embed token to re-apply it to; type the bracket back and you have `![[Name]]` naming nothing. The stored image is still there, with nothing left that can name it. So let an embedder put the id where it cannot be lost: in the text. `keepsIDInSource` copies an image embed through the display transform untouched — the source line reads `![[Name|id]]`, display IS storage, the `.wikiLinkID` attribute is never written for it, and every pasteboard flavor carries the id because the text does. Node links are unaffected, and the option is off by default, so nothing changes for anyone who does not ask. The trade is the source line's length while the caret sits in the embed. For an embedder whose ids point at bytes only that id can find, that is the cheaper half of the bargain.
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.
#185 stops three edits from carrying an embed's id away with them. It cannot stop all of them, because the id lives on an attribute and an attribute only survives while the text around it still parses as an embed.
Delete one bracket:
There is no embed token in that paragraph any more, so the restyle has nothing to re-apply
.wikiLinkIDto andsetAttributesclears it. Type the bracket back and you have![[a.png]]naming nothing. The stored image is still there, and nothing can reach it.That is recoverable for an embedder whose ids are names — look the name up again. It is not recoverable for one whose ids point at bytes only that id can find: content-addressed blobs, database rows, a pasted screenshot that never had a filename. There the id is the data's only handle, and a side-channel is the wrong place to keep it.
What this adds
ImageEmbedStyle.keepsIDInSource(defaultfalse, so nothing changes for anyone who does not ask). With it on, an image embed is copied through the display transform untouched:![[Name|id]]— longer while the caret sits in the embed, and rendered identically when it does notmakeStorageStateround-trips it verbatim.wikiLinkIDis never written for image embeds, andmakeDisplayStaterecords no id for them, so nothing re-appends a suffix that is already in the textThree defaulted parameters thread it through (
makeDisplayState,makeStorageState,displayFragmentAndID) plus the call sites. Source-compatible.Tests
Tests/MarkdownEngineTests/ImageEmbedIDInSourceTests.swift— 9 tests, including the bracket break-and-restore that motivated this, the edits #185 fixes (now trivially safe, since there is no attribute to lose), no.wikiLinkIDwritten, node links still hiding their id, and the option being off by default.swift buildandswift testare green (522 tests).Open question
An option is the smallest change that solves it, but it is still a second mode through the display/storage seam, and you may prefer one of:
wikiLinkMetadatawhen a paragraph loses its embed token) — narrower, but it cannot help the cases where the text leaves the buffer entirelyHappy to take this any of those ways.