From 111bf2966a02af38af528b65543369bc7e86e4cb Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 6 Apr 2024 13:14:22 +0800 Subject: [PATCH] refactor: rewrite external editor supports * supported editors can be different on different platforms. * display founded editors only --- README.md | 14 +- src/Commands/Command.cs | 4 +- src/Commands/GetImageFileAsBitmap.cs | 2 +- src/Commands/SaveChangesAsPatch.cs | 2 +- src/Commands/SaveRevisionFile.cs | 2 +- src/Models/ExternalEditor.cs | 24 +++ src/Models/ExternalMergeTools.cs | 23 +-- src/Native/Linux.cs | 93 +++++++++-- src/Native/MacOS.cs | 93 +++++++++-- src/Native/OS.cs | 68 +------- src/Native/Windows.cs | 156 +++++++++++++----- .../ExternalToolIcons/vscode_insiders.png | Bin 0 -> 16797 bytes src/Resources/Locales.Designer.cs | 35 ++-- src/Resources/Locales.en.resx | 13 +- src/Resources/Locales.resx | 13 +- src/Resources/Locales.zh.resx | 9 +- src/ViewModels/Preference.cs | 6 +- src/ViewModels/Repository.cs | 45 +++-- src/Views/Repository.axaml | 16 +- src/Views/Repository.axaml.cs | 19 ++- 20 files changed, 417 insertions(+), 220 deletions(-) create mode 100644 src/Models/ExternalEditor.cs create mode 100644 src/Resources/ExternalToolIcons/vscode_insiders.png diff --git a/README.md b/README.md index aa21ee55..99928589 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,6 @@ You can download the latest stable from [Releases](https://github.com/sourcegit- For **Windows** users: * **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead. -* You can use `Visual Studio Code Insiders` as the same way as `Visual Studio Code` in this software. For **macOS** users: @@ -54,10 +53,17 @@ For **Linux** users: * Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI. * Modify `SourceGit.desktop.template` (replace SOURCEGIT_LOCAL_FOLDER with real path) and move it into `~/.local/share/applications`. -Other tips: +## External Editors -* You can set `VSCODE_PATH` environment variable if VSCode can NOT be found when you click `Open In Visual Studio Code`. -* You can set `FLEET_PATH` environment variable if JetBrains Fleet can NOT be found when you click `Open In Fleet`. +This app supports open repository in external editors listed in the table below. + +| Editor | Windows | macOS | Linux | Environment Variable | +| --- | --- | --- | --- | --- | +| Visual Studio Code | YES | YES | YES | VSCODE_PATH | +| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH | +| JetBrains Fleet | YES | YES | YES | FLEET_PATH | + +You can set the given environment variable for special editor if it can NOT be found by this app automatically. ## Screen Shots diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 8667346e..c30a3d45 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -32,7 +32,7 @@ namespace SourceGit.Commands public bool Exec() { var start = new ProcessStartInfo(); - start.FileName = Native.OS.GitInstallPath; + start.FileName = Native.OS.GitExecutable; start.Arguments = "--no-pager -c core.quotepath=off " + Args; start.UseShellExecute = false; start.CreateNoWindow = true; @@ -144,7 +144,7 @@ namespace SourceGit.Commands public ReadToEndResult ReadToEnd() { var start = new ProcessStartInfo(); - start.FileName = Native.OS.GitInstallPath; + start.FileName = Native.OS.GitExecutable; start.Arguments = "--no-pager -c core.quotepath=off " + Args; start.UseShellExecute = false; start.CreateNoWindow = true; diff --git a/src/Commands/GetImageFileAsBitmap.cs b/src/Commands/GetImageFileAsBitmap.cs index e145d67f..981b33ba 100644 --- a/src/Commands/GetImageFileAsBitmap.cs +++ b/src/Commands/GetImageFileAsBitmap.cs @@ -12,7 +12,7 @@ namespace SourceGit.Commands { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; - starter.FileName = Native.OS.GitInstallPath; + starter.FileName = Native.OS.GitExecutable; starter.Arguments = $"show {revision}:\"{file}\""; starter.UseShellExecute = false; starter.CreateNoWindow = true; diff --git a/src/Commands/SaveChangesAsPatch.cs b/src/Commands/SaveChangesAsPatch.cs index f15cc2f2..409127ba 100644 --- a/src/Commands/SaveChangesAsPatch.cs +++ b/src/Commands/SaveChangesAsPatch.cs @@ -27,7 +27,7 @@ namespace SourceGit.Commands { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; - starter.FileName = Native.OS.GitInstallPath; + starter.FileName = Native.OS.GitExecutable; starter.Arguments = $"diff --ignore-cr-at-eol --unified=4 {opt}"; starter.UseShellExecute = false; starter.CreateNoWindow = true; diff --git a/src/Commands/SaveRevisionFile.cs b/src/Commands/SaveRevisionFile.cs index 41575efc..6c200940 100644 --- a/src/Commands/SaveRevisionFile.cs +++ b/src/Commands/SaveRevisionFile.cs @@ -30,7 +30,7 @@ namespace SourceGit.Commands { var starter = new ProcessStartInfo(); starter.WorkingDirectory = repo; - starter.FileName = Native.OS.GitInstallPath; + starter.FileName = Native.OS.GitExecutable; starter.Arguments = args; starter.UseShellExecute = false; starter.CreateNoWindow = true; diff --git a/src/Models/ExternalEditor.cs b/src/Models/ExternalEditor.cs new file mode 100644 index 00000000..8ae16132 --- /dev/null +++ b/src/Models/ExternalEditor.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; + +namespace SourceGit.Models +{ + public class ExternalEditor + { + public string Name { get; set; } = string.Empty; + public Uri Icon { get; set; } = null; + public string Executable { get; set; } = string.Empty; + public string OpenCmdArgs { get; set; } = string.Empty; + + public void Open(string repo) + { + Process.Start(new ProcessStartInfo() + { + WorkingDirectory = repo, + FileName = Executable, + Arguments = string.Format(OpenCmdArgs, repo), + UseShellExecute = false, + }); + } + } +} diff --git a/src/Models/ExternalMergeTools.cs b/src/Models/ExternalMergeTools.cs index 1871faf2..4baab460 100644 --- a/src/Models/ExternalMergeTools.cs +++ b/src/Models/ExternalMergeTools.cs @@ -20,12 +20,13 @@ namespace SourceGit.Models { Supported = new List() { new ExternalMergeTools(0, "Custom", "", "", ""), - new ExternalMergeTools(1, "Visual Studio Code", "Code.exe;Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(2, "Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(3, "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""), - new ExternalMergeTools(4, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(5, "Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(6, "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(1, "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(2, "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(3, "Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(4, "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""), + new ExternalMergeTools(5, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(6, "Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(7, "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsMacOS()) @@ -34,8 +35,9 @@ namespace SourceGit.Models new ExternalMergeTools(0, "Custom", "", "", ""), new ExternalMergeTools(1, "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMergeTools(2, "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(3, "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(4, "Beyond Compare 4", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(3, "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(4, "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(5, "Beyond Compare 4", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsLinux()) @@ -43,8 +45,9 @@ namespace SourceGit.Models Supported = new List() { new ExternalMergeTools(0, "Custom", "", "", ""), new ExternalMergeTools(1, "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(2, "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), - new ExternalMergeTools(3, "Beyond Compare 4", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(2, "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(3, "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMergeTools(4, "Beyond Compare 4", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), }; } else diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 9c358cb4..5b7c02e8 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.Versioning; @@ -30,20 +31,47 @@ namespace SourceGit.Native return string.Empty; } - public string FindVSCode() + public List FindExternalEditors() { - var toolPath = "/usr/share/code/code"; - if (File.Exists(toolPath)) - return toolPath; - return string.Empty; - } + var editors = new List(); - public string FindFleet() - { - var toolPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/.local/share/JetBrains/Toolbox/apps/fleet/bin/Fleet"; - if (File.Exists(toolPath)) - return toolPath; - return string.Empty; + var vscode = FindVSCode(); + if (!string.IsNullOrEmpty(vscode) && File.Exists(vscode)) + { + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode.png", UriKind.Absolute), + Executable = vscode, + OpenCmdArgs = "\"{0}\"", + }); + } + + var vscodeInsiders = FindVSCodeInsiders(); + if (!string.IsNullOrEmpty(vscodeInsiders) && File.Exists(vscodeInsiders)) + { + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code - Insiders", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode_insiders.png", UriKind.Absolute), + Executable = vscodeInsiders, + OpenCmdArgs = "\"{0}\"", + }); + } + + var fleet = FindFleet(); + if (!string.IsNullOrEmpty(fleet) && File.Exists(fleet)) + { + editors.Add(new Models.ExternalEditor + { + Name = "JetBrains Fleet", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/fleet.png", UriKind.Absolute), + Executable = fleet, + OpenCmdArgs = "\"{0}\"", + }); + } + + return editors; } public void OpenBrowser(string url) @@ -119,5 +147,46 @@ namespace SourceGit.Native proc.Close(); } + + #region EXTERNAL_EDITORS_FINDER + private string FindVSCode() + { + var toolPath = "/usr/share/code/code"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("VSCODE_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + + private string FindVSCodeInsiders() + { + var toolPath = "/usr/share/code/code"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("VSCODE_INSIDERS_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + + private string FindFleet() + { + var toolPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/.local/share/JetBrains/Toolbox/apps/fleet/bin/Fleet"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("FLEET_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + #endregion } } diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index e19ad328..bc632ca1 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.Versioning; @@ -27,20 +28,47 @@ namespace SourceGit.Native return string.Empty; } - public string FindVSCode() + public List FindExternalEditors() { - var toolPath = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; - if (File.Exists(toolPath)) - return toolPath; - return string.Empty; - } + var editors = new List(); - public string FindFleet() - { - var toolPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"; - if (File.Exists(toolPath)) - return toolPath; - return string.Empty; + var vscode = FindVSCode(); + if (!string.IsNullOrEmpty(vscode) && File.Exists(vscode)) + { + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode.png", UriKind.Absolute), + Executable = vscode, + OpenCmdArgs = "\"{0}\"", + }); + } + + var vscodeInsiders = FindVSCodeInsiders(); + if (!string.IsNullOrEmpty(vscodeInsiders) && File.Exists(vscodeInsiders)) + { + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code - Insiders", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode_insiders.png", UriKind.Absolute), + Executable = vscodeInsiders, + OpenCmdArgs = "\"{0}\"", + }); + } + + var fleet = FindFleet(); + if (!string.IsNullOrEmpty(fleet) && File.Exists(fleet)) + { + editors.Add(new Models.ExternalEditor + { + Name = "JetBrains Fleet", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/fleet.png", UriKind.Absolute), + Executable = fleet, + OpenCmdArgs = "\"{0}\"", + }); + } + + return editors; } public void OpenBrowser(string url) @@ -82,5 +110,46 @@ namespace SourceGit.Native { Process.Start("open", file); } + + #region EXTERNAL_EDITORS_FINDER + private string FindVSCode() + { + var toolPath = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("VSCODE_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + + private string FindVSCodeInsiders() + { + var toolPath = "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("VSCODE_INSIDERS_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + + private string FindFleet() + { + var toolPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"; + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("FLEET_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + #endregion } } diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 004cfc62..61a33ed6 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -1,6 +1,5 @@ using System; -using System.Diagnostics; -using System.IO; +using System.Collections.Generic; using Avalonia; @@ -13,8 +12,7 @@ namespace SourceGit.Native void SetupApp(AppBuilder builder); string FindGitExecutable(); - string FindVSCode(); - string FindFleet(); + List FindExternalEditors(); void OpenTerminal(string workdir); void OpenInFileManager(string path, bool select); @@ -22,11 +20,8 @@ namespace SourceGit.Native void OpenWithDefaultEditor(string file); } - public static string GitInstallPath { get; set; } - - public static string VSCodeExecutableFile { get; set; } - - public static string FleetExecutableFile { get; set; } + public static string GitExecutable { get; set; } = string.Empty; + public static List ExternalEditors { get; set; } = new List(); static OS() { @@ -47,13 +42,7 @@ namespace SourceGit.Native throw new Exception("Platform unsupported!!!"); } - VSCodeExecutableFile = _backend.FindVSCode(); - if (string.IsNullOrEmpty(VSCodeExecutableFile)) - VSCodeExecutableFile = GetPathFromEnvironmentVar("VSCODE_PATH"); - - FleetExecutableFile = _backend.FindFleet(); - if (string.IsNullOrEmpty(FleetExecutableFile)) - FleetExecutableFile = GetPathFromEnvironmentVar("FLEET_PATH"); + ExternalEditors = _backend.FindExternalEditors(); } public static void SetupApp(AppBuilder builder) @@ -86,51 +75,6 @@ namespace SourceGit.Native _backend.OpenWithDefaultEditor(file); } - public static void OpenInVSCode(string repo) - { - if (string.IsNullOrEmpty(VSCodeExecutableFile)) - { - App.RaiseException(repo, "Visual Studio Code can NOT be found in your system!!!"); - return; - } - - Process.Start(new ProcessStartInfo() - { - WorkingDirectory = repo, - FileName = VSCodeExecutableFile, - Arguments = $"\"{repo}\"", - UseShellExecute = false, - }); - } - - public static void OpenInFleet(string repo) - { - if (string.IsNullOrEmpty(FleetExecutableFile)) - { - App.RaiseException(repo, "Fleet can NOT be found in your system!!!"); - return; - } - - Process.Start(new ProcessStartInfo() - { - WorkingDirectory = repo, - FileName = FleetExecutableFile, - Arguments = $"\"{repo}\"", - UseShellExecute = false, - }); - } - - private static string GetPathFromEnvironmentVar(string key) - { - var customPath = Environment.GetEnvironmentVariable(key); - if (!string.IsNullOrEmpty(customPath) && File.Exists(customPath)) - { - return customPath; - } - - return string.Empty; - } - - private static readonly IBackend _backend; + private static IBackend _backend = null; } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index f026852b..829f85c7 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -113,54 +114,47 @@ namespace SourceGit.Native return null; } - public string FindVSCode() + public List FindExternalEditors() { - var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( - Microsoft.Win32.RegistryHive.LocalMachine, - Microsoft.Win32.RegistryView.Registry64); + var editors = new List(); - // VSCode (system) - var systemVScode = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1"); - if (systemVScode != null) + var vscode = FindVSCode(); + if (!string.IsNullOrEmpty(vscode) && File.Exists(vscode)) { - return systemVScode.GetValue("DisplayIcon") as string; + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode.png", UriKind.Absolute), + Executable = vscode, + OpenCmdArgs = "\"{0}\"", + }); } - // VSCode - Insiders (system) - var systemVScodeInsiders = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1"); - if (systemVScodeInsiders != null) + var vscodeInsiders = FindVSCodeInsiders(); + if (!string.IsNullOrEmpty(vscodeInsiders) && File.Exists(vscodeInsiders)) { - return systemVScodeInsiders.GetValue("DisplayIcon") as string; + editors.Add(new Models.ExternalEditor + { + Name = "Visual Studio Code - Insiders", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/vscode_insiders.png", UriKind.Absolute), + Executable = vscodeInsiders, + OpenCmdArgs = "\"{0}\"", + }); } - var currentUser = Microsoft.Win32.RegistryKey.OpenBaseKey( - Microsoft.Win32.RegistryHive.CurrentUser, - Microsoft.Win32.RegistryView.Registry64); - - // VSCode (user) - var vscode = currentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1"); - if (vscode != null) + var fleet = FindFleet(); + if (!string.IsNullOrEmpty(fleet) && File.Exists(fleet)) { - return vscode.GetValue("DisplayIcon") as string; + editors.Add(new Models.ExternalEditor + { + Name = "JetBrains Fleet", + Icon = new Uri("avares://SourceGit/Resources/ExternalToolIcons/fleet.png", UriKind.Absolute), + Executable = fleet, + OpenCmdArgs = "\"{0}\"", + }); } - // VSCode - Insiders (user) - var vscodeInsiders = currentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{217B4C08-948D-4276-BFBB-BEE930AE5A2C}_is1"); - if (vscodeInsiders != null) - { - return vscodeInsiders.GetValue("DisplayIcon") as string; - } - - return string.Empty; - } - - public string FindFleet() - { - var toolPath = Environment.ExpandEnvironmentVariables($"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\AppData\\Local\\Programs\\Fleet\\Fleet.exe"); - if (File.Exists(toolPath)) - return toolPath; - - return string.Empty; + return editors; } public void OpenBrowser(string url) @@ -172,10 +166,11 @@ namespace SourceGit.Native public void OpenTerminal(string workdir) { - var bash = Path.Combine(Path.GetDirectoryName(OS.GitInstallPath), "bash.exe"); + var binDir = Path.GetDirectoryName(OS.GitExecutable); + var bash = Path.Combine(binDir, "bash.exe"); if (!File.Exists(bash)) { - App.RaiseException(string.IsNullOrEmpty(workdir) ? "" : workdir, $"Can NOT found bash.exe under '{Path.GetDirectoryName(OS.GitInstallPath)}'"); + App.RaiseException(string.IsNullOrEmpty(workdir) ? "" : workdir, $"Can NOT found bash.exe under '{binDir}'"); return; } @@ -227,6 +222,89 @@ namespace SourceGit.Native Process.Start(start); } + #region EXTERNAL_EDITOR_FINDER + private string FindVSCode() + { + var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.LocalMachine, + Microsoft.Win32.RegistryView.Registry64); + + // VSCode (system) + var systemVScode = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1"); + if (systemVScode != null) + { + return systemVScode.GetValue("DisplayIcon") as string; + } + + var currentUser = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.CurrentUser, + Microsoft.Win32.RegistryView.Registry64); + + // VSCode (user) + var vscode = currentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1"); + if (vscode != null) + { + return vscode.GetValue("DisplayIcon") as string; + } + + // ENV + var customPath = Environment.GetEnvironmentVariable("VSCODE_PATH"); + if (!string.IsNullOrEmpty(customPath)) + { + return customPath; + } + + return string.Empty; + } + + private string FindVSCodeInsiders() + { + var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.LocalMachine, + Microsoft.Win32.RegistryView.Registry64); + + // VSCode - Insiders (system) + var systemVScodeInsiders = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1"); + if (systemVScodeInsiders != null) + { + return systemVScodeInsiders.GetValue("DisplayIcon") as string; + } + + var currentUser = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.CurrentUser, + Microsoft.Win32.RegistryView.Registry64); + + // VSCode - Insiders (user) + var vscodeInsiders = currentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{217B4C08-948D-4276-BFBB-BEE930AE5A2C}_is1"); + if (vscodeInsiders != null) + { + return vscodeInsiders.GetValue("DisplayIcon") as string; + } + + // ENV + var customPath = Environment.GetEnvironmentVariable("VSCODE_INSIDERS_PATH"); + if (!string.IsNullOrEmpty(customPath)) + { + return customPath; + } + + return string.Empty; + } + + private string FindFleet() + { + var toolPath = Environment.ExpandEnvironmentVariables($"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\AppData\\Local\\Programs\\Fleet\\Fleet.exe"); + if (File.Exists(toolPath)) + return toolPath; + + var customPath = Environment.GetEnvironmentVariable("FLEET_PATH"); + if (!string.IsNullOrEmpty(customPath)) + return customPath; + + return string.Empty; + } + #endregion + private void OpenFolderAndSelectFile(string folderPath) { var pidl = ILCreateFromPathW(folderPath); diff --git a/src/Resources/ExternalToolIcons/vscode_insiders.png b/src/Resources/ExternalToolIcons/vscode_insiders.png new file mode 100644 index 0000000000000000000000000000000000000000..ee11c15132b6d5588fa90e1186954caa9d5790a0 GIT binary patch literal 16797 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 H?EC)!Cs5#x literal 0 HcmV?d00001 diff --git a/src/Resources/Locales.Designer.cs b/src/Resources/Locales.Designer.cs index 1ac0e291..1fc9dfd0 100644 --- a/src/Resources/Locales.Designer.cs +++ b/src/Resources/Locales.Designer.cs @@ -1600,7 +1600,7 @@ namespace SourceGit.Resources { } /// - /// Looks up a localized string similar to GIT FLOW. + /// Looks up a localized string similar to Git-Flow. /// public static string Text_GitFlow { get { @@ -2985,15 +2985,6 @@ namespace SourceGit.Resources { } } - /// - /// Looks up a localized string similar to Open In Fleet. - /// - public static string Text_Repository_Fleet { - get { - return ResourceManager.GetString("Text.Repository.Fleet", resourceCulture); - } - } - /// /// Looks up a localized string similar to LOCAL BRANCHES. /// @@ -3013,7 +3004,7 @@ namespace SourceGit.Resources { } /// - /// Looks up a localized string similar to NEW BRANCH. + /// Looks up a localized string similar to Create Branch. /// public static string Text_Repository_NewBranch { get { @@ -3021,12 +3012,21 @@ namespace SourceGit.Resources { } } + /// + /// Looks up a localized string similar to Open In {0}. + /// + public static string Text_Repository_OpenIn { + get { + return ResourceManager.GetString("Text.Repository.OpenIn", resourceCulture); + } + } + /// /// Looks up a localized string similar to Open In External Tools. /// - public static string Text_Repository_OpenWith { + public static string Text_Repository_OpenWithExternalTools { get { - return ResourceManager.GetString("Text.Repository.OpenWith", resourceCulture); + return ResourceManager.GetString("Text.Repository.OpenWithExternalTools", resourceCulture); } } @@ -3147,15 +3147,6 @@ namespace SourceGit.Resources { } } - /// - /// Looks up a localized string similar to Open In Visual Studio Code. - /// - public static string Text_Repository_VSCode { - get { - return ResourceManager.GetString("Text.Repository.VSCode", resourceCulture); - } - } - /// /// Looks up a localized string similar to WORKSPACE. /// diff --git a/src/Resources/Locales.en.resx b/src/Resources/Locales.en.resx index fc8f7760..4f2b0263 100644 --- a/src/Resources/Locales.en.resx +++ b/src/Resources/Locales.en.resx @@ -339,11 +339,8 @@ Open In File Browser - - Open In Visual Studio Code - - - Open In Fleet + + Open In {0} Open In Terminal @@ -376,7 +373,7 @@ LOCAL BRANCHES - NEW BRANCH + Create Branch REMOTES @@ -409,7 +406,7 @@ ABORT - GIT FLOW + Git-Flow Initialize Git-Flow @@ -1293,7 +1290,7 @@ APPEARANCE - + Open In External Tools diff --git a/src/Resources/Locales.resx b/src/Resources/Locales.resx index 441988ec..970752ab 100644 --- a/src/Resources/Locales.resx +++ b/src/Resources/Locales.resx @@ -339,11 +339,8 @@ Open In File Browser - - Open In Visual Studio Code - - - Open In Fleet + + Open In {0} Open In Terminal @@ -376,7 +373,7 @@ LOCAL BRANCHES - NEW BRANCH + Create Branch REMOTES @@ -409,7 +406,7 @@ ABORT - GIT FLOW + Git-Flow Initialize Git-Flow @@ -1293,7 +1290,7 @@ Appearance - + Open In External Tools diff --git a/src/Resources/Locales.zh.resx b/src/Resources/Locales.zh.resx index b4f350ba..939e99db 100644 --- a/src/Resources/Locales.zh.resx +++ b/src/Resources/Locales.zh.resx @@ -339,8 +339,8 @@ 在文件浏览器中打开 - - 在 Visual Studio Code 中打开 + + 在 {0} 中打开 在终端中打开 @@ -1317,15 +1317,12 @@ 开源免费的Git客户端 - + 使用外部工具打开 • 项目源代码地址 - - 在 Fleet 中打开 - 挑选(Cherry-Pick)操作进行中。点击【终止】回滚到操作前的状态。 diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index d5a6f3be..8be7f9e2 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -189,12 +189,12 @@ namespace SourceGit.ViewModels public string GitInstallPath { - get => Native.OS.GitInstallPath; + get => Native.OS.GitExecutable; set { - if (Native.OS.GitInstallPath != value) + if (Native.OS.GitExecutable != value) { - Native.OS.GitInstallPath = value; + Native.OS.GitExecutable = value; OnPropertyChanged(nameof(GitInstallPath)); } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 5ba26ca7..64b211b0 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -6,6 +6,9 @@ using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Platform; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; @@ -279,21 +282,43 @@ namespace SourceGit.ViewModels Native.OS.OpenInFileManager(_fullpath); } - public void OpenInVSCode() - { - Native.OS.OpenInVSCode(_fullpath); - } - - public void OpenInFleet() - { - Native.OS.OpenInFleet(_fullpath); - } - public void OpenInTerminal() { Native.OS.OpenTerminal(_fullpath); } + public ContextMenu CreateContextMenuForExternalEditors() + { + var editors = Native.OS.ExternalEditors; + if (editors.Count == 0) + { + App.RaiseException(_fullpath, "No available external editors found!"); + return null; + } + + var menu = new ContextMenu(); + menu.Placement = PlacementMode.BottomEdgeAlignedLeft; + RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality); + + foreach (var editor in editors) + { + var dupEditor = editor; + var icon = AssetLoader.Open(dupEditor.Icon); + var item = new MenuItem(); + item.Header = App.Text("Repository.OpenIn", dupEditor.Name); + item.Icon = new Image { Width = 16, Height = 16, Source = new Bitmap(icon) }; + item.Click += (o, e) => + { + dupEditor.Open(_fullpath); + e.Handled = true; + }; + + menu.Items.Add(item); + } + + return menu; + } + public void Fetch() { if (!PopupHost.CanCreatePopup()) diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 83fc4d20..4bfb98a6 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -22,22 +22,8 @@ -