From 3ebf9cb3be2520a1d2a2443c017f429e1d7e48a0 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 23 Aug 2026 09:08:25 +1000 Subject: [PATCH] Reveal the directory when the file is not there yet Revealing a pending move points at the target, which for a snapshot being written for the first time does not exist. Nothing checked, so explorer was asked to select a path that is not there - which opens the default folder, Documents, nothing to do with the review - and open -R errored. Linux happened to be fine, having only ever opened the directory. The directory is shown instead, which is where the file is about to be written, and a path whose directory is missing too opens nothing. The decision is its own function so it can be tested: everything around it starts a process. --- src/DiffEngineViewer.Tests/RevealFileTests.cs | 53 +++++++++++++++++++ src/DiffEngineViewer/RevealFile.cs | 46 ++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/DiffEngineViewer.Tests/RevealFileTests.cs diff --git a/src/DiffEngineViewer.Tests/RevealFileTests.cs b/src/DiffEngineViewer.Tests/RevealFileTests.cs new file mode 100644 index 00000000..59a7f0ac --- /dev/null +++ b/src/DiffEngineViewer.Tests/RevealFileTests.cs @@ -0,0 +1,53 @@ +/// +/// What "reveal" opens. The path it is given is often one that does not exist: revealing a pending +/// move points at the target, and for a snapshot being written for the first time nothing is there +/// yet. +/// +public class RevealFileTests : + IDisposable +{ + [Test] + public async Task A_file_that_is_there_is_selected() + { + var file = Path.Combine(directory, "sample.verified.txt"); + File.WriteAllText(file, ""); + + var resolved = RevealFile.Resolve(file); + + await Assert.That(resolved!.Value.Target).IsEqualTo(file); + await Assert.That(resolved.Value.Select).IsTrue(); + } + + /// + /// Explorer opens the default folder when asked to select a path that is not there - Documents, + /// nothing to do with the review - and open -R errors. The directory the file is about + /// to be written into is the useful answer. + /// + [Test] + public async Task A_file_that_is_not_there_yet_falls_back_to_its_directory() + { + var resolved = RevealFile.Resolve(Path.Combine(directory, "new.verified.txt")); + + await Assert.That(resolved!.Value.Target).IsEqualTo(directory); + await Assert.That(resolved.Value.Select).IsFalse(); + } + + [Test] + public async Task Nothing_is_opened_for_a_path_with_no_directory_either() + { + var missing = Path.Combine(directory, "gone", "new.verified.txt"); + + await Assert.That(RevealFile.Resolve(missing)).IsNull(); + } + + public RevealFileTests() + { + directory = Path.Combine(Path.GetTempPath(), $"RevealFileTests_{Guid.NewGuid()}"); + Directory.CreateDirectory(directory); + } + + public void Dispose() => + Directory.Delete(directory, true); + + readonly string directory; +} diff --git a/src/DiffEngineViewer/RevealFile.cs b/src/DiffEngineViewer/RevealFile.cs index 7532e5d6..fd9cc87f 100644 --- a/src/DiffEngineViewer/RevealFile.cs +++ b/src/DiffEngineViewer/RevealFile.cs @@ -7,11 +7,17 @@ static class RevealFile { public static void Show(string path) { + if (Resolve(path) is not var (target, select)) + { + return; + } + try { if (OperatingSystem.IsWindows()) { - Process.Start(new ProcessStartInfo("explorer.exe", $"/select,\"{path}\"") + var arguments = select ? $"/select,\"{target}\"" : $"\"{target}\""; + Process.Start(new ProcessStartInfo("explorer.exe", arguments) { UseShellExecute = true }); @@ -20,16 +26,48 @@ public static void Show(string path) if (OperatingSystem.IsMacOS()) { - Process.Start("open", ["-R", path]); + Process.Start("open", select ? ["-R", target] : [target]); return; } // No cross-desktop way to select a file, so the directory is the target. - Process.Start("xdg-open", [Path.GetDirectoryName(path) ?? path]); + Process.Start("xdg-open", [select ? Path.GetDirectoryName(target) ?? target : target]); } catch (Exception exception) { - Console.Error.WriteLine($"Could not open a file manager on {path}: {exception.Message}"); + Console.Error.WriteLine($"Could not open a file manager on {target}: {exception.Message}"); + } + } + + /// + /// What to open, and whether the file manager can be asked to select it. + /// + /// A path that is not there cannot be selected, and revealing a move used to hand one over + /// whenever the snapshot was new: the target of the move is where the file is going, not + /// somewhere it has been. Explorer answers that by opening the default folder - Documents, + /// nothing to do with the review - and open -R by erroring. Linux happened to work, + /// having only ever opened the directory. + /// + /// + /// So the directory is what is shown for a path that is not there yet, which is where the + /// file is about to be written. Null when even that is absent, since there is nothing useful + /// left to open. + /// + /// + internal static (string Target, bool Select)? Resolve(string path) + { + if (File.Exists(path)) + { + return (path, true); + } + + var directory = Path.GetDirectoryName(path); + if (directory is {Length: > 0} && + Directory.Exists(directory)) + { + return (directory, false); } + + return null; } }