Merge branch 'feature/powershell_supports' into develop

This commit is contained in:
leo 2024-04-09 14:59:15 +08:00
commit f0e0c90621
26 changed files with 337 additions and 159 deletions

View file

@ -53,20 +53,20 @@ 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. * 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`. * Modify `SourceGit.desktop.template` (replace SOURCEGIT_LOCAL_FOLDER with real path) and move it into `~/.local/share/applications`.
## External Editors ## External Tools
This app supports open repository in external editors listed in the table below. This app supports open repository in external tools listed in the table below.
| Editor | Windows | macOS | Linux | Environment Variable | | Tool | Windows | macOS | Linux | Environment Variable |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Visual Studio Code | YES | YES | YES | VSCODE_PATH | | Visual Studio Code | YES | YES | YES | VSCODE_PATH |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH | | Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH |
| JetBrains Fleet | YES | YES | YES | FLEET_PATH | | JetBrains Fleet | YES | YES | YES | FLEET_PATH |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH | | Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH |
You can set the given environment variable for special editor if it can NOT be found by this app automatically. You can set the given environment variable for special tool if it can NOT be found by this app automatically.
## Screen Shots ## Screenshots
* Dark Theme * Dark Theme

View file

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SourceGit.Models
{
public class ExternalMergeTools
{
public int Type { get; set; }
public string Name { get; set; }
public string Exec { get; set; }
public string Cmd { get; set; }
public string DiffCmd { get; set; }
public static readonly List<ExternalMergeTools> Supported;
static ExternalMergeTools()
{
if (OperatingSystem.IsWindows())
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
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/2022", "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", "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())
{
Supported = new List<ExternalMergeTools>() {
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, "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", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsLinux())
{
Supported = new List<ExternalMergeTools>() {
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, "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", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
};
}
}
public ExternalMergeTools(int type, string name, string exec, string cmd, string diffCmd)
{
Type = type;
Name = name;
Exec = exec;
Cmd = cmd;
DiffCmd = diffCmd;
}
public string[] GetPatterns()
{
if (OperatingSystem.IsWindows())
{
return Exec.Split(';');
}
else
{
var patterns = new List<string>();
var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (var c in choices)
{
patterns.Add(Path.GetFileName(c));
}
return patterns.ToArray();
}
}
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SourceGit.Models
{
public class ExternalMerger
{
public int Type { get; set; }
public string Name { get; set; }
public string Exec { get; set; }
public string Cmd { get; set; }
public string DiffCmd { get; set; }
public static readonly List<ExternalMerger> Supported;
static ExternalMerger()
{
if (OperatingSystem.IsWindows())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "Custom", "", "", ""),
new ExternalMerger(1, "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "Visual Studio 2017/2019/2022", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(4, "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""),
new ExternalMerger(5, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(6, "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(7, "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsMacOS())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "Custom", "", "", ""),
new ExternalMerger(1, "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(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 ExternalMerger(4, "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(5, "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsLinux())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "Custom", "", "", ""),
new ExternalMerger(1, "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(4, "Beyond Compare", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "Custom", "", "", ""),
};
}
}
public ExternalMerger(int type, string name, string exec, string cmd, string diffCmd)
{
Type = type;
Name = name;
Exec = exec;
Cmd = cmd;
DiffCmd = diffCmd;
}
public string[] GetPatterns()
{
if (OperatingSystem.IsWindows())
{
return Exec.Split(';');
}
else
{
var patterns = new List<string>();
var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (var c in choices)
{
patterns.Add(Path.GetFileName(c));
}
return patterns.ToArray();
}
}
}
}

View file

@ -5,7 +5,7 @@ using System.IO;
namespace SourceGit.Models namespace SourceGit.Models
{ {
public class ExternalEditor public class ExternalTool
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty; public string Icon { get; set; } = string.Empty;
@ -24,13 +24,13 @@ namespace SourceGit.Models
} }
} }
public class ExternalEditorFinder public class ExternalToolsFinder
{ {
public List<ExternalEditor> Editors public List<ExternalTool> Founded
{ {
get; get;
private set; private set;
} = new List<ExternalEditor>(); } = new List<ExternalTool>();
public void VSCode(Func<string> platform_finder) public void VSCode(Func<string> platform_finder)
{ {
@ -52,7 +52,7 @@ namespace SourceGit.Models
TryAdd("Sublime Text", "sublime_text.png", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder); TryAdd("Sublime Text", "sublime_text.png", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder);
} }
private void TryAdd(string name, string icon, string args, string env, Func<string> finder) public void TryAdd(string name, string icon, string args, string env, Func<string> finder)
{ {
var path = Environment.GetEnvironmentVariable(env); var path = Environment.GetEnvironmentVariable(env);
if (string.IsNullOrEmpty(path) || !File.Exists(path)) if (string.IsNullOrEmpty(path) || !File.Exists(path))
@ -62,7 +62,7 @@ namespace SourceGit.Models
return; return;
} }
Editors.Add(new ExternalEditor Founded.Add(new ExternalTool
{ {
Name = name, Name = name,
Icon = icon, Icon = icon,

10
src/Models/Shell.cs Normal file
View file

@ -0,0 +1,10 @@
namespace SourceGit.Models
{
public enum Shell
{
Default = 0,
PowerShell,
CommandPrompt,
DefaultShellOfWindowsTerminal,
}
}

View file

@ -31,14 +31,14 @@ namespace SourceGit.Native
return string.Empty; return string.Empty;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(() => "/usr/share/code/code"); finder.VSCode(() => "/usr/share/code/code");
finder.VSCodeInsiders(() => "/usr/share/code-insiders/code-insiders"); finder.VSCodeInsiders(() => "/usr/share/code-insiders/code-insiders");
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet");
finder.SublimeText(() => File.Exists("/usr/bin/subl") ? "/usr/bin/subl" : "/usr/local/bin/subl"); finder.SublimeText(() => File.Exists("/usr/bin/subl") ? "/usr/bin/subl" : "/usr/local/bin/subl");
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)

View file

@ -28,14 +28,14 @@ namespace SourceGit.Native
return string.Empty; return string.Empty;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"); finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code");
finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"); finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code");
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet");
finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin"); finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin");
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)

View file

@ -12,7 +12,7 @@ namespace SourceGit.Native
void SetupApp(AppBuilder builder); void SetupApp(AppBuilder builder);
string FindGitExecutable(); string FindGitExecutable();
List<Models.ExternalEditor> FindExternalEditors(); List<Models.ExternalTool> FindExternalTools();
void OpenTerminal(string workdir); void OpenTerminal(string workdir);
void OpenInFileManager(string path, bool select); void OpenInFileManager(string path, bool select);
@ -21,7 +21,7 @@ namespace SourceGit.Native
} }
public static string GitExecutable { get; set; } = string.Empty; public static string GitExecutable { get; set; } = string.Empty;
public static List<Models.ExternalEditor> ExternalEditors { get; set; } = new List<Models.ExternalEditor>(); public static List<Models.ExternalTool> ExternalTools { get; set; } = new List<Models.ExternalTool>();
static OS() static OS()
{ {
@ -42,7 +42,34 @@ namespace SourceGit.Native
throw new Exception("Platform unsupported!!!"); throw new Exception("Platform unsupported!!!");
} }
ExternalEditors = _backend.FindExternalEditors(); ExternalTools = _backend.FindExternalTools();
}
public static Models.Shell GetShell()
{
if (OperatingSystem.IsWindows())
{
return (_backend as Windows).Shell;
}
else
{
return Models.Shell.Default;
}
}
public static bool SetShell(Models.Shell shell)
{
if (OperatingSystem.IsWindows())
{
var windows = (_backend as Windows);
if (windows.Shell != shell)
{
windows.Shell = shell;
return true;
}
}
return false;
} }
public static void SetupApp(AppBuilder builder) public static void SetupApp(AppBuilder builder)

View file

@ -54,6 +54,12 @@ namespace SourceGit.Native
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)] [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags); private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
public Models.Shell Shell
{
get;
set;
} = Models.Shell.Default;
public void SetupApp(AppBuilder builder) public void SetupApp(AppBuilder builder)
{ {
builder.With(new FontManagerOptions() builder.With(new FontManagerOptions()
@ -114,14 +120,14 @@ namespace SourceGit.Native
return null; return null;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(FindVSCode); finder.VSCode(FindVSCode);
finder.VSCodeInsiders(FindVSCodeInsiders); finder.VSCodeInsiders(FindVSCodeInsiders);
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe");
finder.SublimeText(FindSublimeText); finder.SublimeText(FindSublimeText);
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)
@ -133,19 +139,49 @@ namespace SourceGit.Native
public void OpenTerminal(string workdir) public void OpenTerminal(string workdir)
{ {
var binDir = Path.GetDirectoryName(OS.GitExecutable); if (string.IsNullOrEmpty(workdir) || !Path.Exists(workdir))
var bash = Path.Combine(binDir, "bash.exe");
if (!File.Exists(bash))
{ {
App.RaiseException(string.IsNullOrEmpty(workdir) ? "" : workdir, $"Can NOT found bash.exe under '{binDir}'"); workdir = ".";
return;
} }
var startInfo = new ProcessStartInfo(); var startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true; startInfo.WorkingDirectory = workdir;
startInfo.FileName = bash;
if (!string.IsNullOrEmpty(workdir) && Path.Exists(workdir)) switch (Shell)
startInfo.WorkingDirectory = workdir; {
case Models.Shell.Default:
var binDir = Path.GetDirectoryName(OS.GitExecutable);
var bash = Path.Combine(binDir, "bash.exe");
if (!File.Exists(bash))
{
App.RaiseException(workdir, $"Can NOT found bash.exe under '{binDir}'");
return;
}
startInfo.FileName = bash;
break;
case Models.Shell.PowerShell:
startInfo.FileName = ChoosePowerShell();
startInfo.Arguments = startInfo.FileName.EndsWith("pswd.exe") ? $"-WorkingDirectory \"{workdir}\" -Nologo" : "-Nologo";
break;
case Models.Shell.CommandPrompt:
startInfo.FileName = "cmd";
break;
case Models.Shell.DefaultShellOfWindowsTerminal:
var wt = FindWindowsTerminalApp();
if (!File.Exists(wt))
{
App.RaiseException(workdir, $"Can NOT found wt.exe on your system!");
return;
}
startInfo.FileName = FindWindowsTerminalApp();
break;
default:
App.RaiseException(workdir, $"Bad shell configuration!");
return;
}
Process.Start(startInfo); Process.Start(startInfo);
} }
@ -189,6 +225,52 @@ namespace SourceGit.Native
Process.Start(start); Process.Start(start);
} }
// There are two versions of PowerShell : pwsh.exe (preferred) and powershell.exe (system default)
private string ChoosePowerShell()
{
if (!string.IsNullOrEmpty(_powershellPath))
return _powershellPath;
var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey(
Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64);
var pwsh = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\pwsh.exe");
if (pwsh != null)
{
var path = pwsh.GetValue(null) as string;
if (File.Exists(path))
{
_powershellPath = path;
return _powershellPath;
}
}
var finder = new StringBuilder("powershell.exe", 512);
if (PathFindOnPath(finder, null))
{
_powershellPath = finder.ToString();
return _powershellPath;
}
return string.Empty;
}
private string FindWindowsTerminalApp()
{
if (!string.IsNullOrEmpty(_wtPath))
return _wtPath;
var finder = new StringBuilder("wt.exe", 512);
if (PathFindOnPath(finder, null))
{
_wtPath = finder.ToString();
return _wtPath;
}
return string.Empty;
}
#region EXTERNAL_EDITOR_FINDER #region EXTERNAL_EDITOR_FINDER
private string FindVSCode() private string FindVSCode()
{ {
@ -250,6 +332,7 @@ namespace SourceGit.Native
Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64); Microsoft.Win32.RegistryView.Registry64);
// Sublime Text 4
var sublime = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text_is1"); var sublime = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text_is1");
if (sublime != null) if (sublime != null)
{ {
@ -257,6 +340,7 @@ namespace SourceGit.Native
return Path.Combine(Path.GetDirectoryName(icon), "subl.exe"); return Path.Combine(Path.GetDirectoryName(icon), "subl.exe");
} }
// Sublime Text 3
var sublime3 = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text 3_is1"); var sublime3 = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text 3_is1");
if (sublime3 != null) if (sublime3 != null)
{ {
@ -281,5 +365,8 @@ namespace SourceGit.Native
ILFree(pidl); ILFree(pidl);
} }
} }
private string _powershellPath = string.Empty;
private string _wtPath = string.Empty;
} }
} }

View file

@ -266,6 +266,7 @@
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String> <x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String>
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String> <x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String>
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Install Path</x:String> <x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Install Path</x:String>
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">Shell</x:String>
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">User Name</x:String> <x:String x:Key="Text.Preference.Git.User" xml:space="preserve">User Name</x:String>
<x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">Global git user name</x:String> <x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">Global git user name</x:String>
<x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git version</x:String> <x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git version</x:String>

View file

@ -266,6 +266,7 @@
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String> <x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String>
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String> <x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String>
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安装路径</x:String> <x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安装路径</x:String>
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">终端Shell</x:String>
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">用户名</x:String> <x:String x:Key="Text.Preference.Git.User" xml:space="preserve">用户名</x:String>
<x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">默认GIT用户名</x:String> <x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">默认GIT用户名</x:String>
<x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git 版本</x:String> <x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git 版本</x:String>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -22,8 +22,9 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="App.ico" /> <AvaloniaResource Include="App.ico" />
<AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/ExternalToolIcons/*" /> <AvaloniaResource Include="Resources/ExternalToolIcons/*" />
<AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/ShellIcons/*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -205,7 +205,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");

View file

@ -152,7 +152,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");

View file

@ -200,6 +200,18 @@ namespace SourceGit.ViewModels
} }
} }
public Models.Shell GitShell
{
get => Native.OS.GetShell();
set
{
if (Native.OS.SetShell(value))
{
OnPropertyChanged(nameof(GitShell));
}
}
}
public string GitDefaultCloneDir public string GitDefaultCloneDir
{ {
get => _gitDefaultCloneDir; get => _gitDefaultCloneDir;
@ -225,9 +237,9 @@ namespace SourceGit.ViewModels
set set
{ {
var changed = SetProperty(ref _externalMergeToolType, value); var changed = SetProperty(ref _externalMergeToolType, value);
if (changed && !OperatingSystem.IsWindows() && value > 0 && value < Models.ExternalMergeTools.Supported.Count) if (changed && !OperatingSystem.IsWindows() && value > 0 && value < Models.ExternalMerger.Supported.Count)
{ {
var tool = Models.ExternalMergeTools.Supported[value]; var tool = Models.ExternalMerger.Supported[value];
if (File.Exists(tool.Exec)) if (File.Exists(tool.Exec))
ExternalMergeToolPath = tool.Exec; ExternalMergeToolPath = tool.Exec;
else else

View file

@ -287,10 +287,10 @@ namespace SourceGit.ViewModels
Native.OS.OpenTerminal(_fullpath); Native.OS.OpenTerminal(_fullpath);
} }
public ContextMenu CreateContextMenuForExternalEditors() public ContextMenu CreateContextMenuForExternalTools()
{ {
var editors = Native.OS.ExternalEditors; var tools = Native.OS.ExternalTools;
if (editors.Count == 0) if (tools.Count == 0)
{ {
App.RaiseException(_fullpath, "No available external editors found!"); App.RaiseException(_fullpath, "No available external editors found!");
return null; return null;
@ -300,16 +300,16 @@ namespace SourceGit.ViewModels
menu.Placement = PlacementMode.BottomEdgeAlignedLeft; menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality); RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality);
foreach (var editor in editors) foreach (var tool in tools)
{ {
var dupEditor = editor; var dupTool = tool;
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{dupEditor.Icon}", UriKind.RelativeOrAbsolute)); var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{dupTool.Icon}", UriKind.RelativeOrAbsolute));
var item = new MenuItem(); var item = new MenuItem();
item.Header = App.Text("Repository.OpenIn", dupEditor.Name); item.Header = App.Text("Repository.OpenIn", dupTool.Name);
item.Icon = new Image { Width = 16, Height = 16, Source = new Bitmap(icon) }; item.Icon = new Image { Width = 16, Height = 16, Source = new Bitmap(icon) };
item.Click += (o, e) => item.Click += (o, e) =>
{ {
dupEditor.Open(_fullpath); dupTool.Open(_fullpath);
e.Handled = true; e.Handled = true;
}; };

View file

@ -166,7 +166,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");

View file

@ -423,7 +423,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null) if (tool == null)
{ {
App.RaiseException(_repo.FullPath, "Invalid merge tool in preference setting!"); App.RaiseException(_repo.FullPath, "Invalid merge tool in preference setting!");

View file

@ -231,7 +231,7 @@
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/> <TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
</TabItem.Header> </TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*"> <Grid Margin="8" RowDefinitions="32,32,Auto,32,32,32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0" <TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Path}" Text="{DynamicResource Text.Preference.Git.Path}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -254,11 +254,50 @@
<TextBlock Grid.Row="1" Grid.Column="1" <TextBlock Grid.Row="1" Grid.Column="1"
x:Name="txtVersion"/> x:Name="txtVersion"/>
<TextBlock Grid.Row="2" Grid.Column="0" <Border Grid.Row="2" Grid.Column="0"
Height="32"
IsVisible="{OnPlatform False, Windows=True}">
<TextBlock Text="{DynamicResource Text.Preference.Git.Shell}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
</Border>
<ComboBox Grid.Row="2" Grid.Column="1"
MinHeight="28"
Padding="8,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
RenderOptions.BitmapInterpolationMode="HighQuality"
FontSize="{Binding DefaultFontSize, Mode=OneWay}"
SelectedIndex="{Binding GitShell, Mode=TwoWay}"
IsVisible="{OnPlatform False, Windows=True}">
<ComboBox.Items>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/git-bash.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Git Bash" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/pwsh.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="PowerShell" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/cmd.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Command Prompt" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/wt.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Default Shell in Windows Terminal" Margin="6,0,0,0"/>
</Grid>
</ComboBox.Items>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.DefaultCloneDir}" Text="{DynamicResource Text.Preference.Git.DefaultCloneDir}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="2" Grid.Column="1" <TextBox Grid.Row="3" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding GitDefaultCloneDir, Mode=TwoWay}"> Text="{Binding GitDefaultCloneDir, Mode=TwoWay}">
@ -269,31 +308,31 @@
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.User}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="3" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding #me.DefaultUser, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.User.Placeholder}"/>
<TextBlock Grid.Row="4" Grid.Column="0" <TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Email}" Text="{DynamicResource Text.Preference.Git.User}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="4" Grid.Column="1" <TextBox Grid.Row="4" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding #me.DefaultUser, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.User.Placeholder}"/>
<TextBlock Grid.Row="5" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Email}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="5" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding #me.DefaultEmail, Mode=TwoWay}" Text="{Binding #me.DefaultEmail, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.Email.Placeholder}"/> Watermark="{DynamicResource Text.Preference.Git.Email.Placeholder}"/>
<TextBlock Grid.Row="5" Grid.Column="0" <TextBlock Grid.Row="6" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.CRLF}" Text="{DynamicResource Text.Preference.Git.CRLF}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<ComboBox Grid.Row="5" Grid.Column="1" <ComboBox Grid.Row="6" Grid.Column="1"
MinHeight="28" MinHeight="28"
Padding="8,0" Padding="8,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -309,7 +348,7 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<CheckBox Grid.Row="6" Grid.Column="1" <CheckBox Grid.Row="7" Grid.Column="1"
Content="{DynamicResource Text.Preference.Git.AutoFetch}" Content="{DynamicResource Text.Preference.Git.AutoFetch}"
IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/> IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/>
</Grid> </Grid>
@ -370,8 +409,8 @@
MinHeight="28" MinHeight="28"
Padding="8,0" Padding="8,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Source={x:Static m:ExternalMergeTools.Supported}}" ItemsSource="{Binding Source={x:Static m:ExternalMerger.Supported}}"
DisplayMemberBinding="{Binding Name, x:DataType=m:ExternalMergeTools}" DisplayMemberBinding="{Binding Name, x:DataType=m:ExternalMerger}"
SelectedIndex="{Binding ExternalMergeToolType, Mode=TwoWay}"/> SelectedIndex="{Binding ExternalMergeToolType, Mode=TwoWay}"/>
<TextBlock Grid.Row="1" Grid.Column="0" <TextBlock Grid.Row="1" Grid.Column="0"

View file

@ -222,13 +222,13 @@ namespace SourceGit.Views
private async void SelectExternalMergeTool(object sender, RoutedEventArgs e) private async void SelectExternalMergeTool(object sender, RoutedEventArgs e)
{ {
var type = ViewModels.Preference.Instance.ExternalMergeToolType; var type = ViewModels.Preference.Instance.ExternalMergeToolType;
if (type < 0 || type >= Models.ExternalMergeTools.Supported.Count) if (type < 0 || type >= Models.ExternalMerger.Supported.Count)
{ {
ViewModels.Preference.Instance.ExternalMergeToolType = 0; ViewModels.Preference.Instance.ExternalMergeToolType = 0;
type = 0; type = 0;
} }
var tool = Models.ExternalMergeTools.Supported[type]; var tool = Models.ExternalMerger.Supported[type];
var options = new FilePickerOpenOptions() var options = new FilePickerOpenOptions()
{ {
FileTypeFilter = [new FilePickerFileType(tool.Name) { Patterns = tool.GetPatterns() }], FileTypeFilter = [new FilePickerFileType(tool.Name) { Patterns = tool.GetPatterns() }],

View file

@ -22,7 +22,7 @@
<Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Click="OnOpenWithExternalEditor" ToolTip.Tip="{DynamicResource Text.Repository.OpenWithExternalTools}"> <Button Classes="icon_button" Width="32" Click="OnOpenWithExternalTools" ToolTip.Tip="{DynamicResource Text.Repository.OpenWithExternalTools}">
<Path Width="13" Height="13" Data="{StaticResource Icons.OpenWith}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.OpenWith}"/>
</Button> </Button>

View file

@ -61,11 +61,11 @@ namespace SourceGit.Views
InitializeComponent(); InitializeComponent();
} }
private void OnOpenWithExternalEditor(object sender, RoutedEventArgs e) private void OnOpenWithExternalTools(object sender, RoutedEventArgs e)
{ {
if (sender is Button button && DataContext is ViewModels.Repository repo) if (sender is Button button && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForExternalEditors(); var menu = repo.CreateContextMenuForExternalTools();
if (menu != null) if (menu != null)
{ {
menu.Open(button); menu.Open(button);