Skip to content

feat: preserve user-authored YAML formatting across the save round-trip#63

Merged
bosiakov merged 2 commits into
mainfrom
feat/preserve-yaml-source-formatting
May 17, 2026
Merged

feat: preserve user-authored YAML formatting across the save round-trip#63
bosiakov merged 2 commits into
mainfrom
feat/preserve-yaml-source-formatting

Conversation

@bosiakov

Copy link
Copy Markdown
Contributor

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.

  • Retain raw source bytes on domain.ValuesFile and service.FlatValues so the save layer can write back what was loaded without going through any parser; populated in ReadCustomValues and for single-file ReadAndMergeCustomValues calls, left nil for multi-file merges and editor-parsed text where there is no canonical source.
  • Add a no-edit save short-circuit in the controller that writes the retained bytes directly via SaveRawBytes, skipping EncodeForFile because the bytes already carry the source's BOM and line endings; guards against the doubled-BOM and \r\r\n corruption that EncodeForFile would otherwise produce on second pass.
  • Introduce PatchSourceText as 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.
  • Implement SpliceScalarValues and planScalarSplice to 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 via docsMatchSource.
  • Add PreserveSourceFormatting as 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.
  • Strip the spurious !!merge tag yaml.v3 emits before merge keys so saved files match the canonical hand-authored <<: *anchor form.
  • Carry RawBytes, doc-level comments, NodeTree, anchors, indent, and per-entry Comment forward across editor keystroke reparses in pollEditorParse; without this the first keystroke wipes RawBytes (so splice fails on every save) and empty Comment fields make StripYAMLComments greedy enough to eat #-leading lines from block scalar bodies, corrupting the stored value.
  • Reparse editor text through yaml.v3 inside ParseYAMLText and 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.
  • Refactor the dialog stash path so overwrite confirmation captures the pending write as a closure (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.
  • Bundle the four save inputs the controller pulls off CustomColumnState into SaveInputs, deduplicating the same six-line gather block across three call sites.
  • Bundle the load-time metadata carry-forward into service.MergeLoadedMetadata, deduplicating the per-field copy block in pollEditorParse.
  • Add FORMAT_PRESERVATION.md in 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.

@bosiakov
bosiakov merged commit da46cbc into main May 17, 2026
1 check passed
@bosiakov
bosiakov deleted the feat/preserve-yaml-source-formatting branch May 17, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant