Skip to content

Commit 11f285a

Browse files
committed
Accept all open sweeps viewer backed pairs
"Accept all open" matched a move by its live process, which is what DiffRunner records for every tool that opens one window per pair. The viewer opens none: a pair whose tool is the viewer is tracked with no process id, because it is drawn as a row in the one window every pending pair shares and so nothing may kill it. The one kind of move that is unambiguously on screen was therefore the one kind the hot key could never match, and with the viewer as the resolved tool the key looked dead. The process test predates the viewer becoming a diff tool and was not revisited then. TrackedMove now answers IsOpen for itself - the viewer, or a live process - and AcceptOpen asks that instead. Whether a tracked move is a viewer one is read off its executable name rather than through DiffTools.TryFindByPath, which is an exact path lookup: the sender resolves the copy bundled in its own DiffEngine package and a tray carries one of its own, so those two paths are never the same string. IsOpen joins the debug view, since it is the field this turned on.
1 parent 1e74954 commit 11f285a

8 files changed

Lines changed: 146 additions & 5 deletions

File tree

docs/mdsource/tray.source.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Registers a system wide HotKey to accept pending:
141141
Registers a system wide HotKey to accept pending:
142142

143143
* Deletes
144-
* Moves that are currently open in a diff tool
144+
* Moves that are currently open in a diff tool. A pair whose tool is the viewer counts: it is drawn as a row in the one window every pending pair shares, rather than in a process of its own
145145
* Inline snapshots, all of which are open by definition: the viewer only stays running while it has something to show
146146

147147
To limit impact on system resources, the [default max concurrent open tool instances is limited to 5](/docs/diff-tool.md#maxinstancestolaunch).

docs/tray.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ Registers a system wide HotKey to accept pending:
148148
Registers a system wide HotKey to accept pending:
149149

150150
* Deletes
151-
* Moves that are currently open in a diff tool
151+
* Moves that are currently open in a diff tool. A pair whose tool is the viewer counts: it is drawn as a row in the one window every pending pair shares, rather than in a process of its own
152152
* Inline snapshots, all of which are open by definition: the viewer only stays running while it has something to show
153153

154154
To limit impact on system resources, the [default max concurrent open tool instances is limited to 5](/docs/diff-tool.md#maxinstancestolaunch).

src/DiffEngine/Tray/PendingFiles.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,46 @@ public static void SettleDiff(string tempFile) =>
209209
public static bool IsViewer(ResolvedTool tool) =>
210210
tool.Tool == DiffTool.DiffEngineViewer;
211211

212+
/// <summary>
213+
/// The same question asked of a move that is already tracked, where all that survives of the
214+
/// tool is the executable it was recorded with.
215+
/// <para>
216+
/// By file name rather than through <see cref="DiffTools.TryFindByPath"/>, which is an exact
217+
/// path lookup: the sender resolved the viewer bundled inside its own DiffEngine package and
218+
/// a tray carries a copy of its own, so the two paths are never the same string.
219+
/// </para>
220+
/// </summary>
221+
public static bool IsViewerExe(string? exe) =>
222+
exe != null &&
223+
viewerExeNames.Contains(Path.GetFileName(exe));
224+
225+
// Read off the definition rather than spelled again here, so renaming the executable cannot
226+
// leave this matching the old name. Every OS's name, because the string being tested arrived
227+
// from another process rather than from this one.
228+
static readonly HashSet<string> viewerExeNames = ViewerExeNames();
229+
230+
static HashSet<string> ViewerExeNames()
231+
{
232+
var support = Definitions.Tools
233+
.Single(_ => _.Tool == DiffTool.DiffEngineViewer)
234+
.OsSupport;
235+
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
236+
foreach (var settings in new[]
237+
{
238+
support.Windows,
239+
support.Linux,
240+
support.Osx
241+
})
242+
{
243+
if (settings != null)
244+
{
245+
names.Add(settings.ExeName);
246+
}
247+
}
248+
249+
return names;
250+
}
251+
212252
/// <summary>
213253
/// How a tracked move is opened again, and whether the window that opens may be killed.
214254
/// <para>

src/DiffEngineTray.Tests/DebugReportTests.Full.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Moves (1)
2121
CanKill: True
2222
KillLockingProcess: False
2323
Process: none
24+
IsOpen: False
2425

2526
Snapshots (3)
2627
-------------
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/// <summary>
2+
/// Which pending moves the "Accept all open" hot key sweeps.
3+
/// <para>
4+
/// The rule is "a window is showing this pair", and for every tool but one that is a live process
5+
/// DiffRunner started for it. The viewer is the exception: it draws every pending pair as a row in
6+
/// one shared window, so no process id is ever sent for one and none may be killed. Testing the
7+
/// process alone therefore left exactly the pairs that were on screen out of the sweep, and the
8+
/// hot key looked dead to anyone whose diff tool is the viewer.
9+
/// </para>
10+
/// </summary>
11+
public class TrackerAcceptOpenTest :
12+
IDisposable
13+
{
14+
[Test]
15+
public async Task AViewerPairIsOpenEvenWithNoProcess()
16+
{
17+
await using var tracker = new RecordingTracker(inline: new StubInlineHost());
18+
tracker.AddMove(temp, target, viewerExe, "--diff", false, null);
19+
20+
await tracker.AcceptOpen();
21+
22+
await tracker.AssertEmpty();
23+
await Assert.That(File.Exists(temp)).IsFalse();
24+
await Assert.That(File.ReadAllText(target)).IsEqualTo("received");
25+
}
26+
27+
/// <summary>
28+
/// The other half of the rule, so the fix for the viewer does not quietly turn "accept all
29+
/// open" into "accept all": a pair whose own window has gone is still not open.
30+
/// </summary>
31+
[Test]
32+
public async Task AnotherToolWithNoProcessIsNotOpen()
33+
{
34+
await using var tracker = new RecordingTracker(inline: new StubInlineHost());
35+
tracker.AddMove(temp, target, "theExe", "theArguments", true, null);
36+
37+
await tracker.AcceptOpen();
38+
39+
await Assert.That(tracker.Moves).HasSingleItem();
40+
}
41+
42+
[Test]
43+
public async Task TheViewerIsRecognisedByNameRatherThanByPath()
44+
{
45+
// The sender resolved the copy bundled in its own DiffEngine package, so the path is one
46+
// this process has never seen and the tool lookup finds nothing for it.
47+
await Assert.That(PendingFiles.IsViewerExe(viewerExe)).IsTrue();
48+
await Assert.That(PendingFiles.IsViewerExe("theExe")).IsFalse();
49+
await Assert.That(PendingFiles.IsViewerExe(null)).IsFalse();
50+
}
51+
52+
static readonly string viewerExe = Path.Combine(
53+
Path.GetTempPath(),
54+
"some-other-package",
55+
"viewer",
56+
OperatingSystem.IsWindows() ? "DiffEngineViewer.exe" : "DiffEngineViewer");
57+
58+
readonly string directory = Path.Combine(Path.GetTempPath(), $"AcceptOpen {Guid.NewGuid():N}");
59+
readonly string temp;
60+
readonly string target;
61+
62+
public TrackerAcceptOpenTest()
63+
{
64+
Directory.CreateDirectory(directory);
65+
temp = Path.Combine(directory, "Sample.Test.received.txt");
66+
target = Path.Combine(directory, "Sample.Test.verified.txt");
67+
File.WriteAllText(temp, "received");
68+
File.WriteAllText(target, "verified");
69+
}
70+
71+
public void Dispose() =>
72+
Directory.Delete(directory, true);
73+
}

src/DiffEngineTray/DebugReport.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public static string Build(Tracker tracker, DateTime now)
6464
AppendField(builder, "CanKill", move.CanKill);
6565
AppendField(builder, "KillLockingProcess", move.KillLockingProcess);
6666
AppendField(builder, "Process", Describe(move.Process));
67+
// What "accept all open" acts on, which is not the process for a viewer backed pair.
68+
AppendField(builder, "IsOpen", move.IsOpen);
6769
}
6870

6971
var queued = tracker.QueuedPatches;

src/DiffEngineTray/TrackedMove.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ public TrackedMove(string temp,
88
Process? process,
99
string? group,
1010
string extension,
11-
bool killLockingProcess = false)
11+
bool killLockingProcess = false,
12+
bool isViewer = false)
1213
{
1314
Temp = temp;
1415
Target = target;
@@ -20,6 +21,7 @@ public TrackedMove(string temp,
2021
Process = process;
2122
Group = group;
2223
KillLockingProcess = killLockingProcess;
24+
IsViewer = isViewer;
2325
}
2426

2527
public string Extension { get; }
@@ -32,4 +34,24 @@ public TrackedMove(string temp,
3234
public Process? Process { get; set; }
3335
public string? Group { get; }
3436
public bool KillLockingProcess { get; }
37+
38+
/// <summary>
39+
/// Whether the tool showing this pair is the viewer, which is the one tool that opens no
40+
/// process of its own for it.
41+
/// </summary>
42+
public bool IsViewer { get; }
43+
44+
/// <summary>
45+
/// Whether something is showing this pair right now, which is what "accept all open" acts on.
46+
/// <para>
47+
/// For every other tool that is a live process, because DiffRunner started one window per
48+
/// pair and recorded it. A viewer backed pair has none by construction - it is drawn as a row
49+
/// in the one window holding every pending pair, which is why nothing may kill it and why no
50+
/// process id is sent - so the process test alone left those rows out of every "accept all
51+
/// open" while the window they were drawn in sat on the screen.
52+
/// </para>
53+
/// </summary>
54+
public bool IsOpen =>
55+
IsViewer ||
56+
Process is {HasExited: false};
3557
}

src/DiffEngineTray/Tracker.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,10 @@ static TrackedMove BuildTrackedMove(string temp, string? exe, string? arguments,
227227
}
228228
}
229229

230-
return new(temp, target, exe, arguments, canKill.GetValueOrDefault(false), process, solution, extension, killLockingProcess);
230+
// Off the resolved executable rather than the resolved tool, because the sender's viewer
231+
// and this tray's are different copies at different paths, so the path lookup above finds
232+
// nothing for the one case that matters most here.
233+
return new(temp, target, exe, arguments, canKill.GetValueOrDefault(false), process, solution, extension, killLockingProcess, PendingFiles.IsViewerExe(exe));
231234
}
232235

233236
/// <summary>
@@ -712,7 +715,7 @@ public Task AcceptOpen()
712715

713716
AcceptMoves(
714717
moves.Values
715-
.Where(_ => _.Process is { HasExited: false })
718+
.Where(_ => _.IsOpen)
716719
.ToList());
717720

718721
// Every pending snapshot is open by definition: the viewer only stays running while it

0 commit comments

Comments
 (0)