feature: add powershell support for Windows

This commit is contained in:
leo 2024-04-08 17:39:52 +08:00
parent 8f70778ec2
commit 4ac705f8ca
13 changed files with 88 additions and 46 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

@ -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,

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,8 @@ 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 bool UsePowershellOnWindows { get; set; } = false;
public static List<Models.ExternalTool> ExternalTools { get; set; } = new List<Models.ExternalTool>();
static OS() static OS()
{ {
@ -42,7 +43,7 @@ namespace SourceGit.Native
throw new Exception("Platform unsupported!!!"); throw new Exception("Platform unsupported!!!");
} }
ExternalEditors = _backend.FindExternalEditors(); ExternalTools = _backend.FindExternalTools();
} }
public static void SetupApp(AppBuilder builder) public static void SetupApp(AppBuilder builder)

View file

@ -54,6 +54,16 @@ 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 Windows()
{
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");
_powershellPath = pwsh != null ? pwsh.GetValue(null) as string : "powershell";
}
public void SetupApp(AppBuilder builder) public void SetupApp(AppBuilder builder)
{ {
builder.With(new FontManagerOptions() builder.With(new FontManagerOptions()
@ -114,14 +124,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 +143,27 @@ namespace SourceGit.Native
public void OpenTerminal(string workdir) public void OpenTerminal(string workdir)
{ {
var binDir = Path.GetDirectoryName(OS.GitExecutable); var startInfo = new ProcessStartInfo() { UseShellExecute = true };
var bash = Path.Combine(binDir, "bash.exe");
if (!File.Exists(bash))
{
App.RaiseException(string.IsNullOrEmpty(workdir) ? "" : workdir, $"Can NOT found bash.exe under '{binDir}'");
return;
}
var startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.FileName = bash;
if (!string.IsNullOrEmpty(workdir) && Path.Exists(workdir)) if (!string.IsNullOrEmpty(workdir) && Path.Exists(workdir))
startInfo.WorkingDirectory = workdir; startInfo.WorkingDirectory = workdir;
if (OS.UsePowershellOnWindows)
{
startInfo.FileName = _powershellPath;
}
else
{
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 '{binDir}'");
return;
}
startInfo.FileName = bash;
}
Process.Start(startInfo); Process.Start(startInfo);
} }
@ -281,5 +299,7 @@ namespace SourceGit.Native
ILFree(pidl); ILFree(pidl);
} }
} }
private string _powershellPath = string.Empty;
} }
} }

View file

@ -268,6 +268,7 @@
<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.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.UsePowershellOnWindows" xml:space="preserve">Use Powershell instead of Git bash</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>
<x:String x:Key="Text.Preference.GPG" xml:space="preserve">GPG SIGNING</x:String> <x:String x:Key="Text.Preference.GPG" xml:space="preserve">GPG SIGNING</x:String>
<x:String x:Key="Text.Preference.GPG.Enabled" xml:space="preserve">Commit GPG signing</x:String> <x:String x:Key="Text.Preference.GPG.Enabled" xml:space="preserve">Commit GPG signing</x:String>

View file

@ -268,6 +268,7 @@
<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.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.UsePowershellOnWindows" xml:space="preserve">使用PowerShell替代Git Bash</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>
<x:String x:Key="Text.Preference.GPG" xml:space="preserve">GPG签名</x:String> <x:String x:Key="Text.Preference.GPG" xml:space="preserve">GPG签名</x:String>
<x:String x:Key="Text.Preference.GPG.Enabled" xml:space="preserve">启用提交签名</x:String> <x:String x:Key="Text.Preference.GPG.Enabled" xml:space="preserve">启用提交签名</x:String>

View file

@ -219,6 +219,19 @@ namespace SourceGit.ViewModels
} }
} }
public bool UsePowershellOnWindows
{
get => Native.OS.UsePowershellOnWindows;
set
{
if (Native.OS.UsePowershellOnWindows != value)
{
Native.OS.UsePowershellOnWindows = value;
OnPropertyChanged(nameof(UsePowershellOnWindows));
}
}
}
public int ExternalMergeToolType public int ExternalMergeToolType
{ {
get => _externalMergeToolType; get => _externalMergeToolType;

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

@ -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,32,32,32,32,Auto,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"
@ -310,6 +310,12 @@
</ComboBox> </ComboBox>
<CheckBox Grid.Row="6" Grid.Column="1" <CheckBox Grid.Row="6" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preference.Git.UsePowershellOnWindows}"
IsChecked="{Binding UsePowershellOnWindows, Mode=TwoWay}"
IsVisible="{OnPlatform False, Windows=True}"/>
<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>

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);