diff --git a/docs/diff-tool.custom.md b/docs/diff-tool.custom.md
index a3c2453a..b503b346 100644
--- a/docs/diff-tool.custom.md
+++ b/docs/diff-tool.custom.md
@@ -22,7 +22,7 @@ var resolvedTool = DiffTools.AddTool(
exePath: diffToolPath,
binaryExtensions: new[] {"jpg"})!;
```
-snippet source | anchor
+snippet source | anchor
Add a tool based on existing resolved tool:
@@ -35,7 +35,7 @@ var resolvedTool = DiffTools.AddToolBasedOn(
name: "MyCustomDiffTool",
arguments: (temp, target) => $"\"custom args {temp}\" \"{target}\"");
```
-snippet source | anchor
+snippet source | anchor
@@ -63,7 +63,7 @@ var resolvedTool = DiffTools.AddToolBasedOn(
await DiffRunner.LaunchAsync(resolvedTool!, "PathToTempFile", "PathToTargetFile");
```
-snippet source | anchor
+snippet source | anchor
diff --git a/docs/diff-tool.md b/docs/diff-tool.md
index 32e1ce70..824a9787 100644
--- a/docs/diff-tool.md
+++ b/docs/diff-tool.md
@@ -61,7 +61,7 @@ Setting the `DiffEngine_MaxInstances` environment variable to the number of inst
```cs
DiffRunner.MaxInstancesToLaunch(10);
```
-snippet source | anchor
+snippet source | anchor
diff --git a/docs/diff-tool.order.md b/docs/diff-tool.order.md
index 85af01ff..b213d965 100644
--- a/docs/diff-tool.order.md
+++ b/docs/diff-tool.order.md
@@ -52,5 +52,5 @@ For example `VisualStudio,Meld` will result in VisualStudio then Meld then all o
```cs
DiffTools.UseOrder(DiffTool.VisualStudio, DiffTool.AraxisMerge);
```
-snippet source | anchor
+snippet source | anchor
diff --git a/src/DiffEngine.Tests/DiffToolsTest.cs b/src/DiffEngine.Tests/DiffToolsTest.cs
index b7585797..5a59b31b 100644
--- a/src/DiffEngine.Tests/DiffToolsTest.cs
+++ b/src/DiffEngine.Tests/DiffToolsTest.cs
@@ -1,6 +1,8 @@
using System.Linq;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DiffEngine;
+using DiffEngine.Tests;
using Xunit;
using Xunit.Abstractions;
@@ -37,6 +39,28 @@ public void AddTool()
Assert.Equal(resolvedTool.Name, forExtension!.Name);
}
+ [Fact]
+ public void MacDiffOrderShouldNotMessWithAddTool()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ var diffToolPath = MacDiffTool.Exe;
+ var resolvedTool = DiffTools.AddTool(
+ name: "MacDiffTool",
+ autoRefresh: true,
+ isMdi: false,
+ supportsText: true,
+ requiresTarget: true,
+ arguments: (tempFile, targetFile) => $"\"{tempFile}\" \"{targetFile}\"",
+ exePath: diffToolPath,
+ binaryExtensions: Enumerable.Empty())!;
+ DiffTools.UseOrder(DiffTool.VisualStudio, DiffTool.AraxisMerge);
+ Assert.Equal(resolvedTool.Name, DiffTools.Resolved.First().Name);
+ Assert.True(DiffTools.TryFind("txt", out var forExtension));
+ Assert.Equal(resolvedTool.Name, forExtension!.Name);
+ }
+ }
+
[Fact]
public void OrderShouldNotMessWithAddTool()
{
diff --git a/src/DiffEngine.Tests/MacDiffTool.cs b/src/DiffEngine.Tests/MacDiffTool.cs
new file mode 100644
index 00000000..9838873c
--- /dev/null
+++ b/src/DiffEngine.Tests/MacDiffTool.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace DiffEngine.Tests
+{
+ public class MacDiffTool
+ {
+ public static string Exe;
+
+ static MacDiffTool()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Exe = Path.GetFullPath(Path.Combine(AssemblyLocation.CurrentDirectory, "../../../../DiffEngineTray.Mac/bin/Debug/DiffEngineTray.Mac.app"));
+ return;
+ }
+
+ throw new Exception();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngine.sln b/src/DiffEngine.sln
index eb749ffe..6ec0648f 100644
--- a/src/DiffEngine.sln
+++ b/src/DiffEngine.sln
@@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffEngine.Tests", "DiffEng
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiffEngineTray.Tests", "DiffEngineTray.Tests\DiffEngineTray.Tests.csproj", "{E339AB28-21B2-47DB-B995-EB2C4CE8BA99}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Common", "DiffEngineTray.Common\DiffEngineTray.Common.csproj", "{B62FBE96-EB1E-4621-AA31-8971862FC4FB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -57,6 +59,12 @@ Global
{E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.MacOS|Any CPU.ActiveCfg = MacOS|Any CPU
{E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E339AB28-21B2-47DB-B995-EB2C4CE8BA99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.MacOS|Any CPU.ActiveCfg = Debug|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.MacOS|Any CPU.Build.0 = Debug|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B62FBE96-EB1E-4621-AA31-8971862FC4FB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/DiffEngine/DiffEngine.csproj b/src/DiffEngine/DiffEngine.csproj
index d18bec0a..5cc10660 100644
--- a/src/DiffEngine/DiffEngine.csproj
+++ b/src/DiffEngine/DiffEngine.csproj
@@ -6,8 +6,8 @@
netstandard2.0;netstandard2.1;net461
-
-
+
+
diff --git a/src/DiffEngineTray/AssemblyLocation.cs b/src/DiffEngineTray.Common/AssemblyLocation.cs
similarity index 100%
rename from src/DiffEngineTray/AssemblyLocation.cs
rename to src/DiffEngineTray.Common/AssemblyLocation.cs
diff --git a/src/DiffEngineTray/AsyncTimer.cs b/src/DiffEngineTray.Common/AsyncTimer.cs
similarity index 100%
rename from src/DiffEngineTray/AsyncTimer.cs
rename to src/DiffEngineTray.Common/AsyncTimer.cs
diff --git a/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj b/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj
new file mode 100644
index 00000000..c7b7504d
--- /dev/null
+++ b/src/DiffEngineTray.Common/DiffEngineTray.Common.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netstandard2.1
+ 8
+ false
+ A utility that runs in the windows tray and handles the results of file differences.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DiffEngineTray/DirectoryLauncher.cs b/src/DiffEngineTray.Common/DirectoryLauncher.cs
similarity index 100%
rename from src/DiffEngineTray/DirectoryLauncher.cs
rename to src/DiffEngineTray.Common/DirectoryLauncher.cs
diff --git a/src/DiffEngineTray/ExceptionHandler.cs b/src/DiffEngineTray.Common/ExceptionHandler.cs
similarity index 100%
rename from src/DiffEngineTray/ExceptionHandler.cs
rename to src/DiffEngineTray.Common/ExceptionHandler.cs
diff --git a/src/DiffEngineTray/FileComparer.cs b/src/DiffEngineTray.Common/FileComparer.cs
similarity index 95%
rename from src/DiffEngineTray/FileComparer.cs
rename to src/DiffEngineTray.Common/FileComparer.cs
index 23870c88..47fc76c1 100644
--- a/src/DiffEngineTray/FileComparer.cs
+++ b/src/DiffEngineTray.Common/FileComparer.cs
@@ -28,8 +28,8 @@ public static async Task FilesAreEqual(string file1, string file2)
return false;
}
- await using var fs1 = OpenRead(file1);
- await using var fs2 = OpenRead(file2);
+ using var fs1 = OpenRead(file1);
+ using var fs2 = OpenRead(file2);
return await StreamsAreEqual(fs1, fs2);
}
diff --git a/src/DiffEngineTray.Common/IMessageBox.cs b/src/DiffEngineTray.Common/IMessageBox.cs
new file mode 100644
index 00000000..72e81cb1
--- /dev/null
+++ b/src/DiffEngineTray.Common/IMessageBox.cs
@@ -0,0 +1,25 @@
+namespace DiffEngineTray.Common
+{
+ public enum MessageBoxIcon
+ {
+ Error
+ }
+
+ public enum MessageBoxButtons
+ {
+ YesNo,
+ OK
+ }
+
+ public enum AlertResult
+ {
+ NSAlertFirstButtonReturn = 1000,
+ NSAlertSecondButtonReturn = 1001,
+ NSAlertThirdButtonReturn = 1002
+ }
+
+ public interface IMessageBox
+ {
+ public bool? Show(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo);
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Common/IUpdater.cs b/src/DiffEngineTray.Common/IUpdater.cs
new file mode 100644
index 00000000..36496adb
--- /dev/null
+++ b/src/DiffEngineTray.Common/IUpdater.cs
@@ -0,0 +1,7 @@
+namespace DiffEngineTray.Common
+{
+ public interface IUpdater
+ {
+ void Run();
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Common/InternalsVisibleTo.cs b/src/DiffEngineTray.Common/InternalsVisibleTo.cs
new file mode 100644
index 00000000..5426592f
--- /dev/null
+++ b/src/DiffEngineTray.Common/InternalsVisibleTo.cs
@@ -0,0 +1,7 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("DiffEngineTray.Tests")]
+[assembly:InternalsVisibleTo("DiffEngineTray")]
+[assembly:InternalsVisibleTo("DiffEngineTray.Mac")]
+[assembly:InternalsVisibleTo("DiffEngineTray.Mac.Tests")]
+[assembly:InternalsVisibleTo("DiffEngine")]
diff --git a/src/DiffEngineTray/IssueLauncher.cs b/src/DiffEngineTray.Common/IssueLauncher.cs
similarity index 83%
rename from src/DiffEngineTray/IssueLauncher.cs
rename to src/DiffEngineTray.Common/IssueLauncher.cs
index bf47df81..c9b49c28 100644
--- a/src/DiffEngineTray/IssueLauncher.cs
+++ b/src/DiffEngineTray.Common/IssueLauncher.cs
@@ -2,19 +2,25 @@
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
-using System.Windows.Forms;
+using DiffEngineTray.Common;
-static class IssueLauncher
+class IssueLauncher
{
static ConcurrentBag recorded = new ConcurrentBag();
static string defaultBody;
-
+ static IMessageBox? messageBox;
+
static IssueLauncher()
{
defaultBody = WebUtility.UrlEncode($@" * DiffEngineTray Version: {VersionReader.VersionString}
* OS: {Environment.OSVersion.VersionString}");
}
+ public static void Initialize(IMessageBox message)
+ {
+ messageBox = message;
+ }
+
public static void Launch()
{
LinkLauncher.LaunchUrl($"https://github.com/VerifyTests/DiffEngine/issues/new?title=TODO&body={defaultBody}");
@@ -28,7 +34,7 @@ public static void LaunchForException(string message, Exception exception)
}
recorded.Add(message);
- var result = MessageBox.Show(
+ var result = messageBox?.Show(
$@"An error occurred: {message}
Logged to: {Logging.Directory}
@@ -37,9 +43,8 @@ public static void LaunchForException(string message, Exception exception)
Open an issue on GitHub?",
"DiffEngineTray Error",
- MessageBoxButtons.YesNo,
MessageBoxIcon.Error);
- if (result == DialogResult.No)
+ if (result != true)
{
return;
}
diff --git a/src/DiffEngineTray/JsonEscaping.cs b/src/DiffEngineTray.Common/JsonEscaping.cs
similarity index 100%
rename from src/DiffEngineTray/JsonEscaping.cs
rename to src/DiffEngineTray.Common/JsonEscaping.cs
diff --git a/src/DiffEngineTray/LinkLauncher.cs b/src/DiffEngineTray.Common/LinkLauncher.cs
similarity index 100%
rename from src/DiffEngineTray/LinkLauncher.cs
rename to src/DiffEngineTray.Common/LinkLauncher.cs
diff --git a/src/DiffEngineTray/Logging.cs b/src/DiffEngineTray.Common/Logging.cs
similarity index 100%
rename from src/DiffEngineTray/Logging.cs
rename to src/DiffEngineTray.Common/Logging.cs
diff --git a/src/DiffEngineTray/Payloads/DeletePayload.cs b/src/DiffEngineTray.Common/Payloads/DeletePayload.cs
similarity index 100%
rename from src/DiffEngineTray/Payloads/DeletePayload.cs
rename to src/DiffEngineTray.Common/Payloads/DeletePayload.cs
diff --git a/src/DiffEngineTray/Payloads/MovePayload.cs b/src/DiffEngineTray.Common/Payloads/MovePayload.cs
similarity index 100%
rename from src/DiffEngineTray/Payloads/MovePayload.cs
rename to src/DiffEngineTray.Common/Payloads/MovePayload.cs
diff --git a/src/DiffEngineTray/PiperClient.cs b/src/DiffEngineTray.Common/PiperClient.cs
similarity index 100%
rename from src/DiffEngineTray/PiperClient.cs
rename to src/DiffEngineTray.Common/PiperClient.cs
diff --git a/src/DiffEngineTray/PiperServer.cs b/src/DiffEngineTray.Common/PiperServer.cs
similarity index 100%
rename from src/DiffEngineTray/PiperServer.cs
rename to src/DiffEngineTray.Common/PiperServer.cs
diff --git a/src/DiffEngineTray/ProcessEx.cs b/src/DiffEngineTray.Common/ProcessEx.cs
similarity index 100%
rename from src/DiffEngineTray/ProcessEx.cs
rename to src/DiffEngineTray.Common/ProcessEx.cs
diff --git a/src/DiffEngineTray/Serializer.cs b/src/DiffEngineTray.Common/Serializer.cs
similarity index 100%
rename from src/DiffEngineTray/Serializer.cs
rename to src/DiffEngineTray.Common/Serializer.cs
diff --git a/src/DiffEngineTray/SolutionDirectoryFinder.cs b/src/DiffEngineTray.Common/SolutionDirectoryFinder.cs
similarity index 100%
rename from src/DiffEngineTray/SolutionDirectoryFinder.cs
rename to src/DiffEngineTray.Common/SolutionDirectoryFinder.cs
diff --git a/src/DiffEngineTray/TrackedDelete.cs b/src/DiffEngineTray.Common/TrackedDelete.cs
similarity index 100%
rename from src/DiffEngineTray/TrackedDelete.cs
rename to src/DiffEngineTray.Common/TrackedDelete.cs
diff --git a/src/DiffEngineTray/TrackedMove.cs b/src/DiffEngineTray.Common/TrackedMove.cs
similarity index 100%
rename from src/DiffEngineTray/TrackedMove.cs
rename to src/DiffEngineTray.Common/TrackedMove.cs
diff --git a/src/DiffEngineTray/Tracker.cs b/src/DiffEngineTray.Common/Tracker.cs
similarity index 98%
rename from src/DiffEngineTray/Tracker.cs
rename to src/DiffEngineTray.Common/Tracker.cs
index 0912d198..f135eb9d 100644
--- a/src/DiffEngineTray/Tracker.cs
+++ b/src/DiffEngineTray.Common/Tracker.cs
@@ -189,7 +189,7 @@ static void InnerMove(TrackedMove move)
{
if (File.Exists(move.Temp))
{
- File.Move(move.Temp, move.Target, true);
+ File.Move(move.Temp, move.Target); //Overload not available here
}
KillProcesses(move);
diff --git a/src/DiffEngineTray/VersionReader.cs b/src/DiffEngineTray.Common/VersionReader.cs
similarity index 100%
rename from src/DiffEngineTray/VersionReader.cs
rename to src/DiffEngineTray.Common/VersionReader.cs
diff --git a/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs b/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs
new file mode 100644
index 00000000..bc6da30a
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/AsyncTimerTests.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Threading.Tasks;
+using Xunit;
+
+public class AsyncTimerTests
+{
+ [Fact]
+ public async Task It_calls_error_callback()
+ {
+ var errorCallbackInvoked = new TaskCompletionSource();
+
+ var timer = new AsyncTimer(
+ callback: (time, token) => throw new Exception("Simulated!"),
+ interval: TimeSpan.Zero,
+ errorCallback: e => { errorCallbackInvoked.SetResult(true); });
+
+ Assert.True(await errorCallbackInvoked.Task);
+ }
+
+ [Fact]
+ public async Task It_continues_to_run_after_an_error()
+ {
+ var callbackInvokedAfterError = new TaskCompletionSource();
+
+ var fail = true;
+ var exceptionThrown = false;
+ var timer = new AsyncTimer(
+ callback: (time, token) =>
+ {
+ if (fail)
+ {
+ fail = false;
+ throw new Exception("Simulated!");
+ }
+
+ Assert.True(exceptionThrown);
+ callbackInvokedAfterError.SetResult(true);
+ return Task.FromResult(0);
+ },
+ interval: TimeSpan.Zero,
+ errorCallback: e => { exceptionThrown = true; });
+
+ Assert.True(await callbackInvokedAfterError.Task);
+ }
+
+ [Fact]
+ public async Task Stop_cancels_token_while_waiting()
+ {
+ var waitCanceled = false;
+ var delayStarted = new TaskCompletionSource();
+ var timer = new AsyncTimer(
+ callback: (time, token) => throw new Exception("Simulated!"),
+ interval: TimeSpan.FromDays(7),
+ delayStrategy: async (delayTime, token) =>
+ {
+ delayStarted.SetResult(true);
+ try
+ {
+ await Task.Delay(delayTime, token);
+ }
+ catch (OperationCanceledException)
+ {
+ waitCanceled = true;
+ }
+ });
+
+ await delayStarted.Task;
+ await timer.DisposeAsync();
+
+ Assert.True(waitCanceled);
+ }
+
+ [Fact]
+ public async Task Stop_cancels_token_while_in_callback()
+ {
+ var callbackCanceled = false;
+ var callbackStarted = new TaskCompletionSource();
+ var stopInitiated = new TaskCompletionSource();
+ var timer = new AsyncTimer(
+ callback: async (time, token) =>
+ {
+ callbackStarted.SetResult(true);
+ await stopInitiated.Task;
+ if (token.IsCancellationRequested)
+ {
+ callbackCanceled = true;
+ }
+ },
+ interval: TimeSpan.Zero);
+
+ await callbackStarted.Task;
+ var stopTask = timer.DisposeAsync();
+ stopInitiated.SetResult(true);
+ await stopTask;
+ Assert.True(callbackCanceled);
+ }
+
+ [Fact]
+ public async Task Stop_waits_for_callback_to_complete()
+ {
+ var callbackCompleted = new TaskCompletionSource();
+ var callbackTaskStarted = new TaskCompletionSource();
+ var timer = new AsyncTimer(
+ callback: (time, token) =>
+ {
+ callbackTaskStarted.SetResult(true);
+ return callbackCompleted.Task;
+ },
+ interval: TimeSpan.Zero);
+
+ await callbackTaskStarted.Task;
+
+ var stopTask = timer.DisposeAsync().AsTask();
+ var delayTask = Task.Delay(1000);
+
+ var firstToComplete = await Task.WhenAny(stopTask, delayTask);
+ Assert.Equal(delayTask, firstToComplete);
+ callbackCompleted.SetResult(true);
+
+ await stopTask;
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj b/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj
new file mode 100644
index 00000000..08734ae5
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/DiffEngineTray.Mac.Tests.csproj
@@ -0,0 +1,42 @@
+
+
+
+ netcoreapp3.1
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml b/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml
new file mode 100644
index 00000000..e107bdbc
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png
new file mode 100644
index 00000000..ee8d4bd8
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.Default.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png
new file mode 100644
index 00000000..742ed610
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.WithKeys.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs
new file mode 100644
index 00000000..1a145d19
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/HotKeyControlTests.cs
@@ -0,0 +1,37 @@
+using System.Threading.Tasks;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[UsesVerify]
+public class HotKeyControlTests :
+ XunitContextBase
+{
+ public HotKeyControlTests(ITestOutputHelper output) :
+ base(output)
+ {
+ }
+
+#if DEBUG
+ [Fact]
+ public async Task WithKeys()
+ {
+ using var target = new HotKeyControl
+ {
+ HotKey = new HotKey
+ {
+ Shift = true,
+ Key = "A"
+ }
+ };
+ await Verifier.Verify(target);
+ }
+
+ [Fact]
+ public async Task Default()
+ {
+ using var target = new HotKeyControl();
+ await Verifier.Verify(target);
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png
new file mode 100644
index 00000000..46f61742
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Empty.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png
new file mode 100644
index 00000000..0f02ef4b
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.Full.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png
new file mode 100644
index 00000000..aaed2d16
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyDelete.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png
new file mode 100644
index 00000000..e0caa2f6
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.OnlyMove.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs
new file mode 100644
index 00000000..bb7c62b8
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/MenuBuilderTest.cs
@@ -0,0 +1,78 @@
+using System.IO;
+using System.Threading.Tasks;
+using VerifyTests;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[UsesVerify]
+public class MenuBuilderTest :
+ XunitContextBase
+{
+ [Fact]
+ public async Task Empty()
+ {
+ await using var tracker = new RecordingTracker();
+ var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ await Verifier.Verify(menu, settings);
+ }
+
+ [Fact]
+ public async Task OnlyMove()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
+ var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ await Verifier.Verify(menu, settings);
+ }
+
+ [Fact]
+ public async Task OnlyDelete()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ await Verifier.Verify(menu, settings);
+ }
+
+ [Fact]
+ public async Task Full()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AddDelete(file2);
+ tracker.AddMove(file3, file3, "theExe", "theArguments", true, null);
+ tracker.AddMove(file4, file4, "theExe", "theArguments", true, null);
+ var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ await Verifier.Verify(menu, settings);
+ }
+
+ public MenuBuilderTest(ITestOutputHelper output) :
+ base(output)
+ {
+ settings = new VerifySettings();
+ file1 = "file1.txt";
+ file2 = "file2.txt";
+ file3 = "file3.txt";
+ file4 = "file4.txt";
+ File.WriteAllText(file1, "");
+ File.WriteAllText(file2, "");
+ File.WriteAllText(file3, "");
+ File.WriteAllText(file4, "");
+ }
+
+ public override void Dispose()
+ {
+ File.Delete(file1);
+ File.Delete(file2);
+ File.Delete(file3);
+ File.Delete(file4);
+ base.Dispose();
+ }
+
+ string file1;
+ string file2;
+ string file3;
+ string file4;
+ VerifySettings settings;
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs b/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs
new file mode 100644
index 00000000..63701f01
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/ModuleInitializer.cs
@@ -0,0 +1,11 @@
+using VerifyTests;
+
+public static class ModuleInitializer
+{
+ public static void Initialize()
+ {
+ VerifierSettings.DisableNewLineEscaping();
+ VerifyImageMagick.Initialize();
+ VerifyImageMagick.RegisterComparers();
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png
new file mode 100644
index 00000000..1928a666
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.Default.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png
new file mode 100644
index 00000000..68448324
Binary files /dev/null and b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.WithKeys.verified.png differ
diff --git a/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs
new file mode 100644
index 00000000..c648d019
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/OptionsFormTests.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[UsesVerify]
+public class OptionsFormTests :
+ XunitContextBase
+{
+ public OptionsFormTests(ITestOutputHelper output) :
+ base(output)
+ {
+ VersionReader.VersionString = "TheVersion";
+ }
+
+ //[Fact]
+ //[Trait("Category", "Integration")]
+ //public void Launch()
+ //{
+ // using var form = new OptionsForm(
+ // new Settings
+ // {
+ // AcceptAllHotKey = new HotKey
+ // {
+ // Shift = true,
+ // Key = "A"
+ // }
+ // },
+ // x => Task.FromResult>(new List()));
+ // form.ShowDialog();
+ // form.BringToFront();
+ //}
+#if DEBUG
+ [Fact]
+ public async Task WithKeys()
+ {
+ using var form = new OptionsForm(
+ new Settings
+ {
+ AcceptAllHotKey = new HotKey
+ {
+ Shift = true,
+ Key = "A"
+ }
+ },
+ x => Task.FromResult>(new List()));
+ await Verifier.Verify(form);
+ }
+
+ [Fact]
+ public async Task Default()
+ {
+ using var form = new OptionsForm(
+ new Settings(),
+ x => Task.FromResult>(new List()));
+ await Verifier.Verify(form);
+ }
+#endif
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt
new file mode 100644
index 00000000..a3a5e935
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/PiperTest.Delete.verified.txt
@@ -0,0 +1,3 @@
+{
+ File: 'Foo'
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt
new file mode 100644
index 00000000..e8428b79
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/PiperTest.Move.verified.txt
@@ -0,0 +1,8 @@
+{
+ Temp: 'Foo',
+ Target: 'Bar',
+ Exe: 'theExe',
+ Arguments: 'TheArguments "s"',
+ CanKill: true,
+ ProcessId: 10
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt b/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt
new file mode 100644
index 00000000..34878269
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/PiperTest.SendOnly.verified.txt
@@ -0,0 +1,37 @@
+[
+ 'Failed to send payload to DiffEngineTray.
+
+Payload:
+{
+"Type":"Move",
+"Exe":"theExe",
+"Arguments":"TheArguments \"s\"",
+"CanKill":true,
+"ProcessId":10
+}
+
+Exception:
+System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (10061): No connection could be made because the target machine actively refused it. [::ffff:127.0.0.1]:3492
+ at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
+ at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
+ at System.Net.Sockets.TcpClient.EndConnect(IAsyncResult asyncResult)
+ at System.Net.Sockets.TcpClient.<>c.b__27_1(IAsyncResult asyncResult)
+ at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
+--- End of stack trace from previous location where exception was thrown ---
+ 'Failed to send payload to DiffEngineTray.
+
+Payload:
+{
+"Type":"Delete",
+}
+
+
+Exception:
+System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (10061): No connection could be made because the target machine actively refused it. [::ffff:127.0.0.1]:3492
+ at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
+ at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
+ at System.Net.Sockets.TcpClient.EndConnect(IAsyncResult asyncResult)
+ at System.Net.Sockets.TcpClient.<>c.b__27_1(IAsyncResult asyncResult)
+ at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
+--- End of stack trace from previous location where exception was thrown ---
+]
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/PiperTest.cs b/src/DiffEngineTray.Mac.Tests/PiperTest.cs
new file mode 100644
index 00000000..b4d68e33
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/PiperTest.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using VerifyTests;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[UsesVerify]
+public class PiperTest :
+ XunitContextBase
+{
+ [Fact]
+ public async Task Delete()
+ {
+ DeletePayload received = null!;
+ var source = new CancellationTokenSource();
+ //var task = PiperServer.Start(s => { }, s => received = s, source.Token);
+ await PiperClient.SendDeleteAsync("Foo", source.Token);
+ await Task.Delay(1000);
+ source.Cancel();
+ //await task;
+ await Verifier.Verify(received);
+ }
+
+ [Fact]
+ public async Task Move()
+ {
+ MovePayload received = null!;
+ var source = new CancellationTokenSource();
+ var task = PiperServer.Start(s => received = s, s => { }, source.Token);
+ await PiperClient.SendMoveAsync("Foo", "Bar", "theExe", "TheArguments \"s\"", true, 10, source.Token);
+ await Task.Delay(1000);
+ source.Cancel();
+ await task;
+ await Verifier.Verify(received);
+ }
+
+ [Fact]
+ public async Task SendOnly()
+ {
+ var file = Path.GetFullPath("temp.txt");
+ File.Delete(file);
+ await File.WriteAllTextAsync(file, "a");
+ try
+ {
+ await PiperClient.SendMoveAsync(file, file, "theExe", "TheArguments \"s\"", true, 10);
+ await PiperClient.SendDeleteAsync(file);
+ }
+ catch (InvalidOperationException)
+ {
+ }
+
+ var settings = new VerifySettings();
+ settings.ScrubLinesContaining("temp.txt");
+ //TODO: add "scrub source dir" to verify and remove the below
+ settings.ScrubLinesContaining("PiperClient");
+ await Verifier.Verify(Logs, settings);
+ }
+
+ public PiperTest(ITestOutputHelper output) :
+ base(output)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs b/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs
new file mode 100644
index 00000000..c65444e3
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/RecordingTracker.cs
@@ -0,0 +1,17 @@
+using Xunit;
+
+class RecordingTracker :
+ Tracker
+{
+ public RecordingTracker() :
+ base(() => {}, () => {})
+ {
+ }
+
+ public void AssertEmpty()
+ {
+ Assert.Empty(Deletes);
+ Assert.Empty(Moves);
+ Assert.False(TrackingAny);
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt
new file mode 100644
index 00000000..69187cdd
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.ReadWrite.verified.txt
@@ -0,0 +1,5 @@
+{
+ AcceptAllHotKey: {
+ Key: 'T'
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs
new file mode 100644
index 00000000..b6b4e837
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/SettingsHelperTests.cs
@@ -0,0 +1,40 @@
+using System.IO;
+using System.Threading.Tasks;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[UsesVerify]
+public class SettingsHelperTests :
+ XunitContextBase
+{
+ [Fact]
+ public async Task ReadWrite()
+ {
+ var tempFile = "ReadWrite.txt";
+ File.Delete(tempFile);
+ try
+ {
+ SettingsHelper.FilePath = tempFile;
+ await SettingsHelper.Write(
+ new Settings
+ {
+ AcceptAllHotKey = new HotKey
+ {
+ Key = "T"
+ }
+ });
+ var result = await SettingsHelper.Read();
+ await Verifier.Verify(result);
+ }
+ finally
+ {
+ File.Delete(tempFile);
+ }
+ }
+
+ public SettingsHelperTests(ITestOutputHelper output) :
+ base(output)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs
new file mode 100644
index 00000000..31a00fe3
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/TrackerClearTest.cs
@@ -0,0 +1,35 @@
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+public class TrackerClearTest :
+ XunitContextBase
+{
+ [Fact]
+ public async Task Simple()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
+ tracker.Clear();
+ tracker.AssertEmpty();
+ }
+
+ public TrackerClearTest(ITestOutputHelper output) :
+ base(output)
+ {
+ file1 = Path.GetTempFileName();
+ file2 = Path.GetTempFileName();
+ }
+
+ public override void Dispose()
+ {
+ File.Delete(file1);
+ File.Delete(file2);
+ base.Dispose();
+ }
+
+ string file1;
+ string file2;
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs
new file mode 100644
index 00000000..e3254225
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/TrackerDeleteTest.cs
@@ -0,0 +1,107 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+public class TrackerDeleteTest :
+ XunitContextBase
+{
+ [Fact]
+ public async Task AddSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ Assert.Equal(1, tracker.Deletes.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public async Task AddSingle_BackgroundDelete()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ File.Delete(file1);
+ Thread.Sleep(3000);
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AddMultiple()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AddDelete(file2);
+ Assert.Equal(2, tracker.Deletes.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public void AddSame()
+ {
+ var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AddDelete(file1);
+ Assert.Equal(1, tracker.Deletes.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public async Task AcceptAllSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AcceptAll();
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AcceptAllMultiple()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddDelete(file1);
+ tracker.AddDelete(file2);
+ tracker.AcceptAll();
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AcceptSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ var tracked = tracker.AddDelete(file1);
+ tracker.Accept(tracked);
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AcceptSingle_NotEmpty()
+ {
+ await using var tracker = new RecordingTracker();
+ var tracked = tracker.AddDelete(file1);
+ tracker.AddDelete(file2);
+ tracker.Accept(tracked);
+ Assert.Equal(1, tracker.Deletes.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ public TrackerDeleteTest(ITestOutputHelper output) :
+ base(output)
+ {
+ file1 = Path.GetTempFileName();
+ file2 = Path.GetTempFileName();
+ file3 = Path.GetTempFileName();
+ }
+
+ public override void Dispose()
+ {
+ File.Delete(file1);
+ File.Delete(file2);
+ File.Delete(file3);
+ base.Dispose();
+ }
+
+ string file1;
+ string file2;
+ string file3;
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs b/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs
new file mode 100644
index 00000000..f20fb057
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/TrackerMoveTest.cs
@@ -0,0 +1,111 @@
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+public class TrackerMoveTest :
+ XunitContextBase
+{
+ [Fact]
+ public async Task AddSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ Assert.Equal(1, tracker.Moves.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public async Task AddMultiple()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
+ Assert.Equal(2, tracker.Moves.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public async Task AddSame()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ using var process = Process.GetCurrentProcess();
+ var processId = process.Id;
+ var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, processId);
+ Assert.Equal(1, tracker.Moves.Count);
+ Assert.Equal(process.Id, tracked.Process!.Id);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ [Fact]
+ public async Task AcceptAllSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ tracker.AcceptAll();
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AcceptAllMultiple()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
+ tracker.AcceptAll();
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AcceptSingle()
+ {
+ await using var tracker = new RecordingTracker();
+ var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ tracker.Accept(tracked);
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public async Task AddSingle_BackgroundDelete()
+ {
+ await using var tracker = new RecordingTracker();
+ tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ File.Delete(file1);
+ Thread.Sleep(3000);
+ tracker.AssertEmpty();
+ }
+
+ [Fact]
+ public void AcceptSingle_NotEmpty()
+ {
+ var tracker = new RecordingTracker();
+ var tracked = tracker.AddMove(file1, file1, "theExe", "theArguments", true, null);
+ tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
+ tracker.Accept(tracked);
+ Assert.Equal(1, tracker.Moves.Count);
+ Assert.True(tracker.TrackingAny);
+ }
+
+ public TrackerMoveTest(ITestOutputHelper output) :
+ base(output)
+ {
+ file1 = Path.GetTempFileName();
+ file2 = Path.GetTempFileName();
+ file3 = Path.GetTempFileName();
+ }
+
+ public override void Dispose()
+ {
+ File.Delete(file1);
+ File.Delete(file2);
+ File.Delete(file3);
+ base.Dispose();
+ }
+
+ string file1;
+ string file2;
+ string file3;
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs b/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs
new file mode 100644
index 00000000..cf2656ed
--- /dev/null
+++ b/src/DiffEngineTray.Mac.Tests/VersionReaderTest.cs
@@ -0,0 +1,18 @@
+using Xunit;
+using Xunit.Abstractions;
+
+public class VersionReaderTest :
+ XunitContextBase
+{
+ [Fact]
+ public void AddSingle()
+ {
+ Assert.NotEmpty(VersionReader.VersionString);
+ Assert.NotNull(VersionReader.VersionString);
+ }
+
+ public VersionReaderTest(ITestOutputHelper output) :
+ base(output)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac.sln b/src/DiffEngineTray.Mac.sln
new file mode 100755
index 00000000..67aaf33b
--- /dev/null
+++ b/src/DiffEngineTray.Mac.sln
@@ -0,0 +1,48 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Mac", "DiffEngineTray.Mac\DiffEngineTray.Mac.csproj", "{FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Common", "DiffEngineTray.Common\DiffEngineTray.Common.csproj", "{CD26DF8E-447A-49CE-9B8C-C31E251FFC84}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngine", "DiffEngine\DiffEngine.csproj", "{0978620F-897F-4BF4-AB1E-D47B1A17C2F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngine.Tests", "DiffEngine.Tests\DiffEngine.Tests.csproj", "{29435A62-0E93-42EA-8831-F72B4144C1EB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeDiffTool", "FakeDiffTool\FakeDiffTool.csproj", "{C614433F-1C56-4CA4-BE35-CD7B4D224A5C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiffEngineTray.Mac.Tests", "DiffEngineTray.Mac.Tests\DiffEngineTray.Mac.Tests.csproj", "{74C91284-6F82-43B4-B4B7-BFDD67FB4F26}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.ActiveCfg = Debug|x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.Build.0 = Debug|x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Debug|x86.Deploy.0 = Debug|x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.ActiveCfg = Release|x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.Build.0 = Release|x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}.Release|x86.Deploy.0 = Release|x86
+ {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Debug|x86.Build.0 = Debug|Any CPU
+ {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Release|x86.ActiveCfg = Release|Any CPU
+ {CD26DF8E-447A-49CE-9B8C-C31E251FFC84}.Release|x86.Build.0 = Release|Any CPU
+ {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Debug|x86.Build.0 = Debug|Any CPU
+ {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Release|x86.ActiveCfg = Release|Any CPU
+ {0978620F-897F-4BF4-AB1E-D47B1A17C2F9}.Release|x86.Build.0 = Release|Any CPU
+ {29435A62-0E93-42EA-8831-F72B4144C1EB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {29435A62-0E93-42EA-8831-F72B4144C1EB}.Debug|x86.Build.0 = Debug|Any CPU
+ {29435A62-0E93-42EA-8831-F72B4144C1EB}.Release|x86.ActiveCfg = Release|Any CPU
+ {29435A62-0E93-42EA-8831-F72B4144C1EB}.Release|x86.Build.0 = Release|Any CPU
+ {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Debug|x86.Build.0 = Debug|Any CPU
+ {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Release|x86.ActiveCfg = Release|Any CPU
+ {C614433F-1C56-4CA4-BE35-CD7B4D224A5C}.Release|x86.Build.0 = Release|Any CPU
+ {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Debug|x86.Build.0 = Debug|Any CPU
+ {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Release|x86.ActiveCfg = Release|Any CPU
+ {74C91284-6F82-43B4-B4B7-BFDD67FB4F26}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/src/DiffEngineTray.Mac/App.cs b/src/DiffEngineTray.Mac/App.cs
new file mode 100644
index 00000000..8c2aa167
--- /dev/null
+++ b/src/DiffEngineTray.Mac/App.cs
@@ -0,0 +1,56 @@
+using System.Diagnostics;
+using AppKit;
+using CoreGraphics;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.MacOS;
+
+namespace DiffEngineTray.Mac
+{
+ public class App : Application
+ {
+ NSViewController controller;
+
+ public void CreateMainController()
+ {
+ MainPage = new NavigationPage(new MainPage());
+ controller = MainPage.CreateViewController();
+ controller.View.Frame = new CGRect(0, 0, 300, 400);
+ }
+
+ protected override void OnStart()
+ {
+ base.OnStart();
+ Debug.WriteLine("Application started");
+ }
+
+ protected override void OnSleep()
+ {
+ base.OnSleep();
+ Debug.WriteLine("Application sleeps");
+ }
+
+ protected override void OnResume()
+ {
+ base.OnResume();
+ Debug.WriteLine("Application resumes");
+ }
+
+ public void ShowSettings(NSStatusItem statusBarItem)
+ {
+ if(MainPage == null)
+ {
+ CreateMainController();
+ Current.SendStart();
+ }
+
+ var popover = new NSPopover
+ {
+ ContentViewController = controller,
+ Behavior = NSPopoverBehavior.Transient,
+ Delegate = new PopoverDelegate()
+ };
+
+ popover.Show(statusBarItem.Button.Bounds, statusBarItem.Button, NSRectEdge.MaxYEdge);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/AppDelegate.cs b/src/DiffEngineTray.Mac/AppDelegate.cs
new file mode 100755
index 00000000..a90bdd5c
--- /dev/null
+++ b/src/DiffEngineTray.Mac/AppDelegate.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using AppKit;
+using Foundation;
+using Xamarin.Forms;
+
+namespace DiffEngineTray.Mac
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : NSApplicationDelegate
+ {
+ NSStatusItem statusBarItem;
+ PopupMenuItems popup;
+ App app;
+
+ public AppDelegate()
+ {
+ Forms.Init();
+ Application.SetCurrentApplication(app = new App());
+ CreateStatusItems();
+ SubscribeEvents();
+
+ IssueLauncher.Initialize(new MacMessageBox());
+ }
+
+ void SubscribeEvents()
+ {
+ popup.CloseApp = () => Exit();
+ popup.ShowOptions = () => app.ShowSettings(statusBarItem);
+ }
+
+ void CreateStatusItems()
+ {
+ // Create the status bar item
+ statusBarItem = PopupMenu.CreateStatusBarItem();
+ statusBarItem.Button.Activated += StatusItemActivated;
+
+ // Create the popup
+ popup = new PopupMenuItems();
+ }
+
+ void StatusItemActivated(object sender, EventArgs e)
+ {
+ statusBarItem.PopUpStatusItemMenu(popup);
+ }
+
+ void Exit()
+ {
+ NSApplication.SharedApplication.Terminate(this);
+ }
+
+ public override void DidFinishLaunching(NSNotification notification)
+ {
+ Task.Run(StartServer);
+ }
+
+ public override void WillTerminate(NSNotification notification)
+ {
+ // Insert code here to tear down your application
+ }
+
+ async Task StartServer()
+ {
+ var tokenSource = new CancellationTokenSource();
+ var cancellation = tokenSource.Token;
+
+ await using var tracker = new Tracker(
+ active: () => statusBarItem.Image = NSImage.ImageNamed("activate.ico"),
+ inactive: () => statusBarItem.Image = NSImage.ImageNamed("default.ico"));
+
+ using var task = StartServer(tracker, cancellation);
+ }
+
+ static Task StartServer(Tracker tracker, CancellationToken cancellation)
+ {
+ return PiperServer.Start(
+ payload =>
+ {
+ tracker.AddMove(
+ payload.Temp,
+ payload.Target,
+ payload.Exe,
+ payload.Arguments,
+ payload.CanKill,
+ payload.ProcessId);
+ },
+ payload => tracker.AddDelete(payload.File),
+ cancellation);
+ }
+ }
+
+ public class PopoverDelegate : NSPopoverDelegate
+ {
+ public override void DidClose(NSNotification notification)
+ {
+ Application.Current.SendSleep();
+ }
+ }
+}
diff --git a/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100755
index 00000000..6b285452
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images": [
+ {
+ "filename": "AppIcon-16.png",
+ "size": "16x16",
+ "scale": "1x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-16@2x.png",
+ "size": "16x16",
+ "scale": "2x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-32.png",
+ "size": "32x32",
+ "scale": "1x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-32@2x.png",
+ "size": "32x32",
+ "scale": "2x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-128.png",
+ "size": "128x128",
+ "scale": "1x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-128@2x.png",
+ "size": "128x128",
+ "scale": "2x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-256.png",
+ "size": "256x256",
+ "scale": "1x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-256@2x.png",
+ "size": "256x256",
+ "scale": "2x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-512.png",
+ "size": "512x512",
+ "scale": "1x",
+ "idiom": "mac"
+ },
+ {
+ "filename": "AppIcon-512@2x.png",
+ "size": "512x512",
+ "scale": "2x",
+ "idiom": "mac"
+ }
+ ],
+ "info": {
+ "version": 1,
+ "author": "xcode"
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json b/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json
new file mode 100755
index 00000000..4caf392f
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj b/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj
new file mode 100755
index 00000000..67a66d25
--- /dev/null
+++ b/src/DiffEngineTray.Mac/DiffEngineTray.Mac.csproj
@@ -0,0 +1,124 @@
+
+
+
+ Debug
+ x86
+ {FAFC7DE7-FACE-4947-8332-5D644DDC8DB7}
+ {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Exe
+ DiffEngineTray.Mac
+ DiffEngineTray.Mac
+ v2.0
+ Xamarin.Mac
+ Resources
+
+
+ true
+ full
+ false
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ false
+ Mac Developer
+ false
+ false
+ false
+ true
+ true
+ x86
+
+
+ pdbonly
+ true
+ bin\Release
+ prompt
+ 4
+ false
+ true
+ false
+ true
+ true
+ true
+ SdkOnly
+ x86
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MainPage.xaml
+
+
+
+ SettingsPage.xaml
+
+
+
+
+ OptionsForm.xaml
+
+
+
+
+
+
+
+ ViewController.cs
+
+
+
+
+
+
+
+
+
+
+
+
+ {cd26df8e-447a-49ce-9b8c-c31e251ffc84}
+ DiffEngineTray.Common
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Entitlements.plist b/src/DiffEngineTray.Mac/Entitlements.plist
new file mode 100755
index 00000000..9ae59937
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Entitlements.plist
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/DiffEngineTray.Mac/Info.plist b/src/DiffEngineTray.Mac/Info.plist
new file mode 100755
index 00000000..4ad72122
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Info.plist
@@ -0,0 +1,34 @@
+
+
+
+
+ LSUIElement
+
+ CFBundleName
+ DiffEngineTray.Mac
+ CFBundleIdentifier
+ com.companyname.DiffEngineTray.Mac
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ 10.15
+ CFBundleDevelopmentRegion
+ en
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ ????
+ NSHumanReadableCopyright
+ ${AuthorCopyright:HtmlEncode}
+ NSPrincipalClass
+ NSApplication
+ NSMainStoryboardFile
+ Main
+ XSAppIconAssets
+ Assets.xcassets/AppIcon.appiconset
+
+
diff --git a/src/DiffEngineTray.Mac/MacMessageBox.cs b/src/DiffEngineTray.Mac/MacMessageBox.cs
new file mode 100644
index 00000000..4a6bae96
--- /dev/null
+++ b/src/DiffEngineTray.Mac/MacMessageBox.cs
@@ -0,0 +1,49 @@
+using System;
+using AppKit;
+using CoreGraphics;
+using DiffEngineTray.Common;
+
+namespace DiffEngineTray.Mac
+{
+ public class MacMessageBox : IMessageBox
+ {
+ public static bool? ShowMessage(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo)
+ {
+ return new MacMessageBox().Show(message, title, icon);
+ }
+
+ public bool? Show(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo)
+ {
+ var alert = new NSAlert();
+ alert.AlertStyle = NSAlertStyle.Warning;
+ alert.MessageText = title;
+ alert.InformativeText = message;
+ alert.Icon = NSImage.ImageNamed("error.png");
+ alert.Icon.Size = new CGSize(16, 16);
+
+ AddButtons(alert, buttons);
+
+ var result = alert.RunModal();
+
+ return TranslateResult(result, buttons);
+ }
+
+ private bool? TranslateResult(nint result, MessageBoxButtons buttons)
+ {
+ return result == 1000;
+ }
+
+ private void AddButtons(NSAlert alert, MessageBoxButtons butons)
+ {
+ if (butons == MessageBoxButtons.YesNo)
+ {
+ alert.AddButton("Yes");
+ alert.AddButton("No");
+ }
+ else
+ {
+ alert.AddButton("OK");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Main.cs b/src/DiffEngineTray.Mac/Main.cs
new file mode 100755
index 00000000..8c136513
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Main.cs
@@ -0,0 +1,13 @@
+using AppKit;
+
+namespace DiffEngineTray.Mac
+{
+ static class MainClass
+ {
+ static void Main(string[] args)
+ {
+ NSApplication.Init();
+ NSApplication.Main(args);
+ }
+ }
+}
diff --git a/src/DiffEngineTray.Mac/Main.storyboard b/src/DiffEngineTray.Mac/Main.storyboard
new file mode 100755
index 00000000..4cb7c482
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Main.storyboard
@@ -0,0 +1,681 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DiffEngineTray.Mac/MainPage.xaml b/src/DiffEngineTray.Mac/MainPage.xaml
new file mode 100644
index 00000000..bc7243ce
--- /dev/null
+++ b/src/DiffEngineTray.Mac/MainPage.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/MainPage.xaml.cs b/src/DiffEngineTray.Mac/MainPage.xaml.cs
new file mode 100644
index 00000000..53419906
--- /dev/null
+++ b/src/DiffEngineTray.Mac/MainPage.xaml.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace DiffEngineTray.Mac
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class MainPage : ContentPage
+ {
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+
+ private async void GotoCalculatorClicked(object sender, EventArgs e)
+ {
+ await Navigation.PushAsync(new SettingsPage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/PopupMenu.cs b/src/DiffEngineTray.Mac/PopupMenu.cs
new file mode 100644
index 00000000..086bdd2d
--- /dev/null
+++ b/src/DiffEngineTray.Mac/PopupMenu.cs
@@ -0,0 +1,89 @@
+using System;
+using AppKit;
+using CoreGraphics;
+
+namespace DiffEngineTray.Mac
+{
+ public static class PopupMenu
+ {
+ public static NSStatusItem CreateStatusBarItem()
+ {
+ var statusBarItem = NSStatusBar.SystemStatusBar.CreateStatusItem(NSStatusItemLength.Variable);
+
+ statusBarItem.Button.Image = CreateAppIcon();
+ statusBarItem.SendActionOn(NSTouchPhase.Any);
+
+ return statusBarItem;
+ }
+
+ private static NSImage CreateAppIcon()
+ {
+ var image = NSImage.ImageNamed("icon.ico");
+ image.Size = new CGSize(16, 16);
+ image.Template = true;
+
+ return image;
+ }
+ }
+
+ public class PopupMenuItems : NSMenu
+ {
+ NSMenu menu;
+
+ public Action CloseApp { get; set; }
+ public Action ShowOptions { get; set; }
+
+
+ public PopupMenuItems()
+ {
+ Close = new NSMenuItem("Close", (s, e) => CloseApp?.Invoke());
+ Options = new NSMenuItem("Options", (s, e) => ShowOptions?.Invoke());
+ OpenLogs = new NSMenuItem("Open Logs");
+ RaiseIssue = new NSMenuItem("Raise issue");
+ Clear = new NSMenuItem("Clear");
+ PendingDeletes = new NSMenuItem("Pending Deletes");
+ PendingMoves = new NSMenuItem("Pending Moves");
+ AcceptAll = new NSMenuItem("Accept All");
+
+ AssignImages();
+ AssignItems();
+ }
+
+ void AssignImages()
+ {
+ Close.Image = NSImage.ImageNamed("exit.png");
+ Options.Image = NSImage.ImageNamed("cogs.png");
+ OpenLogs.Image = NSImage.ImageNamed("folder.png");
+ RaiseIssue.Image = NSImage.ImageNamed("link.png");
+ Clear.Image = NSImage.ImageNamed("clear.png");
+ PendingDeletes.Image = NSImage.ImageNamed("delete.png");
+ PendingMoves.Image = NSImage.ImageNamed("accept.png");
+ AcceptAll.Image = NSImage.ImageNamed("acceptAll.png");
+ }
+
+ void AssignItems()
+ {
+ AddItem(Close);
+ AddItem(Options);
+ AddItem(OpenLogs);
+ AddItem(RaiseIssue);
+ AddItem(NSMenuItem.SeparatorItem);
+ AddItem(Clear);
+ AddItem(NSMenuItem.SeparatorItem);
+ AddItem(PendingDeletes);
+ AddItem(NSMenuItem.SeparatorItem);
+ AddItem(PendingMoves);
+ AddItem(NSMenuItem.SeparatorItem);
+ AddItem(AcceptAll);
+ }
+
+ public NSMenuItem Close { get; set; }
+ public NSMenuItem Options { get; set; }
+ public NSMenuItem OpenLogs { get; set; }
+ public NSMenuItem RaiseIssue { get; set; }
+ public NSMenuItem Clear { get; set; }
+ public NSMenuItem PendingDeletes { get; set; }
+ public NSMenuItem PendingMoves { get; set; }
+ public NSMenuItem AcceptAll { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Resources/accept.png b/src/DiffEngineTray.Mac/Resources/accept.png
new file mode 100644
index 00000000..08a2f53e
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/accept.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/acceptAll.png b/src/DiffEngineTray.Mac/Resources/acceptAll.png
new file mode 100644
index 00000000..0e6a8c00
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/acceptAll.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/active.ico b/src/DiffEngineTray.Mac/Resources/active.ico
new file mode 100644
index 00000000..fd47263e
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/active.ico differ
diff --git a/src/DiffEngineTray.Mac/Resources/clear.png b/src/DiffEngineTray.Mac/Resources/clear.png
new file mode 100644
index 00000000..9cdc063c
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/clear.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/cogs.png b/src/DiffEngineTray.Mac/Resources/cogs.png
new file mode 100644
index 00000000..f4a18bdc
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/cogs.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/default.ico b/src/DiffEngineTray.Mac/Resources/default.ico
new file mode 100644
index 00000000..68590fc7
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/default.ico differ
diff --git a/src/DiffEngineTray.Mac/Resources/delete.png b/src/DiffEngineTray.Mac/Resources/delete.png
new file mode 100644
index 00000000..0e49710c
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/delete.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/error.png b/src/DiffEngineTray.Mac/Resources/error.png
new file mode 100644
index 00000000..e723999b
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/error.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/exit.png b/src/DiffEngineTray.Mac/Resources/exit.png
new file mode 100644
index 00000000..e5eb3d6a
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/exit.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/folder.png b/src/DiffEngineTray.Mac/Resources/folder.png
new file mode 100644
index 00000000..e762796d
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/folder.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/icon.ico b/src/DiffEngineTray.Mac/Resources/icon.ico
new file mode 100755
index 00000000..a2795971
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/icon.ico differ
diff --git a/src/DiffEngineTray.Mac/Resources/icon.png b/src/DiffEngineTray.Mac/Resources/icon.png
new file mode 100755
index 00000000..419fb464
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/icon.png differ
diff --git a/src/DiffEngineTray.Mac/Resources/link.png b/src/DiffEngineTray.Mac/Resources/link.png
new file mode 100644
index 00000000..789c58fb
Binary files /dev/null and b/src/DiffEngineTray.Mac/Resources/link.png differ
diff --git a/src/DiffEngineTray.Mac/Settings/HotKeyControl.Designer.cs b/src/DiffEngineTray.Mac/Settings/HotKeyControl.Designer.cs
new file mode 100644
index 00000000..d38ec722
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/HotKeyControl.Designer.cs
@@ -0,0 +1,212 @@
+partial class HotKeyControl
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.hotKey = new System.Windows.Forms.GroupBox();
+ this.hotKeyEnabled = new System.Windows.Forms.CheckBox();
+ this.keysSelectionPanel = new System.Windows.Forms.Panel();
+ this.keyPanel = new System.Windows.Forms.Panel();
+ this.keyCombo = new System.Windows.Forms.ComboBox();
+ this.keyLabel = new System.Windows.Forms.Label();
+ this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
+ this.control = new System.Windows.Forms.CheckBox();
+ this.alt = new System.Windows.Forms.CheckBox();
+ this.shift = new System.Windows.Forms.CheckBox();
+ this.helpLabel = new System.Windows.Forms.Label();
+ this.hotKey.SuspendLayout();
+ this.keysSelectionPanel.SuspendLayout();
+ this.keyPanel.SuspendLayout();
+ this.flowLayoutPanel1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // hotKey
+ //
+ this.hotKey.AutoSize = true;
+ this.hotKey.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ this.hotKey.Controls.Add(this.hotKeyEnabled);
+ this.hotKey.Controls.Add(this.keysSelectionPanel);
+ this.hotKey.Controls.Add(this.helpLabel);
+ this.hotKey.Dock = System.Windows.Forms.DockStyle.Top;
+ this.hotKey.Location = new System.Drawing.Point(0, 0);
+ this.hotKey.Name = "hotKey";
+ this.hotKey.Size = new System.Drawing.Size(367, 108);
+ this.hotKey.TabIndex = 3;
+ this.hotKey.TabStop = false;
+ //
+ // hotKeyEnabled
+ //
+ this.hotKeyEnabled.AutoSize = true;
+ this.hotKeyEnabled.Location = new System.Drawing.Point(6, -1);
+ this.hotKeyEnabled.Name = "hotKeyEnabled";
+ this.hotKeyEnabled.Size = new System.Drawing.Size(96, 19);
+ this.hotKeyEnabled.TabIndex = 3;
+ this.hotKeyEnabled.Text = "HotKey Label";
+ this.hotKeyEnabled.UseVisualStyleBackColor = true;
+ this.hotKeyEnabled.CheckedChanged += new System.EventHandler(this.hotKeyEnabled_CheckedChanged);
+ //
+ // keysSelectionPanel
+ //
+ this.keysSelectionPanel.AutoSize = true;
+ this.keysSelectionPanel.Controls.Add(this.keyPanel);
+ this.keysSelectionPanel.Controls.Add(this.flowLayoutPanel1);
+ this.keysSelectionPanel.Dock = System.Windows.Forms.DockStyle.Top;
+ this.keysSelectionPanel.Enabled = false;
+ this.keysSelectionPanel.Location = new System.Drawing.Point(3, 40);
+ this.keysSelectionPanel.Margin = new System.Windows.Forms.Padding(2);
+ this.keysSelectionPanel.Name = "keysSelectionPanel";
+ this.keysSelectionPanel.Padding = new System.Windows.Forms.Padding(5, 4, 5, 4);
+ this.keysSelectionPanel.Size = new System.Drawing.Size(361, 65);
+ this.keysSelectionPanel.TabIndex = 1;
+ //
+ // keyPanel
+ //
+ this.keyPanel.Controls.Add(this.keyCombo);
+ this.keyPanel.Controls.Add(this.keyLabel);
+ this.keyPanel.Dock = System.Windows.Forms.DockStyle.Top;
+ this.keyPanel.Location = new System.Drawing.Point(5, 33);
+ this.keyPanel.Name = "keyPanel";
+ this.keyPanel.Padding = new System.Windows.Forms.Padding(3);
+ this.keyPanel.Size = new System.Drawing.Size(351, 28);
+ this.keyPanel.TabIndex = 6;
+ //
+ // keyCombo
+ //
+ this.keyCombo.Dock = System.Windows.Forms.DockStyle.Left;
+ this.keyCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.keyCombo.FormattingEnabled = true;
+ this.keyCombo.Location = new System.Drawing.Point(38, 3);
+ this.keyCombo.Name = "keyCombo";
+ this.keyCombo.Size = new System.Drawing.Size(44, 23);
+ this.keyCombo.TabIndex = 5;
+ //
+ // keyLabel
+ //
+ this.keyLabel.AutoSize = true;
+ this.keyLabel.Dock = System.Windows.Forms.DockStyle.Left;
+ this.keyLabel.Location = new System.Drawing.Point(3, 3);
+ this.keyLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
+ this.keyLabel.Name = "keyLabel";
+ this.keyLabel.Padding = new System.Windows.Forms.Padding(3);
+ this.keyLabel.Size = new System.Drawing.Size(35, 21);
+ this.keyLabel.TabIndex = 0;
+ this.keyLabel.Text = "Key:";
+ //
+ // flowLayoutPanel1
+ //
+ this.flowLayoutPanel1.AutoSize = true;
+ this.flowLayoutPanel1.Controls.Add(this.control);
+ this.flowLayoutPanel1.Controls.Add(this.alt);
+ this.flowLayoutPanel1.Controls.Add(this.shift);
+ this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
+ this.flowLayoutPanel1.Location = new System.Drawing.Point(5, 4);
+ this.flowLayoutPanel1.Name = "flowLayoutPanel1";
+ this.flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(3);
+ this.flowLayoutPanel1.Size = new System.Drawing.Size(351, 29);
+ this.flowLayoutPanel1.TabIndex = 4;
+ //
+ // control
+ //
+ this.control.AutoSize = true;
+ this.control.Dock = System.Windows.Forms.DockStyle.Top;
+ this.control.Location = new System.Drawing.Point(5, 5);
+ this.control.Margin = new System.Windows.Forms.Padding(2);
+ this.control.Name = "control";
+ this.control.Size = new System.Drawing.Size(66, 19);
+ this.control.TabIndex = 7;
+ this.control.Text = "Control";
+ this.control.UseVisualStyleBackColor = true;
+ //
+ // alt
+ //
+ this.alt.AutoSize = true;
+ this.alt.Dock = System.Windows.Forms.DockStyle.Top;
+ this.alt.Location = new System.Drawing.Point(75, 5);
+ this.alt.Margin = new System.Windows.Forms.Padding(2);
+ this.alt.Name = "alt";
+ this.alt.Size = new System.Drawing.Size(41, 19);
+ this.alt.TabIndex = 6;
+ this.alt.Text = "Alt";
+ this.alt.UseVisualStyleBackColor = true;
+ //
+ // shift
+ //
+ this.shift.AutoSize = true;
+ this.shift.Dock = System.Windows.Forms.DockStyle.Top;
+ this.shift.Location = new System.Drawing.Point(120, 5);
+ this.shift.Margin = new System.Windows.Forms.Padding(2);
+ this.shift.Name = "shift";
+ this.shift.Size = new System.Drawing.Size(50, 19);
+ this.shift.TabIndex = 5;
+ this.shift.Text = "Shift";
+ this.shift.UseVisualStyleBackColor = true;
+ //
+ // helpLabel
+ //
+ this.helpLabel.AutoSize = true;
+ this.helpLabel.Dock = System.Windows.Forms.DockStyle.Top;
+ this.helpLabel.Location = new System.Drawing.Point(3, 19);
+ this.helpLabel.Name = "helpLabel";
+ this.helpLabel.Padding = new System.Windows.Forms.Padding(3);
+ this.helpLabel.Size = new System.Drawing.Size(38, 21);
+ this.helpLabel.TabIndex = 2;
+ this.helpLabel.Text = "Help";
+ //
+ // HotKeyControl
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.Controls.Add(this.hotKey);
+ this.Margin = new System.Windows.Forms.Padding(2);
+ this.Name = "HotKeyControl";
+ this.Size = new System.Drawing.Size(367, 122);
+ this.hotKey.ResumeLayout(false);
+ this.hotKey.PerformLayout();
+ this.keysSelectionPanel.ResumeLayout(false);
+ this.keysSelectionPanel.PerformLayout();
+ this.keyPanel.ResumeLayout(false);
+ this.keyPanel.PerformLayout();
+ this.flowLayoutPanel1.ResumeLayout(false);
+ this.flowLayoutPanel1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.GroupBox hotKey;
+ private System.Windows.Forms.Panel keysSelectionPanel;
+ private System.Windows.Forms.Panel keyPanel;
+ private System.Windows.Forms.ComboBox keyCombo;
+ private System.Windows.Forms.Label keyLabel;
+ private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
+ private System.Windows.Forms.CheckBox control;
+ private System.Windows.Forms.CheckBox alt;
+ private System.Windows.Forms.CheckBox shift;
+ private System.Windows.Forms.Label helpLabel;
+ private System.Windows.Forms.CheckBox hotKeyEnabled;
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/HotKeyControl.cs b/src/DiffEngineTray.Mac/Settings/HotKeyControl.cs
new file mode 100644
index 00000000..53638d67
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/HotKeyControl.cs
@@ -0,0 +1,70 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+public partial class HotKeyControl :
+ UserControl
+{
+ public HotKeyControl()
+ {
+ InitializeComponent();
+ keyCombo.Items.AddRange(GetAlphabet().ToArray());
+ }
+
+ public HotKey? HotKey
+ {
+ get
+ {
+ if (!hotKeyEnabled.Checked)
+ {
+ return null;
+ }
+
+ return new HotKey
+ {
+ Shift = shift.Checked,
+ Control = control.Checked,
+ Alt = alt.Checked,
+ Key = (string) keyCombo.SelectedItem
+ };
+ }
+ set
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ hotKeyEnabled.Checked = true;
+ keyCombo.SelectedItem = value.Key;
+ shift.Checked = value.Shift;
+ control.Checked = value.Control;
+ alt.Checked = value.Alt;
+ }
+ }
+
+ public string Label
+ {
+ get => hotKeyEnabled.Text;
+ set => hotKeyEnabled.Text = value;
+ }
+
+ public string? Help
+ {
+ get => (string?) helpLabel.Text;
+ set => helpLabel.Text = value;
+ }
+
+ static IEnumerable GetAlphabet()
+ {
+ for (var c = 'A'; c <= 'Z'; c++)
+ {
+ yield return c.ToString();
+ }
+ }
+
+ void hotKeyEnabled_CheckedChanged(object sender, System.EventArgs e)
+ {
+ keysSelectionPanel.Enabled = hotKeyEnabled.Checked;
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/HotKeyControl.resx b/src/DiffEngineTray.Mac/Settings/HotKeyControl.resx
new file mode 100644
index 00000000..f298a7be
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/HotKeyControl.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml b/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml
new file mode 100644
index 00000000..b6e02228
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml.cs b/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml.cs
new file mode 100644
index 00000000..97951e45
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/OptionsForm.xaml.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DiffEngineTray.Common;
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+using Setting = Settings;
+
+namespace DiffEngineTray.Mac.Settings
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class OptionsForm : ContentPage
+ {
+ public OptionsForm()
+ {
+ InitializeComponent();
+ //Icon = Images.Active;
+ //versionLabel.Text = $"Version: {VersionReader.VersionString}";
+ }
+
+ public OptionsForm(Setting settings, Func>> trySave) :
+ this()
+ {
+ this.trySave = trySave;
+ //acceptAllHotKey.HotKey = settings.AcceptAllHotKey;
+ //acceptOpenHotKey.HotKey = settings.AcceptOpenHotKey;
+ //startupCheckBox.Checked = settings.RunAtStartup;
+ }
+
+ Func>> trySave = null!;
+
+ //IUpdater updater = new WindowsAppUpdater();
+
+ async void save_Click(object sender, EventArgs e)
+ {
+ var newSettings = new Setting
+ {
+ //RunAtStartup = startupCheckBox.Checked,
+ // AcceptAllHotKey = acceptAllHotKey.HotKey,
+ // AcceptOpenHotKey = acceptOpenHotKey.HotKey
+ };
+
+ var errors = (await trySave(newSettings)).ToList();
+ if (!errors.Any())
+ {
+ //DialogResult = DialogResult.OK;
+ return;
+ }
+
+ var builder = new StringBuilder();
+ foreach (var error in errors)
+ {
+ builder.AppendLine($" * {error}");
+ }
+
+ MacMessageBox.ShowMessage(builder.ToString(), "Errors", MessageBoxIcon.Error, MessageBoxButtons.OK);
+ }
+
+ // void diffEngineLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+ // {
+ // LinkLauncher.LaunchUrl("https://github.com/VerifyTests/DiffEngine");
+ // }
+ //
+ // void trayDocsLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
+ // {
+ // LinkLauncher.LaunchUrl("https://github.com/VerifyTests/DiffEngine/blob/master/docs/tray.md");
+ // }
+ //
+ // void updateButton_Click(object sender, EventArgs e)
+ // {
+ // updater.Run();
+ // }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/OptionsFormLauncher.cs b/src/DiffEngineTray.Mac/Settings/OptionsFormLauncher.cs
new file mode 100644
index 00000000..c13fa432
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/OptionsFormLauncher.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using DiffEngineTray.Mac.Settings;
+
+static class OptionsFormLauncher
+{
+ public static async Task Launch(Tracker tracker)
+ {
+ var settings = await SettingsHelper.Read();
+ var form = new OptionsForm(
+ settings,
+ async newSettings => await Save(tracker, newSettings));
+ form.ShowDialog();
+ }
+
+ static async Task> Save(Tracker tracker, Settings settings)
+ {
+ if (!settings.IsValidate(out var errors))
+ {
+ return errors;
+ }
+
+ var saveErrors = new List();
+
+ //AddHotKey(keyRegister, settings.AcceptAllHotKey, KeyBindingIds.AcceptAll, tracker.AcceptAll, saveErrors);
+ //AddHotKey(keyRegister, settings.AcceptOpenHotKey, KeyBindingIds.AcceptOpen, tracker.AcceptOpen, saveErrors);
+
+ if (saveErrors.Any())
+ {
+ return saveErrors;
+ }
+
+ if (settings.RunAtStartup)
+ {
+// Startup.Add();
+ }
+ else
+ {
+// Startup.Remove();
+ }
+
+ await SettingsHelper.Write(settings);
+ return new List();
+ }
+
+ // static void AddHotKey(KeyRegister keyRegister, HotKey? hotKey, int id, Action action, List saveErrors)
+ // {
+ // keyRegister.ClearBinding(id);
+ // if (hotKey == null)
+ // {
+ // return;
+ // }
+ //
+ // if (!keyRegister.TryAddBinding(id, hotKey.Shift, hotKey.Control, hotKey.Alt, hotKey.Key, action))
+ // {
+ // saveErrors.Add("Binding already registered");
+ // }
+ // }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/Settings.cs b/src/DiffEngineTray.Mac/Settings/Settings.cs
new file mode 100644
index 00000000..4a11211f
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/Settings.cs
@@ -0,0 +1,6 @@
+public class Settings
+{
+ //public HotKey? AcceptAllHotKey { get; set; }
+ //public HotKey? AcceptOpenHotKey { get; set; }
+ public bool RunAtStartup { get; set; }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/SettingsHelper.cs b/src/DiffEngineTray.Mac/Settings/SettingsHelper.cs
new file mode 100644
index 00000000..547bf9ee
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/SettingsHelper.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+class SettingsHelper
+{
+ public static string FilePath;
+
+ static SettingsHelper()
+ {
+ var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+ string directory = Path.Combine(appData, "DiffEngine");
+ Directory.CreateDirectory(directory);
+ FilePath = Path.Combine(directory, "settings.json");
+ }
+
+ public static async Task Read()
+ {
+ if (File.Exists(FilePath))
+ {
+ await using var stream = File.OpenRead(FilePath);
+ return await JsonSerializer.DeserializeAsync(stream);
+ }
+
+ await File.WriteAllTextAsync(FilePath, "{}");
+ return new Settings();
+ }
+
+ public static async Task Write(Settings settings)
+ {
+ File.Delete(FilePath);
+ await using var stream = File.OpenWrite(FilePath);
+ await JsonSerializer.SerializeAsync(stream, settings);
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/Settings/SettingsValidator.cs b/src/DiffEngineTray.Mac/Settings/SettingsValidator.cs
new file mode 100644
index 00000000..f5bed203
--- /dev/null
+++ b/src/DiffEngineTray.Mac/Settings/SettingsValidator.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Linq;
+
+public static class SettingsValidator
+{
+ public static bool IsValidate(this Settings settings, out List errors)
+ {
+ errors = new List();
+ var hotKey = settings.AcceptAllHotKey;
+ if (hotKey != null)
+ {
+ if (!hotKey.Alt && !hotKey.Shift && !hotKey.Control)
+ {
+ errors.Add("HotKey: At least one modifier is required");
+ }
+
+ if (string.IsNullOrWhiteSpace(hotKey.Key))
+ {
+ errors.Add("HotKey: key is required");
+ }
+ }
+
+ return !errors.Any();
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/SettingsPage.xaml b/src/DiffEngineTray.Mac/SettingsPage.xaml
new file mode 100644
index 00000000..6f692718
--- /dev/null
+++ b/src/DiffEngineTray.Mac/SettingsPage.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/SettingsPage.xaml.cs b/src/DiffEngineTray.Mac/SettingsPage.xaml.cs
new file mode 100644
index 00000000..4cd6f6e2
--- /dev/null
+++ b/src/DiffEngineTray.Mac/SettingsPage.xaml.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using Xamarin.Forms;
+using Xamarin.Forms.Xaml;
+
+namespace DiffEngineTray.Mac
+{
+ [XamlCompilation(XamlCompilationOptions.Compile)]
+ public partial class SettingsPage : ContentPage
+ {
+ public SettingsPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DiffEngineTray.Mac/ViewController.cs b/src/DiffEngineTray.Mac/ViewController.cs
new file mode 100755
index 00000000..859648b6
--- /dev/null
+++ b/src/DiffEngineTray.Mac/ViewController.cs
@@ -0,0 +1,34 @@
+using System;
+
+using AppKit;
+using Foundation;
+
+namespace DiffEngineTray.Mac
+{
+ public partial class ViewController : NSViewController
+ {
+ public ViewController(IntPtr handle) : base(handle)
+ {
+ }
+
+ public override void ViewDidLoad()
+ {
+ base.ViewDidLoad();
+
+ // Do any additional setup after loading the view.
+ }
+
+ public override NSObject RepresentedObject
+ {
+ get
+ {
+ return base.RepresentedObject;
+ }
+ set
+ {
+ base.RepresentedObject = value;
+ // Update the view, if already loaded.
+ }
+ }
+ }
+}
diff --git a/src/DiffEngineTray.Mac/ViewController.designer.cs b/src/DiffEngineTray.Mac/ViewController.designer.cs
new file mode 100755
index 00000000..edc4ff44
--- /dev/null
+++ b/src/DiffEngineTray.Mac/ViewController.designer.cs
@@ -0,0 +1,18 @@
+// WARNING
+//
+// This file has been generated automatically by Xamarin Studio to store outlets and
+// actions made in the UI designer. If it is removed, they will be lost.
+// Manual changes to this file may not be handled correctly.
+//
+using Foundation;
+
+namespace DiffEngineTray.Mac
+{
+ [Register("ViewController")]
+ partial class ViewController
+ {
+ void ReleaseDesignerOutlets()
+ {
+ }
+ }
+}
diff --git a/src/DiffEngineTray.Tests/MenuBuilderTest.cs b/src/DiffEngineTray.Tests/MenuBuilderTest.cs
index 66fa0a7e..674d7a95 100644
--- a/src/DiffEngineTray.Tests/MenuBuilderTest.cs
+++ b/src/DiffEngineTray.Tests/MenuBuilderTest.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
+using System.Windows.Forms;
using VerifyTests;
using VerifyXunit;
using Xunit;
@@ -13,7 +14,8 @@ public class MenuBuilderTest :
public async Task Empty()
{
await using var tracker = new RecordingTracker();
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
@@ -22,7 +24,8 @@ public async Task OnlyMove()
{
await using var tracker = new RecordingTracker();
tracker.AddMove(file2, file2, "theExe", "theArguments", true, null);
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
@@ -31,7 +34,8 @@ public async Task OnlyDelete()
{
await using var tracker = new RecordingTracker();
tracker.AddDelete(file1);
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
@@ -43,7 +47,8 @@ public async Task Full()
tracker.AddDelete(file2);
tracker.AddMove(file3, file3, "theExe", "theArguments", true, null);
tracker.AddMove(file4, file4, "theExe", "theArguments", true, null);
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
@@ -53,7 +58,8 @@ public async Task Grouped()
await using var tracker = new RecordingTracker();
tracker.AddDelete("file2.txt");
tracker.AddMove(file4, "file4.txt", "theExe", "theArguments", true, null);
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
@@ -65,7 +71,8 @@ public async Task FullGrouped()
tracker.AddDelete("file2.txt");
tracker.AddMove(file3, file3, "theExe", "theArguments", true, null);
tracker.AddMove(file4, "file4.txt", "theExe", "theArguments", true, null);
- var menu = MenuBuilder.Build(() => { }, () => { }, tracker);
+ var menu = new ContextMenuStrip();
+ MenuBuilder.Build(menu, () => { }, () => { }, tracker);
await Verifier.Verify(menu, settings);
}
diff --git a/src/DiffEngineTray.Tests/OptionsFormTests.cs b/src/DiffEngineTray.Tests/OptionsFormTests.cs
index c648d019..b05c3ee5 100644
--- a/src/DiffEngineTray.Tests/OptionsFormTests.cs
+++ b/src/DiffEngineTray.Tests/OptionsFormTests.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using DiffEngineTray.Common;
using VerifyXunit;
using Xunit;
using Xunit.Abstractions;
@@ -36,6 +37,7 @@ public OptionsFormTests(ITestOutputHelper output) :
public async Task WithKeys()
{
using var form = new OptionsForm(
+ new MockMessageBox(),
new Settings
{
AcceptAllHotKey = new HotKey
@@ -52,9 +54,18 @@ public async Task WithKeys()
public async Task Default()
{
using var form = new OptionsForm(
+ new MockMessageBox(),
new Settings(),
x => Task.FromResult>(new List()));
await Verifier.Verify(form);
}
#endif
+}
+
+public class MockMessageBox : IMessageBox
+{
+ public bool? Show(string message, string title, MessageBoxIcon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo)
+ {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/DiffEngineTray/DiffEngineTray.csproj b/src/DiffEngineTray/DiffEngineTray.csproj
index 8517f1eb..6fc114d8 100644
--- a/src/DiffEngineTray/DiffEngineTray.csproj
+++ b/src/DiffEngineTray/DiffEngineTray.csproj
@@ -23,10 +23,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DiffEngineTray/MenuBuilder.cs b/src/DiffEngineTray/MenuBuilder.cs
index 7a53f92e..b3e49094 100644
--- a/src/DiffEngineTray/MenuBuilder.cs
+++ b/src/DiffEngineTray/MenuBuilder.cs
@@ -6,9 +6,8 @@
static class MenuBuilder
{
- public static ContextMenuStrip Build(Action exit, Action launchOptions, Tracker tracker)
+ public static void Build(ContextMenuStrip menu, Action exit, Action launchOptions, Tracker tracker)
{
- var menu = new ContextMenuStrip();
var items = menu.Items;
menu.Opening += delegate
{
@@ -24,7 +23,6 @@ public static ContextMenuStrip Build(Action exit, Action launchOptions, Tracker
items.Add(new MenuButton("Options", launchOptions, Images.Options));
items.Add(new MenuButton("Open logs", Logging.OpenDirectory, Images.Folder));
items.Add(new MenuButton("Raise issue", IssueLauncher.Launch, Images.Link));
- return menu;
}
static List NonDefaultMenus(ToolStripItemCollection items)
diff --git a/src/DiffEngineTray/Program.cs b/src/DiffEngineTray/Program.cs
index 5ca5f589..97a39711 100644
--- a/src/DiffEngineTray/Program.cs
+++ b/src/DiffEngineTray/Program.cs
@@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
+using DiffEngineTray;
using Serilog;
static class Program
@@ -27,6 +28,7 @@ static async Task Main()
return;
}
+
using var icon = new NotifyIcon
{
Icon = Images.Default,
@@ -52,10 +54,15 @@ static async Task Main()
using var keyRegister = new KeyRegister(icon.Handle());
ReBindKeys(settings, keyRegister, tracker);
- icon.ContextMenuStrip = MenuBuilder.Build(
+
+ icon.ContextMenuStrip = new ContextMenuStrip();
+ var messageBox = new WindowsMessageBox(icon.ContextMenuStrip);
+
+ MenuBuilder.Build(icon.ContextMenuStrip,
Application.Exit,
- async () => await OptionsFormLauncher.Launch(keyRegister, tracker),
+ async () => await OptionsFormLauncher.Launch(messageBox, keyRegister, tracker),
tracker);
+ IssueLauncher.Initialize(messageBox);
Application.Run();
tokenSource.Cancel();
diff --git a/src/DiffEngineTray/Settings/OptionsForm.cs b/src/DiffEngineTray/Settings/OptionsForm.cs
index a1bf46ff..96a7080e 100644
--- a/src/DiffEngineTray/Settings/OptionsForm.cs
+++ b/src/DiffEngineTray/Settings/OptionsForm.cs
@@ -4,21 +4,27 @@
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
+using DiffEngineTray.Common;
+using MessageBoxButtons = DiffEngineTray.Common.MessageBoxButtons;
+using MessageBoxIcon = DiffEngineTray.Common.MessageBoxIcon;
public partial class OptionsForm :
Form
{
Func>> trySave = null!;
+ IUpdater updater = new WindowsAppUpdater();
+ IMessageBox messageBox;
- public OptionsForm()
+ public OptionsForm(IMessageBox messageBox)
{
+ this.messageBox = messageBox;
InitializeComponent();
Icon = Images.Active;
versionLabel.Text = $"Version: {VersionReader.VersionString}";
}
- public OptionsForm(Settings settings, Func>> trySave) :
- this()
+ public OptionsForm(IMessageBox messageBox, Settings settings, Func>> trySave) :
+ this(messageBox)
{
this.trySave = trySave;
acceptAllHotKey.HotKey = settings.AcceptAllHotKey;
@@ -48,7 +54,7 @@ async void save_Click(object sender, EventArgs e)
builder.AppendLine($" * {error}");
}
- MessageBox.Show(builder.ToString(), "Errors", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ messageBox.Show(builder.ToString(), "Errors", MessageBoxIcon.Error, MessageBoxButtons.OK);
}
void diffEngineLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
@@ -63,6 +69,6 @@ void trayDocsLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
void updateButton_Click(object sender, EventArgs e)
{
- Updater.Run();
+ updater.Run();
}
}
\ No newline at end of file
diff --git a/src/DiffEngineTray/Settings/OptionsFormLauncher.cs b/src/DiffEngineTray/Settings/OptionsFormLauncher.cs
index 903e4693..2f44fa0e 100644
--- a/src/DiffEngineTray/Settings/OptionsFormLauncher.cs
+++ b/src/DiffEngineTray/Settings/OptionsFormLauncher.cs
@@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using DiffEngineTray.Common;
static class OptionsFormLauncher
{
- public static async Task Launch(KeyRegister keyRegister, Tracker tracker)
+ public static async Task Launch(IMessageBox messageBox, KeyRegister keyRegister, Tracker tracker)
{
var settings = await SettingsHelper.Read();
var form = new OptionsForm(
+ messageBox,
settings,
async newSettings => await Save(keyRegister, tracker, newSettings));
form.ShowDialog();
diff --git a/src/DiffEngineTray/Updater.cs b/src/DiffEngineTray/WindowsAppUpdater.cs
similarity index 87%
rename from src/DiffEngineTray/Updater.cs
rename to src/DiffEngineTray/WindowsAppUpdater.cs
index f272caac..5500ff1f 100644
--- a/src/DiffEngineTray/Updater.cs
+++ b/src/DiffEngineTray/WindowsAppUpdater.cs
@@ -2,10 +2,11 @@
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
+using DiffEngineTray.Common;
-static class Updater
+class WindowsAppUpdater : IUpdater
{
- public static void Run()
+ public void Run()
{
var psCommandBytes = Encoding.Unicode.GetBytes("dotnet tool update diffenginetray --global; diffenginetray");
var psCommandBase64 = Convert.ToBase64String(psCommandBytes);
diff --git a/src/DiffEngineTray/WindowsMessageBox.cs b/src/DiffEngineTray/WindowsMessageBox.cs
new file mode 100644
index 00000000..15ab6c67
--- /dev/null
+++ b/src/DiffEngineTray/WindowsMessageBox.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Windows.Forms;
+using DiffEngineTray.Common;
+using Icon = DiffEngineTray.Common.MessageBoxIcon;
+using MessageBoxButtons = DiffEngineTray.Common.MessageBoxButtons;
+using WinIcon = System.Windows.Forms.MessageBoxIcon;
+
+namespace DiffEngineTray
+{
+ public class WindowsMessageBox : IMessageBox
+ {
+ IWin32Window window;
+ Dictionary iconMap = new Dictionary();
+
+ public WindowsMessageBox(IWin32Window window)
+ {
+ this.window = window;
+ }
+
+ public bool? Show(string message, string title, Icon icon, MessageBoxButtons buttons = MessageBoxButtons.YesNo)
+ {
+ var mappedIcon = iconMap[icon];
+ var result = MessageBox.Show(window, message, title, System.Windows.Forms.MessageBoxButtons.YesNo, mappedIcon);
+
+ return ReturnResult(result);
+ }
+
+ bool? ReturnResult(DialogResult result)
+ {
+ if (result == DialogResult.OK || result == DialogResult.Yes)
+ return true;
+ if (result == DialogResult.No || result == DialogResult.Abort)
+ return false;
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/appveyor.yml b/src/appveyor.yml
index c8fe97de..84ea98df 100644
--- a/src/appveyor.yml
+++ b/src/appveyor.yml
@@ -1,5 +1,5 @@
image:
-- Visual Studio 2019 Preview
+- Visual Studio 2019
- macos
skip_commits:
message: /docs|Merge pull request.*/
@@ -11,12 +11,12 @@ for:
-
matrix:
only:
- - image: Visual Studio 2019 Preview
+ - image: Visual Studio 2019
build_script:
- ps: >-
- dotnet build src --configuration Release
+ dotnet build src/DiffEngine.sln --configuration Release
- dotnet test src --configuration Release --no-build --no-restore --filter Category!=Integration
+ dotnet test src/DiffEngine.sln --configuration Release --no-build --no-restore --filter Category!=Integration
-
matrix:
@@ -24,9 +24,9 @@ for:
- image: macos
build_script:
- ps: >-
- dotnet build src --configuration MacOs
+ dotnet build src/DiffEngineTray.Mac.sln --configuration Release
- dotnet test src --configuration MacOs --no-build --no-restore --filter Category!=Integration
+ dotnet test src/DiffEngineTray.Mac.sln --configuration Release --no-build --no-restore --filter Category!=Integration
on_failure:
- ps: Get-ChildItem *.received.* -recurse | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
\ No newline at end of file