feat: show the character a projected reference names beside its source - #409
Merged
Conversation
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
A projected character reference replaced the character it named, so with the source open the author read
©and no longer saw what it renders — the answer the projection was opened to get. A reference naming whitespace, such as or​, could not be confirmed from the source at all.The character is now drawn immediately before the source wherever a reference is projected: the reference's own projection, a marked fragment holding one, and a link label holding one. It reads as the content it renders while the source beside it reads as the marker it is, so a reference in a link label draws its character in the label's styling with
©muted beside it, and one in a bold fragment draws its character bold.It is a
Decoration.widgetrather than document text. Were the character text, a projected run would hold characters the file does not and an edit would have to guess whether the author meant the character or the source; as a decoration it touches neither the projected text nor the source maps, so every offset between the document and the file is unchanged. The character rides indata-leafdown-previewand is drawn through::before, the way a persistent block marker already is, which is what keeps it out of every reading of the document: it sits in no text node, so no selection covers it, no copy carries it, and nothing serializes it.white-space: prekeeps the width of a character a reference names as whitespace.The widget stands outside the document's marks, so the run's own styling reaches the character through a class rather than through the marks the source carries. A positive
sideputs the widget after the caret at the source's opening offset, so the character and the&read as one position: the caret at the start of the source rests before both, and one move right leaves the pair. Entry direction is unchanged, and a caret entering from the right still starts at the end of the source.The link source map has no character-reference segment, so a reference in a label arrives inside a text segment and is located from that segment's
sourceBoundaries, where each pair is the source one document character was read from. A pair whose source decodes as a whole reference is one; an escape spends two characters on the ampersand it keeps literal and a code span spends one per character, so neither decodes and neither draws a character. The reference's run is split out of its segment's span rather than covered by a second one, because ProseMirror merges decorations over the same range into a single element, where the content class would win over the marker.getProjectionContentClassNameemitted a literalfalseclass token for every absent mark, which.filter(isNonNullish)kept. It now filters on truthiness like its counterpart in the link syntax; the defect was invisible until the character's element carried the result into the DOM.Related Issue
Closes #308
Verification
pnpm check:frontendpasses on the complete change.Focused tests in
sourceProjectionCharacterReference.test.tsprove the character appears for every reference form and in all three owners, that it takes the run's content class while the source takes the marker class, that a backslash or a code span in a label draws none, and that the document size, the caret position, a selection over the source, a copy of it, and the saved Markdown are unchanged by its presence.editorPresentation.test.tsguards the stylesheet rule that draws it.Manually verified in Chrome against the dev server, on Windows 11, with the editor mounted on a document holding each projection owner:
©beside it is muted and stays underlined; in a bold fragment the character is bold and the source is not.ArrowRightreaches the position after the&, and oneArrowLeftreturns. draws at its own width, so whitespace the source hid is visible again.Not verified in this session: the Tauri window, which the frontend was driven at the Vite dev server in place of; it renders the same editor and stylesheet but not the native shell.
Notes
A reference naming a zero-width character draws nothing, because the character it names is nothing. The rule shows the character rather than a stand-in for it.
Typora's deletion behaviour, where removing the character removes the reference's opening
&, is left to its own decision as #308 scoped it, and so is a character shown before the caret arrives, which is marker presentation rather than projection. A reference in a link destination or an image description draws none either; only the three owners the issue enumerates project a reference as label or fragment content.Adjacent references still resolve a caret at their shared boundary to the reference that follows it, so leaving one leftward lands at the start of its neighbour rather than at the end. That belongs to #406 and #312 rather than to anything this change reaches.