diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index 61e99404..b0bdfb8b 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -12,16 +12,28 @@ namespace SourceGit.Models { public class ExternalTool { + public class LaunchOption + { + public string Title { get; set; } + public string Args { get; set; } + + public LaunchOption(string title, string args) + { + Title = title; + Args = args; + } + } + public string Name { get; } public string ExecFile { get; } public Bitmap IconImage { get; } - public ExternalTool(string name, string icon, string execFile, Func execArgsGenerator = null, Func> subOptionsFinder = null) + public ExternalTool(string name, string icon, string execFile, Func> optionsGenerator = null) { Name = name; ExecFile = execFile; - _execArgsGenerator = execArgsGenerator ?? (path => path.Quoted()); - _subOptionsFinder = subOptionsFinder; + + _optionsGenerator = optionsGenerator; try { @@ -35,30 +47,25 @@ namespace SourceGit.Models } } - public void Open(string path) + public List MakeLaunchOptions(string repo) { - // The executable file may be removed after the tool list is loaded (once time on startup). - if (!File.Exists(ExecFile)) - return; + return _optionsGenerator?.Invoke(repo); + } - Process.Start(new ProcessStartInfo() + public void Launch(string args) + { + if (File.Exists(ExecFile)) { - FileName = ExecFile, - Arguments = _execArgsGenerator.Invoke(path), - UseShellExecute = false, - }); + Process.Start(new ProcessStartInfo() + { + FileName = ExecFile, + Arguments = args, + UseShellExecute = false, + }); + } } - public List FindSubOptions(string path) - { - if (_subOptionsFinder == null) - return null; - - return _subOptionsFinder.Invoke(path); - } - - private Func _execArgsGenerator = null; - private Func> _subOptionsFinder = null; + private Func> _optionsGenerator = null; } public class VisualStudioInstance @@ -140,20 +147,20 @@ namespace SourceGit.Models _customization ??= new ExternalToolCustomization(); } - public void TryAdd(string name, string icon, Func finder, Func execArgsGenerator = null, Func> subOptionsFinder = null) + public void TryAdd(string name, string icon, Func finder, Func> optionsGenerator = null) { if (_customization.Excludes.Contains(name)) return; if (_customization.Tools.TryGetValue(name, out var customPath) && File.Exists(customPath)) { - Tools.Add(new ExternalTool(name, icon, customPath, execArgsGenerator, subOptionsFinder)); + Tools.Add(new ExternalTool(name, icon, customPath, optionsGenerator)); } else { var path = finder(); if (!string.IsNullOrEmpty(path) && File.Exists(path)) - Tools.Add(new ExternalTool(name, icon, path, execArgsGenerator, subOptionsFinder)); + Tools.Add(new ExternalTool(name, icon, path, optionsGenerator)); } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index b5722537..d8333559 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -401,7 +401,7 @@ namespace SourceGit.Native { var exec = instance.ProductPath; var icon = instance.IsPrerelease ? "vs-preview" : "vs"; - finder.TryAdd(instance.DisplayName, icon, () => exec, GenerateCommandlineArgsForVisualStudio, FindVisualStudioSolutions); + finder.TryAdd(instance.DisplayName, icon, () => exec, GenerateVSProjectLaunchOptions); } } } @@ -444,35 +444,34 @@ namespace SourceGit.Native } } - private string GenerateCommandlineArgsForVisualStudio(string path) + private List GenerateVSProjectLaunchOptions(string path) { - var solutions = FindVisualStudioSolutions(path); - return solutions.Count > 0 ? solutions[0].Quoted() : path.Quoted(); - } - - public List FindVisualStudioSolutions(string path) - { - var solutions = new List(); - if (!Directory.Exists(path)) - return solutions; - - void Search(DirectoryInfo dir, int depth) + if (Directory.Exists(path)) { - if (depth < 0) - return; + void Search(List opts, DirectoryInfo dir, string root, int depth) + { + if (depth < 0) + return; - foreach (var file in dir.GetFiles("*.sln")) - solutions.Add(file.FullName); + foreach (var file in dir.GetFiles()) + { + if (file.Name.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) || + file.Name.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase)) + opts.Add(new(Path.GetRelativePath(root, file.FullName), file.FullName.Quoted())); + } - foreach (var file in dir.GetFiles("*.slnx")) - solutions.Add(file.FullName); + foreach (var subDir in dir.GetDirectories()) + Search(opts, subDir, root, depth - 1); + } - foreach (var subDir in dir.GetDirectories()) - Search(subDir, depth - 1); + var rootDir = new DirectoryInfo(path); + var options = new List(); + Search(options, rootDir, rootDir.FullName, 4); + if (options.Count > 0) + return options; } - Search(new DirectoryInfo(path), 4); - return solutions; + return null; } } } diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 8562e335..4b5dbd26 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -729,6 +729,7 @@ Create Branch CLEAR NOTIFICATIONS Only highlight current branch + Open as Folder Open in {0} Open in External Tools REMOTES diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 0bce1c72..117ffc9f 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -733,6 +733,7 @@ 新建分支 清空通知列表 仅高亮显示当前分支 + 本仓库(文件夹) 在 {0} 中打开 使用外部工具打开 远程列表 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index d5183981..35f04c5c 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -733,6 +733,7 @@ 新增分支 清除所有通知 路線圖中僅對目前分支上色 + 此存放庫(資料夾) 在 {0} 中開啟 使用外部工具開啟 遠端列表 diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index f0862c0f..6440a3e7 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -343,7 +343,7 @@ namespace SourceGit.ViewModels if (tool == null) Native.OS.OpenWithDefaultEditor(tmpFile); else - tool.Open(tmpFile); + tool.Launch(tmpFile.Quoted()); } public async Task SaveRevisionFileAsync(Models.Object file, string saveTo) diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index fc1fcce2..88433771 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using Avalonia.Controls; using Avalonia.Input; @@ -63,34 +62,42 @@ namespace SourceGit.Views item.Header = App.Text("Repository.OpenIn", dupTool.Name); item.Icon = new Image { Width = 16, Height = 16, Source = dupTool.IconImage }; - var subOptions = dupTool.FindSubOptions(fullpath); - if (subOptions != null && subOptions.Count > 1) + var options = dupTool.MakeLaunchOptions(fullpath); + if (options is { Count: > 0 }) { - foreach (var subOption in subOptions) + var openAsFolder = new MenuItem(); + openAsFolder.Header = App.Text("Repository.OpenAsFolder"); + openAsFolder.Click += (_, e) => + { + dupTool.Launch(fullpath.Quoted()); + e.Handled = true; + }; + item.Items.Add(openAsFolder); + item.Items.Add(new MenuItem() { Header = "-" }); + + foreach (var opt in options) { var subItem = new MenuItem(); - subItem.Header = Path.GetFileName(subOption); + subItem.Header = opt.Title; subItem.Click += (_, e) => { - dupTool.Open(subOption); + dupTool.Launch(opt.Args); e.Handled = true; }; item.Items.Add(subItem); } - - menu.Items.Add(item); } else { item.Click += (_, e) => { - dupTool.Open(fullpath); + dupTool.Launch(fullpath.Quoted()); e.Handled = true; }; - - menu.Items.Add(item); } + + menu.Items.Add(item); } } diff --git a/src/Views/WorkingCopy.axaml.cs b/src/Views/WorkingCopy.axaml.cs index 5840d247..5ca64918 100644 --- a/src/Views/WorkingCopy.axaml.cs +++ b/src/Views/WorkingCopy.axaml.cs @@ -1290,7 +1290,7 @@ namespace SourceGit.Views item.Icon = new Image { Width = 16, Height = 16, Source = tool.IconImage }; item.Click += (_, e) => { - tool.Open(fullpath); + tool.Launch(fullpath.Quoted()); e.Handled = true; };