fix: keep the outer pipes a table's rows were authored with - #351
Merged
Conversation
`mdast-util-gfm-table` never passes `delimiterStart` or `delimiterEnd` to `markdown-table` and exposes neither as a setting, so the option is only reachable from a `table` stringify handler of Leafdown's own. The form is read off the rows rather than the table's own slice: a row's position already has its container prefix and its indentation taken off, while the lines after the first inside a blockquote or a list item keep theirs. The delimiter row is no node, so it follows the rows that are. A pipe is dropped only where every row was authored without it. Taking it off the rows that carry one to match a row that does not would rewrite more of the file than the form keeps. Two positions read a bare row back as something other than the row it was written from, and both give way to both pipes: - A blank cell at either end leaves the row opening or closing on a pipe of its own, which GFM strips before it splits the row, so the cell is gone and every cell after it has moved a column. - A delimiter cell is as wide as its column, so a first column one character wide is written `-`, which opens a bullet list item where no pipe precedes it. An alignment marker widens the cell past a lone hyphen, so only a column carrying none reaches this. `markdown-table` is pinned to the version `mdast-util-gfm-table` already resolves, so the handler writes what the extension would have.
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 table is now written with the outer pipes its rows were authored with.
mdast-util-gfm-tablecallsmarkdown-tablewith the alignment, the padding, and the cell width it was configured with and never withdelimiterStartordelimiterEnd, and exposes neither as a setting, so every table came back with an outer pipe on both sides of every row and a file using GFM's pipe-optional form was rewritten on its first save.leafdownTableFormstamps the authored form onto the mdast node the wayleafdownThematicBreakstamps a break's run, thetableschema carries it as an attribute, and atablestringify handler of Leafdown's own reaches the two options. The form is read off the rows rather than off the table's own slice: a row'spositionalready has its container prefix and its indentation taken off, while the lines after the first inside a blockquote or a list item keep theirs. The delimiter row is no node of its own, so it follows the rows that are.All four forms GFM reads alike are kept — both pipes, a leading one, a trailing one, and neither. A pipe is dropped only where every row was authored without it, because taking it off the rows that carry one to match a row that does not would rewrite more of the file than the form keeps.
Two positions read a row written without an outer pipe back as something other than the row it was written from, and both give way to both pipes rather than write a row the next open would disagree with:
-, and that hyphen and the space after it open a bullet list item where no pipe precedes it. An alignment marker widens the cell past a lone hyphen, so only a column carrying none reaches this.A table the editor inserts writes both pipes, which is also both fallbacks: it is the one form every row reads back as the row it was written from. That default is recorded in
docs/decisions.mdanddocs/specification.md, since it was not defined before.markdown-tableis added as a direct dependency, pinned to the versionmdast-util-gfm-tablealready resolves, so the handler writes what the extension would have.Related Issue
Closes #349
Verification
markdownCompatibility.test.tsgainsTable outer pipe form, which pins the written bytes across a save: seven sources kept as authored, covering all four forms, a header-only table, a one-character first column carrying an alignment marker, and a one-character first column behind a leading pipe; two containers, a blockquote and a list item; one row for a table whose rows disagree; and one row per fallback. The three fallback rows empty a first cell, empty a last cell, and narrow a first column to one character through an edit, which is the arrangement no source can author — a blank edge cell or a lone-hyphen delimiter row is what stops the lines being read as a table in the first place.blocks.test.tsasserts the form an inserted table writes, since a table read from a file now writes its own and one the editor creates needs a defined one.Measured by diffing
corpus/gfm/tables.mdagainst its first save: the four rows underAlignment markers and outer pipes are optionalno longer gain outer pipes. What still differs in that file is cell padding, delimiter row width, and the padding an alignment marker redistributes, whichPreserve the form a file was written innormalizes and #348 recorded. No corpus fixture is added: the round-trip assertions compare a save against the save before it and converge either way, whileTable outer pipe formcompares the written bytes, which is what distinguishes the two behaviors.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
A table that reaches the document without a parse — a pasted one — carries the default rather than the form of the document it came from. The rendered table would have to carry the form the way a thematic break carries its run in
data-marker, which is clipboard scope and is not what this issue measures.A fallback rewrites the recorded form as well as the bytes, so a table whose form was withdrawn reopens holding the form it was written with rather than the one it was authored with. The content is identical; only the recorded attribute differs. A thematic break behaves the same way where its run gives way.
The form is carried by a plugin of its own rather than by
tableShape, which the issue named as the local seam.tableShapeanswers for how many cells a row holds and repairs the ones that disagree; the outer pipes are a property of the whole table that no repair touches, and the two walks share nothing but the tree.The delimiter row's own pipes are not recorded separately. It is no node, so a table authored with pipes on its rows and none on its delimiter row is written with pipes throughout.