|
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.Diagnostics; |
4 | 4 | using System.IO; |
| 5 | +using System.Linq; |
5 | 6 | using System.Runtime.InteropServices; |
6 | 7 | using System.Runtime.Versioning; |
7 | 8 | using System.Text; |
8 | | - |
| 9 | +using System.Text.RegularExpressions; |
9 | 10 | using Avalonia; |
10 | 11 | using Avalonia.Controls; |
11 | 12 | using Avalonia.Platform; |
|
14 | 15 | namespace SourceGit.Native |
15 | 16 | { |
16 | 17 | [SupportedOSPlatform("windows")] |
17 | | - internal class Windows : OS.IBackend |
| 18 | + internal partial class Windows : OS.IBackend |
18 | 19 | { |
| 20 | + private const string VSWHERE_PATH = @"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"; |
| 21 | + |
19 | 22 | internal struct RECT |
20 | 23 | { |
21 | 24 | public int left; |
@@ -189,7 +192,13 @@ public string FindTerminal(Models.ShellOrTerminal shell) |
189 | 192 | finder.Fleet(() => Path.Combine(localAppDataDir, @"Programs\Fleet\Fleet.exe")); |
190 | 193 | finder.FindJetBrainsFromToolbox(() => Path.Combine(localAppDataDir, @"JetBrains\Toolbox")); |
191 | 194 | finder.SublimeText(FindSublimeText); |
192 | | - finder.TryAdd("Visual Studio", "vs", FindVisualStudio, GenerateCommandlineArgsForVisualStudio); |
| 195 | + |
| 196 | + foreach (var instance in FindVisualStudioInstances().OrderBy(q => q.IsPreview)) |
| 197 | + { |
| 198 | + var name = instance.IsPreview ? "Visual Studio - Preview" : "Visual Studio"; |
| 199 | + finder.TryAdd(name, "vs", () => instance.Path, GenerateCommandlineArgsForVisualStudio); |
| 200 | + } |
| 201 | + |
193 | 202 | return finder.Tools; |
194 | 203 | } |
195 | 204 |
|
@@ -371,25 +380,60 @@ private string FindSublimeText() |
371 | 380 | return string.Empty; |
372 | 381 | } |
373 | 382 |
|
374 | | - private string FindVisualStudio() |
| 383 | + private static (string Path, bool IsPreview)[] FindVisualStudioInstances() |
375 | 384 | { |
376 | | - var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( |
377 | | - Microsoft.Win32.RegistryHive.LocalMachine, |
378 | | - Microsoft.Win32.RegistryView.Registry64); |
| 385 | + static string ExecuteAndGetOutput(string programPath, string arguments) |
| 386 | + { |
| 387 | + using var process = new Process |
| 388 | + { |
| 389 | + StartInfo = new ProcessStartInfo |
| 390 | + { |
| 391 | + FileName = programPath, |
| 392 | + Arguments = arguments, |
| 393 | + UseShellExecute = false, |
| 394 | + RedirectStandardOutput = true, |
| 395 | + } |
| 396 | + }; |
| 397 | + |
| 398 | + process.Start(); |
| 399 | + var stdOut = process.StandardOutput.ReadToEnd(); |
| 400 | + process.WaitForExit(); |
379 | 401 |
|
380 | | - // Get default class for VisualStudio.Launcher.sln - the handler for *.sln files |
381 | | - if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is { } launcher) |
| 402 | + if (process.ExitCode > 0) |
| 403 | + { |
| 404 | + return null; |
| 405 | + } |
| 406 | + else |
| 407 | + { |
| 408 | + return stdOut; |
| 409 | + } |
| 410 | + } |
| 411 | + |
| 412 | + static Dictionary<string, string> InterpretVsWhereChunk(string chunk) |
382 | 413 | { |
383 | | - // Get actual path to the executable |
384 | | - if (launcher.GetValue(string.Empty) is string CLSID && |
385 | | - localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is { } devenv && |
386 | | - devenv.GetValue(string.Empty) is string localServer32) |
387 | | - return localServer32!.Trim('\"'); |
| 414 | + var lines = chunk.Split(Environment.NewLine); |
| 415 | + |
| 416 | + return lines.Select(l => VsWhereLine().Match(l)) |
| 417 | + .Where(regexResult => regexResult.Success) |
| 418 | + .ToDictionary(regexResult => regexResult.Groups[1].Value, regexResult => regexResult.Groups[2].Value); |
388 | 419 | } |
389 | 420 |
|
390 | | - return string.Empty; |
| 421 | + var vswherePath = Environment.ExpandEnvironmentVariables(VSWHERE_PATH); |
| 422 | + if (!File.Exists(vswherePath)) |
| 423 | + { |
| 424 | + return []; |
| 425 | + } |
| 426 | + |
| 427 | + var outputChunks = ExecuteAndGetOutput(vswherePath, "-nologo -nocolor -prerelease").Split(Environment.NewLine + Environment.NewLine); |
| 428 | + |
| 429 | + return outputChunks.Select(InterpretVsWhereChunk) |
| 430 | + .Select(q => (q["productPath"], q["isPrerelease"] == "1")) |
| 431 | + .ToArray(); |
391 | 432 | } |
392 | 433 |
|
| 434 | + [GeneratedRegex(@"^(\w+):\s(.*)$")] |
| 435 | + private static partial Regex VsWhereLine(); |
| 436 | + |
393 | 437 | private string FindCursor() |
394 | 438 | { |
395 | 439 | var cursorPath = Path.Combine( |
|
0 commit comments