code_review: PR #2043

- Opening repo as folder in VS should always be available
- Use single `_optionsGenerator` instead of `_execArgsGenerator` and `_subOptionsFinder`
- Make sure path is always quoted

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2026-01-13 11:06:21 +08:00
parent 3dbab1c7bf
commit 62bdadc40d
8 changed files with 77 additions and 61 deletions

View File

@@ -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<string, string> execArgsGenerator = null, Func<string, List<string>> subOptionsFinder = null)
public ExternalTool(string name, string icon, string execFile, Func<string, List<LaunchOption>> 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<LaunchOption> 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<string> FindSubOptions(string path)
{
if (_subOptionsFinder == null)
return null;
return _subOptionsFinder.Invoke(path);
}
private Func<string, string> _execArgsGenerator = null;
private Func<string, List<string>> _subOptionsFinder = null;
private Func<string, List<LaunchOption>> _optionsGenerator = null;
}
public class VisualStudioInstance
@@ -140,20 +147,20 @@ namespace SourceGit.Models
_customization ??= new ExternalToolCustomization();
}
public void TryAdd(string name, string icon, Func<string> finder, Func<string, string> execArgsGenerator = null, Func<string, List<string>> subOptionsFinder = null)
public void TryAdd(string name, string icon, Func<string> finder, Func<string, List<ExternalTool.LaunchOption>> 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));
}
}

View File

@@ -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<Models.ExternalTool.LaunchOption> GenerateVSProjectLaunchOptions(string path)
{
var solutions = FindVisualStudioSolutions(path);
return solutions.Count > 0 ? solutions[0].Quoted() : path.Quoted();
}
public List<string> FindVisualStudioSolutions(string path)
{
var solutions = new List<string>();
if (!Directory.Exists(path))
return solutions;
void Search(DirectoryInfo dir, int depth)
if (Directory.Exists(path))
{
if (depth < 0)
return;
void Search(List<Models.ExternalTool.LaunchOption> 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<Models.ExternalTool.LaunchOption>();
Search(options, rootDir, rootDir.FullName, 4);
if (options.Count > 0)
return options;
}
Search(new DirectoryInfo(path), 4);
return solutions;
return null;
}
}
}

View File

@@ -729,6 +729,7 @@
<x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">Create Branch</x:String>
<x:String x:Key="Text.Repository.Notifications.Clear" xml:space="preserve">CLEAR NOTIFICATIONS</x:String>
<x:String x:Key="Text.Repository.OnlyHighlightCurrentBranchInGraph" xml:space="preserve">Only highlight current branch</x:String>
<x:String x:Key="Text.Repository.OpenAsFolder" xml:space="preserve">Open as Folder</x:String>
<x:String x:Key="Text.Repository.OpenIn" xml:space="preserve">Open in {0}</x:String>
<x:String x:Key="Text.Repository.OpenWithExternalTools" xml:space="preserve">Open in External Tools</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">REMOTES</x:String>

View File

@@ -733,6 +733,7 @@
<x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">新建分支</x:String>
<x:String x:Key="Text.Repository.Notifications.Clear" xml:space="preserve">清空通知列表</x:String>
<x:String x:Key="Text.Repository.OnlyHighlightCurrentBranchInGraph" xml:space="preserve">仅高亮显示当前分支</x:String>
<x:String x:Key="Text.Repository.OpenAsFolder" xml:space="preserve">本仓库(文件夹)</x:String>
<x:String x:Key="Text.Repository.OpenIn" xml:space="preserve">在 {0} 中打开</x:String>
<x:String x:Key="Text.Repository.OpenWithExternalTools" xml:space="preserve">使用外部工具打开</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">远程列表</x:String>

View File

@@ -733,6 +733,7 @@
<x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">新增分支</x:String>
<x:String x:Key="Text.Repository.Notifications.Clear" xml:space="preserve">清除所有通知</x:String>
<x:String x:Key="Text.Repository.OnlyHighlightCurrentBranchInGraph" xml:space="preserve">路線圖中僅對目前分支上色</x:String>
<x:String x:Key="Text.Repository.OpenAsFolder" xml:space="preserve">此存放庫(資料夾)</x:String>
<x:String x:Key="Text.Repository.OpenIn" xml:space="preserve">在 {0} 中開啟</x:String>
<x:String x:Key="Text.Repository.OpenWithExternalTools" xml:space="preserve">使用外部工具開啟</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">遠端列表</x:String>

View File

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

View File

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

View File

@@ -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;
};