From 1d0098703e771d4368821c80bc6179561efc201b Mon Sep 17 00:00:00 2001 From: "Dmitrij D. Czarkoff" Date: Mon, 4 Nov 2024 01:22:16 +0000 Subject: [PATCH] feature: add support for Visual Studio as external tool (#648) * feature: support Visual Studio external tool on Windows * feature: when opening in Visual Studio, try to locate solution file --- README.md | 1 + src/Models/ExternalTool.cs | 40 ++++++++++++++++++++++++++++++++------ src/Native/Windows.cs | 20 +++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 52383670..44ed5917 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ This app supports open repository in external tools listed in the table below. | JetBrains Fleet | YES | YES | YES | FLEET | | Sublime Text | YES | YES | YES | SUBLIME_TEXT | | Zed | NO | YES | YES | ZED | +| Visual Studio | YES | YES | YES | VISUALSTUDIO | > [!NOTE] > This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app. diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index b26a9a90..960ebde6 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -16,12 +17,14 @@ namespace SourceGit.Models public string Executable { get; private set; } public string OpenCmdArgs { get; private set; } public Bitmap IconImage { get; private set; } = null; + public Func ArgTransform { get; private set; } - public ExternalTool(string name, string icon, string executable, string openCmdArgs) + public ExternalTool(string name, string icon, string executable, string openCmdArgs, Func argsTransform) { Name = name; Executable = executable; OpenCmdArgs = openCmdArgs; + ArgTransform = argsTransform ?? ((s) => s); try { @@ -37,11 +40,16 @@ namespace SourceGit.Models public void Open(string repo) { + string arguments = string.Format(OpenCmdArgs, repo); + + if (ArgTransform != null) + arguments = ArgTransform.Invoke(arguments); + Process.Start(new ProcessStartInfo() { WorkingDirectory = repo, FileName = Executable, - Arguments = string.Format(OpenCmdArgs, repo), + Arguments = arguments, UseShellExecute = false, }); } @@ -110,17 +118,17 @@ namespace SourceGit.Models _customPaths = new ExternalToolPaths(); } - public void TryAdd(string name, string icon, string args, string key, Func finder) + public void TryAdd(string name, string icon, string args, string key, Func finder, Func argsTransform = null) { if (_customPaths.Tools.TryGetValue(key, out var customPath) && File.Exists(customPath)) { - Founded.Add(new ExternalTool(name, icon, customPath, args)); + Founded.Add(new ExternalTool(name, icon, customPath, args, argsTransform)); } else { var path = finder(); if (!string.IsNullOrEmpty(path) && File.Exists(path)) - Founded.Add(new ExternalTool(name, icon, path, args)); + Founded.Add(new ExternalTool(name, icon, path, args, argsTransform)); } } @@ -154,6 +162,25 @@ namespace SourceGit.Models TryAdd("Zed", "zed", "\"{0}\"", "ZED", platformFinder); } + public void VisualStudio(Func platformFinder) + { + TryAdd("Visual Studio", "vs", "\"{0}\"", "VISUALSTUDIO", platformFinder, VisualStudioTryFindSolution); + } + + private static string VisualStudioTryFindSolution(string path) + { + try + { + if (Directory.GetFiles(path.Trim('\"'), "*.sln", SearchOption.AllDirectories).FirstOrDefault() is string solutionPath) + return Path.GetFullPath(solutionPath); + } + catch + { + // do nothing + } + return path; + } + public void FindJetBrainsFromToolbox(Func platformFinder) { var exclude = new List { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" }; @@ -171,7 +198,8 @@ namespace SourceGit.Models $"{tool.DisplayName} {tool.DisplayVersion}", supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB", Path.Combine(tool.InstallLocation, tool.LaunchCommand), - "\"{0}\"")); + "\"{0}\"", + null)); } } } diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 6ca0bbb0..0563644c 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -134,6 +134,7 @@ namespace SourceGit.Native finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox"); finder.SublimeText(FindSublimeText); + finder.VisualStudio(FindVisualStudio); return finder.Founded; } @@ -313,6 +314,25 @@ namespace SourceGit.Native return string.Empty; } + + private string FindVisualStudio() + { + var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.LocalMachine, + Microsoft.Win32.RegistryView.Registry64); + + // Get default class for VisualStudio.Launcher.sln - the handler for *.sln files + if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is Microsoft.Win32.RegistryKey launcher) + { + // Get actual path to the executable + if (launcher.GetValue(string.Empty) is string CLSID && localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is Microsoft.Win32.RegistryKey devenv && devenv.GetValue(string.Empty) is string localServer32) + { + return localServer32!.Trim('\"'); + } + } + + return string.Empty; + } #endregion private void OpenFolderAndSelectFile(string folderPath)