feat: add useTabs config option - #253
Open
dsherret wants to merge 3 commits into
Open
Conversation
Writes the four columns of indentation that an indented code block and the body of a footnote definition are written with as a tab. A tab reaches to the next multiple of four columns, so it only stands for those four where the content around the block begins at a multiple of four itself. Either keeps its spaces where it doesn't, and within a block quote, whose markers leave the column its content begins at unknown. A list keeps its spaces throughout, since the column its content begins at follows the width of its marker. Closes #242
Review found the footnote definition half was the source of most of the change's complexity and of a limitation with no fix here: the printer measures a tab as one column, so a footnote body wrapped ~3 columns wide and overran the line width. Code blocks are unaffected, since their content is never wrapped, so this drops the footnote half along with the machinery it needed -- `gen_block_quote` and its markers are untouched again. Also fixes an indented code block written beside a list marker being given a tab, which reached the next tab stop from the marker's column rather than standing for four columns and left the code read back as a paragraph. A tab now only goes where the block begins a line of its own. The option is no longer taken from the global configuration. A project that indents its code with tabs isn't saying anything about the few places markdown can use one, and inheriting it changed the output of ~2.8% of the markdown files on hand without any config change. The oracle test was comparing a spec that sets `useTabs` itself against a configuration that also set it, so it was inert on exactly the specs that exercise the feature.
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.
Partially addresses #242.
Writes the four columns of indentation of an indented code block as a tab:
Test: codeSame mechanism as prettier/prettier#19352, minus the footnote half — see below.
Scope
useTabsis not inherited from dprint's global configuration; it only takes effect when set in the markdown plugin's own section. A project that indents its code with tabs isn't saying anything about the few places markdown can use one, and inheriting it changed the output of ~2.8% of the markdown files on hand with no config change on the user's part. (This is also why the PR only partially addresses #242 — the reporter asked for lists too.)CommonMark fixes tab stops at four columns, so a tab only stands for those four where it is written at a column that is already a multiple of four. Everything else keeps its spaces:
- oneputs its content at column 2, where a tab reaches column 4 and gives two columns rather than four;[^1]:/:label.Lists are unchanged, and so is every other kind of indentation. Note that under
list.indentKind: pythonMarkdowna list's own increment is a fixed 4, so a code block inside a list does reach a tab stop and gets a tab (- one/\tcode) — pinned inCodeBlocks_UseTabs_PythonMarkdown.txt.Why not footnote definition bodies
An earlier revision covered them, as prettier's PR does. Review turned up a limitation with no fix on this side: dprint-core's writer counts a tab as
indent_widthcolumns — 1 here, since the plugin setsindent_width: 1so that column-granular indents are expressible at all — so a tab-indented footnote body wrapped about three columns wide and overranlineWidthundertextWrap: always. Code block content is never wrapped, so the code block half is unaffected.That half also needed a print-item walker to insert the tab into arbitrary generated content, plus plumbing so a block quote inside a footnote wrote the tab ahead of its
>markers. Dropping it removes all of that:gen_block_quoteandgen_block_quote_markersare untouched again, and the whole feature is one predicate and one line-writing helper.Lists would need the same dprint-core change, and more besides: the column a list item's content begins at follows the width of its marker, so putting it on a tab stop means writing the marker with a tab after it and choosing different content columns. Separate work.
Testing
tests/use_tabs_test.rsis an oracle over the whole spec corpus. Every spec is formatted withuseTabsadded to whatever configuration it already sets, and the result written back out with the option off, which must equal what the formatter produces from spaces alone — a tab that failed to reach its column would be read back as something else and show up there. It also pins idempotency under tabs and checks no line gains trailing whitespace.Verified beyond the specs:
main's binary against this branch over 160,505 formatted outputs — 12,101 documents including 10,552 real.mdfiles, plus a 20,000-document nesting fuzz corpus, across five configs — zero differing bytes. Turning the option on changes 358 of those same documents, which is what shows the comparison was live.useTabs: 55 reformat, all idempotent, all oracle-clean.Known limitation
Where the first pass also re-indents the document — list renumbering under
pythonMarkdown, task-list-marker normalisation — the parse shifts underneath theindent_level % 4decision and a second pass can rewrite the same columns as spaces instead of a tab. It converges after that. Fuzzing found two such inputs in ~40k random documents and none in the 4,905 real files; space mode converges in one pass for both.