Skip to content

feat: replace text across the current folder - #636

Merged
Azganoth merged 2 commits into
mainfrom
feature/folder-replace
Oct 5, 2026
Merged

Azganoth merged 2 commits into
mainfrom
feature/folder-replace

Conversation

@Azganoth

@Azganoth Azganoth commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Edit > Find and replace > Replace in folder... (Mod+Shift+H), or a toggle before the folder search field, shows a replacement field in the folder search view. While it shows, the search plans the replacement instead of stopping at the result limit: each match is shown struck through beside its replacement, skipped files keep their reasons, and a file that would not read as its matches replaced, such as a footnote label the replacement would duplicate, is skipped as refused. Planning writes nothing, and Replace all is offered only once the plan is complete and was made for the current query, options, and replacement.
  • Replacement reuses document Find. A file no editor holds is parsed by the detached search editor into a view-less state carrying the editor's plugins, the Replace all transaction runs there, and save-time settling and the serializer produce its text. The open document is planned from a settled copy of its editor state, dispatching nothing.
  • A file no editor holds gets only the lines holding a replacement: the save serializations before and after the replacement are line-diffed against the file, and the changed lines are spliced into the file as authored, kept only when the result parses and serializes to exactly what Save would write. Otherwise the file is written as Save writes it and marked in the preview. This keeps a one-word replacement from re-padding tables or dropping escapes in files the author never opened. Encoding, byte order mark, line ending, and the final-newline setting follow Save.
  • When the open document has unsaved changes and the plan includes it, Replace all asks Save, Discard, or Cancel; the shared confirmation service gains a third action for this. Save or Discard runs the ordinary workflow and plans again without writing, so the updated preview is confirmed before anything is written.
  • Replace all preflights every planned file against the metadata and full-content fingerprint it was planned from, and checks that its text encodes strictly, through new bounded backend commands. It then writes in folder-relative-path order, checking each file again just before its staged atomic write. A stale file is left alone, a failed file does not stop the rest, and there is no cross-file transaction or Undo. The open document takes the replacement in its editor as one Undo step and is saved through the save queue without prompts, so neither watcher mistakes Leafdown's writes for outside changes. A report in the sidebar lists the files replaced, changed since the plan, failed with their reason, and not attempted.
  • Specification, architecture, decisions, reference, the File and folder workflows help page, and CHANGELOG.md describe the feature.

Related Issue

Closes #632

Verification

Focused tests:

  • headless planning produces the same Markdown as the opened editor's Replace all and Save across the corpus, refuses a footnote label collision, and drops runs a deletion empties;
  • splicing over the corpus changes only lines holding the replacement and reads back as Save's output, keeps an untouched table as authored, and falls back when a replaced line is one Save reformats;
  • the engine's replace mode: per-file plans with their versions, no result limit, rereading only files whose kept text matches, refused and unchanged files, the open document planned from its editor, a plan withdrawn the moment it is refreshed in place, and a 10,000-file plan in bounded reads;
  • the Apply workflow: no writes before Apply, path order, files changed before and after the preflight, a per-file failure, a failed preflight, bounded batches with progress, a plan the fields have moved on from, the Save, Discard, and Cancel gate, the open document replaced in its editor and saved, and a disk change or save conflict on it;
  • the panel's replace toggle and focus, before-and-after rows and their accessible names, Replace all availability, progress, and the report;
  • Rust unit and contract tests for preflight, writes in each file's encoding and byte order mark, changed, missing, and same-metadata files, unrepresentable characters, paths outside the folder and symlinks, request bounds, and serialized outcomes.

pnpm check:frontend and pnpm check:backend pass.

A throwaway Vitest probe planned replacing the across 10,000 corpus-derived files (11.7 MB) with the real parser: about 110 s for 34,689 matches in 5,249 files, never holding the main thread longer than about 280 ms, roughly double search's per-file cost. 896 of those files fell back to a whole write.

Not verified: the desktop E2E case added to the folder-search scenario, which replaces across the fixture folder with its open document and checks the files on disk and the absence of an external-change warning, was not run locally and is left to CI. Planning time was not measured in the release app, and the rendered sidebar was not inspected by hand.

Notes

  • Each write checks the file and then renames over it, so a change landing between those two steps is not caught, as with Save.
  • An open document whose save fails after taking the replacement keeps it as unsaved changes, and the report says so.

@Azganoth Azganoth added the Feature New feature or request label Oct 5, 2026
@Azganoth Azganoth self-assigned this Oct 5, 2026
@Azganoth
Azganoth merged commit 8d32705 into main Oct 5, 2026
3 checks passed
@Azganoth
Azganoth deleted the feature/folder-replace branch October 5, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Replace text across the current folder

1 participant