feat: preserve user-authored YAML formatting across the save round-trip#63
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.
Saving a values.yaml in qdeck used to rewrite every line in the file, even on a save with zero user edits, because yaml.v3 is a parse and re-emit library that normalizes blank lines, inline comment column alignment, folded scalars, the merge tag, the !!set form, !!binary spacing, per-block indent, inline comments on null leaves, and empty container placeholders. This change replaces that behavior with a layered pipeline that keeps untouched bytes byte-identical and falls back to a restoration pipeline when the encoder must run.
domain.ValuesFileandservice.FlatValuesso the save layer can write back what was loaded without going through any parser; populated inReadCustomValuesand for single-fileReadAndMergeCustomValuescalls, left nil for multi-file merges and editor-parsed text where there is no canonical source.SaveRawBytes, skippingEncodeForFilebecause the bytes already carry the source's BOM and line endings; guards against the doubled-BOM and\r\r\ncorruption that EncodeForFile would otherwise produce on second pass.PatchSourceTextas the byte-faithful save entrypoint, routing between leaf-line splice and the encoder fallback with restoration, so the controller has one call point regardless of edit shape.SpliceScalarValuesandplanScalarSpliceto rewrite only the value portion of changed plain-scalar lines, preserving every other byte; the all-or-nothing viability check covers new-key adds, comment edits, alias targets, quoted/block scalars, null type changes, and doc-level comment edits viadocsMatchSource.PreserveSourceFormattingas an encoder-output post-processor that runs four restoration passes in order: byte-range substitution for unchanged block scalars and tagged collections, line-for-line substitution of semantically-equivalent leaves, source-line re-insertion for single-line entries the encoder dropped, and blank-line restoration; collectively recover inline comment column alignment, folded scalar shape, !!set explicit-key form, !!binary spacing, empty container placeholders, and most blank lines.!!mergetag yaml.v3 emits before merge keys so saved files match the canonical hand-authored<<: *anchorform.pollEditorParse; without this the first keystroke wipes RawBytes (so splice fails on every save) and empty Comment fields makeStripYAMLCommentsgreedy enough to eat#-leading lines from block scalar bodies, corrupting the stored value.ParseYAMLTextand rewrite scalar entries from the resulting NodeTree, so tagged scalars (!!binary, large integers,!!int "x") keep the literal text the source NodeTree carries instead of the lossy chartutil/sigs decoding; previously every tagged leaf looked changed on every save and forced the encoder fallback.overwritePendingWrite) rather than a YAML string, letting both the encoder save and the raw-bytes save flow through the same dialog handler without branching on save-shape.CustomColumnStateintoSaveInputs, deduplicating the same six-line gather block across three call sites.service.MergeLoadedMetadata, deduplicating the per-field copy block inpollEditorParse.FORMAT_PRESERVATION.mdin the service directory as a narrative reference for future maintainers, covering motivation, the three save paths, each restoration pass, the editor reparse carry-forward, the bool-switch add scenario, the BOM/CRLF double-encode hazard, and the known limitations.