mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-26 04:44:59 +00:00
feature: Find external terminals.
This commit is contained in:
parent
718788e07e
commit
421127bec9
8 changed files with 264 additions and 35 deletions
98
src/Models/ExternalTerminal.cs
Normal file
98
src/Models/ExternalTerminal.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class ExternalTerminal
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class ExternalTerminalFinder
|
||||
{
|
||||
public List<ExternalTerminal> Terminals
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<ExternalTerminal>();
|
||||
|
||||
public void WindowsGitBash(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("Git Bash", "git-bash.png", "bash", "\"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
public void Gnome(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("gnome-terminal", "gnome.png", "/usr/bin/gnome-terminal", "--working-directory=\"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
public void Konsole(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("gnome-terminal", "gnome.png", "/usr/bin/konsole", "--workdir \"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
public void osaScript(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("AppleScript", "osascript.png", "/usr/bin/osascript",
|
||||
"""
|
||||
on run argv
|
||||
tell application "Terminal"
|
||||
do script "cd '{0}'"
|
||||
activate
|
||||
end tell
|
||||
end run
|
||||
""",
|
||||
platform_finder);
|
||||
}
|
||||
|
||||
public void PowerShell(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("PowerShell", "pwsh.png", "pwsh", "-WorkingDirectory \"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
public void WindowsTerminal(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("Windows Terminal", "wt.png", "wt", "-d \"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
public void Xfce4(Func<string> platform_finder)
|
||||
{
|
||||
TryAdd("gnome-terminal", "xfce4.png", "/usr/bin/xfce4-terminal", "--working-directory=\"{0}\"", platform_finder);
|
||||
}
|
||||
|
||||
private void TryAdd(string name, string icon, string cmd, string args, Func<string> finder)
|
||||
{
|
||||
var path = Environment.GetEnvironmentVariable(cmd);
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
{
|
||||
path = finder();
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
return;
|
||||
}
|
||||
|
||||
Terminals.Add(new ExternalTerminal
|
||||
{
|
||||
Name = name,
|
||||
Icon = icon,
|
||||
OpenCmdArgs = args,
|
||||
Executable = path,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,15 @@ namespace SourceGit.Native
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public List<Models.ExternalTerminal> FindExternalTerminals()
|
||||
{
|
||||
var finder = new Models.ExternalTerminalFinder();
|
||||
finder.Gnome(() => "/usr/bin/gnome-terminal");
|
||||
finder.Konsole(() => "/usr/bin/konsole");
|
||||
finder.Xfce4(() => "/usr/bin/xfce4-terminal");
|
||||
return finder.Terminals;
|
||||
}
|
||||
|
||||
public List<Models.ExternalEditor> FindExternalEditors()
|
||||
{
|
||||
var finder = new Models.ExternalEditorFinder();
|
||||
|
|
|
@ -28,6 +28,13 @@ namespace SourceGit.Native
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public List<Models.ExternalTerminal> FindExternalTerminals()
|
||||
{
|
||||
var finder = new Models.ExternalTerminalFinder();
|
||||
finder.osaScript(() => "/usr/bin/osascript");
|
||||
return finder.Terminals;
|
||||
}
|
||||
|
||||
public List<Models.ExternalEditor> FindExternalEditors()
|
||||
{
|
||||
var finder = new Models.ExternalEditorFinder();
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace SourceGit.Native
|
|||
void SetupApp(AppBuilder builder);
|
||||
|
||||
string FindGitExecutable();
|
||||
|
||||
List<Models.ExternalTerminal> FindExternalTerminals();
|
||||
List<Models.ExternalEditor> FindExternalEditors();
|
||||
|
||||
void OpenTerminal(string workdir);
|
||||
|
@ -21,6 +23,7 @@ namespace SourceGit.Native
|
|||
}
|
||||
|
||||
public static string GitExecutable { get; set; } = string.Empty;
|
||||
public static List<Models.ExternalTerminal> ExternalTerminals { get; set; } = new List<Models.ExternalTerminal>();
|
||||
public static List<Models.ExternalEditor> ExternalEditors { get; set; } = new List<Models.ExternalEditor>();
|
||||
|
||||
static OS()
|
||||
|
@ -42,6 +45,7 @@ namespace SourceGit.Native
|
|||
throw new Exception("Platform unsupported!!!");
|
||||
}
|
||||
|
||||
ExternalTerminals = _backend.FindExternalTerminals();
|
||||
ExternalEditors = _backend.FindExternalEditors();
|
||||
}
|
||||
|
||||
|
|
|
@ -114,6 +114,15 @@ namespace SourceGit.Native
|
|||
return null;
|
||||
}
|
||||
|
||||
public List<Models.ExternalTerminal> FindExternalTerminals()
|
||||
{
|
||||
var finder = new Models.ExternalTerminalFinder();
|
||||
finder.WindowsGitBash(() => FindExternalTerminal("bash"));
|
||||
finder.PowerShell(() => FindExternalTerminal("pwsh"));
|
||||
finder.WindowsTerminal(() => FindExternalTerminal("wt"));
|
||||
return finder.Terminals;
|
||||
}
|
||||
|
||||
public List<Models.ExternalEditor> FindExternalEditors()
|
||||
{
|
||||
var finder = new Models.ExternalEditorFinder();
|
||||
|
@ -130,6 +139,26 @@ namespace SourceGit.Native
|
|||
info.CreateNoWindow = true;
|
||||
Process.Start(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the external terminal full path via the command name (e.g. "bash").
|
||||
/// </summary>
|
||||
/// <param name="cmd"></param>
|
||||
/// <returns></returns>
|
||||
public string FindExternalTerminal(string cmd)
|
||||
{
|
||||
using var process = Process.Start(new ProcessStartInfo(
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "where.exe"),
|
||||
cmd)
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
})!;
|
||||
var fullPath = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
public void OpenTerminal(string workdir)
|
||||
{
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
|
@ -12,6 +14,9 @@ using Avalonia.Platform;
|
|||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using SourceGit.Commands;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
|
@ -89,6 +94,16 @@ namespace SourceGit.ViewModels
|
|||
get => _selectedView;
|
||||
set => SetProperty(ref _selectedView, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<ExternalMenuItem> ExternalTerminals
|
||||
{
|
||||
get;
|
||||
} = new AvaloniaList<ExternalMenuItem>();
|
||||
|
||||
public AvaloniaList<ExternalMenuItem> ExternalEditors
|
||||
{
|
||||
get;
|
||||
} = new AvaloniaList<ExternalMenuItem>();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Models.Remote> Remotes
|
||||
|
@ -244,6 +259,9 @@ namespace SourceGit.ViewModels
|
|||
Task.Run(RefreshWorkingCopyChanges);
|
||||
Task.Run(RefreshStashes);
|
||||
Task.Run(RefreshGitFlow);
|
||||
|
||||
RefreshExternalTerminals();
|
||||
RefreshExternalEditors();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
|
@ -287,36 +305,77 @@ namespace SourceGit.ViewModels
|
|||
Native.OS.OpenTerminal(_fullpath);
|
||||
}
|
||||
|
||||
public ContextMenu CreateContextMenuForExternalEditors()
|
||||
public void OpenWithExternalTool()
|
||||
{
|
||||
Native.OS.OpenWithDefaultEditor(_fullpath);
|
||||
}
|
||||
|
||||
private void RefreshExternalTerminals()
|
||||
{
|
||||
ExternalTerminals.Clear();
|
||||
var terminals = CreateContextMenuForExternalTerminals();
|
||||
foreach (var terminal in terminals)
|
||||
{
|
||||
ExternalTerminals.Add(terminal);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshExternalEditors()
|
||||
{
|
||||
ExternalEditors.Clear();
|
||||
var editors = CreateContextMenuForExternalEditors();
|
||||
foreach (var editor in editors)
|
||||
{
|
||||
ExternalEditors.Add(editor);
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ExternalMenuItem> CreateContextMenuForExternalTerminals()
|
||||
{
|
||||
var terminals = Native.OS.ExternalTerminals;
|
||||
if (terminals.Count == 0)
|
||||
{
|
||||
App.RaiseException(_fullpath, "No available external terminals found!");
|
||||
return [new ExternalMenuItem("No terminal found")
|
||||
{
|
||||
IsEnabled = false,
|
||||
}];
|
||||
}
|
||||
|
||||
var items = new List<ExternalMenuItem>(terminals.Count);
|
||||
foreach (var terminal in terminals)
|
||||
{
|
||||
var dupTerminal = terminal;
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalTerminalIcons/{dupTerminal.Icon}", UriKind.RelativeOrAbsolute));
|
||||
var item = new ExternalMenuItem(App.Text("Repository.OpenIn", dupTerminal.Name), new Bitmap(icon), () => dupTerminal.Open(_fullpath));
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return [..items];
|
||||
}
|
||||
|
||||
public ImmutableArray<ExternalMenuItem> CreateContextMenuForExternalEditors()
|
||||
{
|
||||
var editors = Native.OS.ExternalEditors;
|
||||
if (editors.Count == 0)
|
||||
{
|
||||
App.RaiseException(_fullpath, "No available external editors found!");
|
||||
return null;
|
||||
return [new ExternalMenuItem("No editor found")
|
||||
{
|
||||
IsEnabled = false,
|
||||
}];
|
||||
}
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
|
||||
RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality);
|
||||
|
||||
var items = new List<ExternalMenuItem>(editors.Count);
|
||||
foreach (var editor in editors)
|
||||
{
|
||||
var dupEditor = editor;
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{dupEditor.Icon}", UriKind.RelativeOrAbsolute));
|
||||
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);
|
||||
var item = new ExternalMenuItem(App.Text("Repository.OpenIn", dupEditor.Name), new Bitmap(icon), () => dupEditor.Open(_fullpath));
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return menu;
|
||||
return [..items];
|
||||
}
|
||||
|
||||
public void Fetch()
|
||||
|
@ -1356,4 +1415,44 @@ namespace SourceGit.ViewModels
|
|||
private InProgressContext _inProgressContext = null;
|
||||
private bool _hasUnsolvedConflicts = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A menu item for external tools.
|
||||
/// </summary>
|
||||
public readonly record struct ExternalMenuItem
|
||||
{
|
||||
public ExternalMenuItem(string header)
|
||||
{
|
||||
Header = header;
|
||||
Icon = null;
|
||||
Command = null;
|
||||
}
|
||||
|
||||
public ExternalMenuItem(string header, Bitmap icon, Action click)
|
||||
{
|
||||
Header = header;
|
||||
Icon = icon;
|
||||
Command = new RelayCommand(click);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The external tool name.
|
||||
/// </summary>
|
||||
public string Header { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The resource key of the icon.
|
||||
/// </summary>
|
||||
public Bitmap? Icon { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The command when the user click the menu item.
|
||||
/// </summary>
|
||||
public ICommand Command { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if the menu item is enabled; otherwise, <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,8 @@
|
|||
|
||||
<UserControl.Resources>
|
||||
<MenuFlyout x:Key="TerminalShellsMenuFlyout" Placement="Bottom">
|
||||
<MenuItem Header="git bash" />
|
||||
<MenuItem Header="PowerShell" />
|
||||
</MenuFlyout>
|
||||
<MenuFlyout x:Key="ExternalToolsMenuFlyout" Placement="Bottom">
|
||||
<MenuItem Header="Visual Studio Code" />
|
||||
<MenuItem Header="Sublime Text" />
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
|
||||
|
@ -36,7 +32,7 @@
|
|||
<Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/>
|
||||
</SplitButton>
|
||||
|
||||
<SplitButton Classes="icon_button" Click="OnOpenWithExternalEditor"
|
||||
<SplitButton Classes="icon_button" Command="{Binding OpenWithExternalTool}"
|
||||
Flyout="{StaticResource ExternalToolsMenuFlyout}"
|
||||
ToolTip.Tip="{DynamicResource Text.Repository.OpenWithExternalTools}">
|
||||
<Path Width="13" Height="13" Data="{StaticResource Icons.OpenWith}"/>
|
||||
|
|
|
@ -61,19 +61,6 @@ namespace SourceGit.Views
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnOpenWithExternalEditor(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var menu = repo.CreateContextMenuForExternalEditors();
|
||||
if (menu != null)
|
||||
{
|
||||
menu.Open(button);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLocalBranchTreeLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TreeView tree)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue