public sealed class ExternalEditsThe editor's answer to a file it has open being changed by something else.
Remarks
The seam that was missing. The asset database, the project browser, the build panel and the shader library have all followed the watcher for as long as there has been one — but every one of them reads the drained list for a count and rescans. Nothing asked which file, so nothing could ask which document. A .vxcompositor edited in a text editor beside the running editor changed the database, the tree and the build, and did not change the panel that was open on it.
Two directions, and this class is both of them. Outward: the editor's own saves must not come back through the watcher as somebody else's edit, which is what IFileWatcher.Suppress is for and why DocumentSaving fires before the write rather than after it. Inward: a change that really did come from outside has to reach the document open on that file. Both are one object because they are one question — "is this change ours?" — and answering it in two places is how the answers drift.
The policy, which is the only decision here. A document that can re-read its file and has no unsaved edits is reloaded, silently: what was in memory was the file's previous contents and nothing else, so there is nothing to lose and no one to ask. A document with unsaved edits is not reloaded. It is marked IsStale and reported through Applied, and both copies go on existing until somebody picks one.
That follows EditorFrames.Reframe's precedent — prefer the state a person can still act on — and it is the asymmetry that decides it: an edit that exists only in this process's memory is gone the moment it is overwritten, and a file on disk is not. Reloading over unsaved work destroys the only copy of it; declining to reload costs a person one click and leaves both copies intact. Reload-when-clean and say-so-otherwise is also what every editor that has ever done this does, so it is the behaviour a person arrives already expecting.
⚠ What is deliberately not decided here is which copy wins. That is a question only a person can answer, and this is the non-destructive half of asking it: the flag on the document, the report on the event, and Reload and Save as the two answers. The editor's head puts both in front of somebody — a notification naming the file, Ctrl+S to keep theirs, and file.revert to take the file's, which asks before it discards anything.
What is not built is the banner: the offer across the document itself rather than in the corner of the window, so that the choice is made where the conflict is. That is a panel and not a mechanism, and everything it would need is already public.
⚠ Deletion is not a reload. A file that has gone would read back as empty — see AssetFile.Read, which is right for opening an asset somebody has just created and catastrophic here. A document whose file was deleted keeps what it has, which is now the only copy of it, and Save is how it comes back.
Fields and properties (1)
public EditorProject ProjectThe project whose documents this follows.
Events (1)
public Action<ExternalEdit>? AppliedRaised once per open document a drained change reached, whatever it did about it.
Methods (4)
public ExternalEdits(EditorProject project, IFileWatcher? watcher = null, string? watchedDirectory = null)Follows a project's documents, and keeps its own saves out of a watcher's way.
public int Apply(IReadOnlyList<FileChange> changes)Routes what the watcher drained to whichever open documents those files belong to.
public int Rescan()Re-reads every open document that can be, for when the events themselves were lost.
public void Dispose()Stops following the project's saves.
Used by (7)
- EditorApplicationVixen.Editor.App
- ExternalCompoundTestsVixen.Editor.Texturing.Tests
- ExternalEditPumpTestsVixen.Editor.App.Tests
- ExternalEditsTestsVixen.Editor.Core.Tests
- LayerStackBindingTestsVixen.Editor.Texturing.Tests
- LayerStackCompoundCacheTestsVixen.Editor.Texturing.Tests
- LayerStackWatchTestsVixen.Editor.Texturing.Tests