Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions MacDown2/MacDown2/FolderSearchModel+Replace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ extension FolderSearchModel {
replaceCompleted += 1
}
)
// Notified even when the run was superseded: the files are already rewritten on disk.
for result in results {
if case .replaced = result.outcome {
fileWasRewritten(root.appendingPathComponent(result.relativePath))
}
}
guard replaceGeneration == thisGeneration else { return }
replaceSummary = Self.summarize(results)
isReplacing = false
Expand Down
4 changes: 4 additions & 0 deletions MacDown2/MacDown2/FolderSearchModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ final class FolderSearchModel {
/// overwritten on disk. Assigned by `WindowController`, which knows the
/// coordinator that can see every window's tabs.
var hasUnsavedOpenDocument: @MainActor (URL) -> Bool = { _ in false }
/// Called once for every file Replace in Folder rewrote. A rewrite publishes a new inode and creation date, which
/// the Open Recent fingerprint rejects, so the entry for that file must follow it
/// (`RecentFileDocuments.noteWrite`).
var fileWasRewritten: @MainActor (URL) -> Void = { _ in }
let performReplace: FolderReplaceRunner
let performPreview: FolderReplacePreviewBuilder
var replaceTask: Task<Void, Never>?
Expand Down
3 changes: 3 additions & 0 deletions MacDown2/MacDown2/WindowController+WorkspaceIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ extension WindowController {
folderSearchModel.hasUnsavedOpenDocument = { [weak coordinator] url in
coordinator?.hasUnsavedOpenDocument(at: url) ?? false
}
folderSearchModel.fileWasRewritten = { [weak coordinator] url in
coordinator?.recentFileDocuments.noteWrite(of: url)
}
fileTreeModel.onDidMutate = { [weak self] in
Task { await self?.refreshWorkspaceIndex() }
}
Expand Down
16 changes: 16 additions & 0 deletions MacDown2/MacDown2Tests/FolderReplaceModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ struct FolderReplaceModelTests {
#expect(model.replaceSummary?.replacedFiles == 1)
}

/// Review pass 6: a rewritten file gets a new inode and creation date, so its Open Recent entry was rejected by
/// the fingerprint check and silently dropped on the next click.
@Test func everyRewrittenFileIsReportedSoRecentsCanFollowIt() async throws {
let tree = try TempTree(["a.txt": "foo a", "b.txt": "foo b", "c.txt": "none"])
let model = await searchedModel(tree, query: "foo")
var rewritten: [String] = []
model.fileWasRewritten = { rewritten.append($0.lastPathComponent) }
model.replacement = "bar"

model.requestReplace()
model.confirmReplace()
await waitUntil { model.replaceSummary != nil && model.outcome != nil }

#expect(rewritten.sorted() == ["a.txt", "b.txt"])
}

@Test func aFileOpenWithUnsavedChangesIsSkippedAndReported() async throws {
let tree = try TempTree(["dirty.txt": "foo dirty", "clean.txt": "foo clean"])
let model = await searchedModel(tree, query: "foo")
Expand Down
Loading