fix: leave whitespace that starts a line out of the saved file - #342
Merged
Conversation
Encoding it as ` ` would preserve it, and unlike the closing edge that direction was reachable here: a leading space in a heading and a leading tab in a paragraph already round-trip. It would still return the entity noise #242 removed to every file whose line happens to begin in whitespace, and a parse strips the character on read while a block renders it as nothing, so the two directions differ in what the file carries rather than in what an author can observe. Whitespace typed at one of those positions is dropped as a result, a tab and a leading space in a heading included, both of which round-tripped as a character reference before. An authored reference is dropped only where Milkdown hoists its character out of the mark that records it, which it does for the ASCII space alone; ` ` and `	` keep their source and are written as they were.
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 space typed at the start of a line was written as
 , reloaded with the space intact, written raw on the next save, and gone on the open after that. The document took three saves to settle and lost the whitespace on the way.serializeMarkdownTextnow leaves that whitespace out, the way #278 leaves out whitespace closing a line.opensTrimmedContentasks whether a text node begins the content of a paragraph, a heading, or a table cell, which are the parents a parse trims, and the value is escaped without the leading whitespace so the escape passes read the real line start.Two things decide where a line begins, because neither covers the other:
beforemarks the line a hard break leaves behind.beforeand a cell hands its own padding, so neither block's first line is visible inbeforeat all.Reading the tree is also what separates a hoisted space from an ordinary one. Milkdown's
SerializerStatelifts a leading ASCII space out of the mark that records a character reference and leaves the mark empty (@milkdown/transformer@7.22.1,#moveSpaces), so an authored arrives as a whitespace-only sibling with an emptied reference in front of it. An emptied reference writes nothing, while any sibling that writes even one character puts the space mid-line, which is the difference between plainanda b.The position comes from the tree rather than from
state.indexStackfor a second reason.containerPhrasingpeeks the next child's handler to learn what the current one must be escaped against, and during that peek the stack still points at the child being written. Trusting it made" and "intesting@example.com and …look like the first child, so the peek dropped its leading space and reportedaas the following character; the bare autolink was then written as<testing@example.com>. Resolving the index by identity makes a peek agree with the write it predicts.Not writing the character is the direction, rather than encoding it as
 . This edge is not symmetric with #278: a leading space in a heading and a leading tab in a paragraph already round-tripped, so preservation was reachable here in a way it was not there. It would still return the entity noise #242 removed to every file whose line happens to begin in whitespace, and a parse strips the character on read while a block renders it as nothing, so the two directions differ in what the file carries rather than in what an author can observe.Related Issue
Closes #339
Verification
markdownCompatibility.test.tsgainsLine-initial whitespace, which drives the editor mount through edit, save, reload, save and asserts each stage rather than the bytes alone.Eight shapes that diverged now converge after a space is typed at the start: a paragraph, a paragraph with two spaces, a paragraph with a tab, a heading, a list item, a blockquote, a later paragraph in the same document, and emphasis opening a paragraph. Alongside them a table cell, the line a hard break leaves behind, and an authored
 opening a paragraph and a heading. Two rows assert the reloaded document text, for the typed space and for the authored reference.Five rows assert what must not change, each being whitespace a parse keeps or a reference written from its source: a character reference away from a line edge,
,	, a space a construct on the same line precedes, and whitespace inside fenced code.corpusRoundTrip.test.tsand thetesting@example.com and first.last+tag@example.co.ukautolink fixture both caught the peek defect described above, and both pass.pnpm check:frontendpasses. The backend is untouched, sopnpm check:backendwas not run.Not verified: the manual pass over
corpus/in the running application, which covers rendering, interaction, and navigator behavior beyond the automated round trip.Notes
Whitespace that round-tripped as a character reference is now dropped: a typed tab, which was written as
	, and a leading space in a heading, whichmdast-util-to-markdown's own heading handler encodes as before this change reaches it. An authored reference is dropped only where Milkdown hoists its character out of the mark, which it does for the ASCII space alone, so and	keep their source and are written as they were.The issue framed this edge as a mirror of #278 and expected preservation to be unreachable. Measurement contradicted that on both counts, and the body has been left as filed; the direction was confirmed against the measurements before implementing.
The corpus round-trip guard cannot see any of this and is unchanged, for the reason #278 recorded: a file it has opened once no longer holds line-initial whitespace, so its baseline is already stable.
TRAILING_WHITESPACE_PATTERNis/\s+$/u, which matches every Unicode space separator rather than the space and tab CommonMark trims, so #278's trim deletes a no-break space that ends a line. The leading pattern added here is/^[\t ]+/uand does not repeat it, which is why the two edges disagree until that is fixed. Filed as #341.