Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions src/Native/Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;

using System.Text.RegularExpressions;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform;
Expand All @@ -14,8 +15,10 @@
namespace SourceGit.Native
{
[SupportedOSPlatform("windows")]
internal class Windows : OS.IBackend
internal partial class Windows : OS.IBackend
{
private const string VSWHERE_PATH = @"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe";

internal struct RECT
{
public int left;
Expand Down Expand Up @@ -189,7 +192,13 @@ public string FindTerminal(Models.ShellOrTerminal shell)
finder.Fleet(() => Path.Combine(localAppDataDir, @"Programs\Fleet\Fleet.exe"));
finder.FindJetBrainsFromToolbox(() => Path.Combine(localAppDataDir, @"JetBrains\Toolbox"));
finder.SublimeText(FindSublimeText);
finder.TryAdd("Visual Studio", "vs", FindVisualStudio, GenerateCommandlineArgsForVisualStudio);

foreach (var instance in FindVisualStudioInstances().OrderBy(q => q.IsPreview))
{
var name = instance.IsPreview ? "Visual Studio - Preview" : "Visual Studio";
finder.TryAdd(name, "vs", () => instance.Path, GenerateCommandlineArgsForVisualStudio);
}

return finder.Tools;
}

Expand Down Expand Up @@ -371,25 +380,60 @@ private string FindSublimeText()
return string.Empty;
}

private string FindVisualStudio()
private static (string Path, bool IsPreview)[] FindVisualStudioInstances()
{
var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey(
Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64);
static string ExecuteAndGetOutput(string programPath, string arguments)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = programPath,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
}
};

process.Start();
var stdOut = process.StandardOutput.ReadToEnd();
process.WaitForExit();

// Get default class for VisualStudio.Launcher.sln - the handler for *.sln files
if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is { } launcher)
if (process.ExitCode > 0)
{
return null;
}
else
{
return stdOut;
}
}

static Dictionary<string, string> InterpretVsWhereChunk(string chunk)
{
// Get actual path to the executable
if (launcher.GetValue(string.Empty) is string CLSID &&
localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is { } devenv &&
devenv.GetValue(string.Empty) is string localServer32)
return localServer32!.Trim('\"');
var lines = chunk.Split(Environment.NewLine);

return lines.Select(l => VsWhereLine().Match(l))
.Where(regexResult => regexResult.Success)
.ToDictionary(regexResult => regexResult.Groups[1].Value, regexResult => regexResult.Groups[2].Value);
}

return string.Empty;
var vswherePath = Environment.ExpandEnvironmentVariables(VSWHERE_PATH);
if (!File.Exists(vswherePath))
{
return [];
}

var outputChunks = ExecuteAndGetOutput(vswherePath, "-nologo -nocolor -prerelease").Split(Environment.NewLine + Environment.NewLine);

return outputChunks.Select(InterpretVsWhereChunk)
.Select(q => (q["productPath"], q["isPrerelease"] == "1"))
.ToArray();
}

[GeneratedRegex(@"^(\w+):\s(.*)$")]
private static partial Regex VsWhereLine();

private string FindCursor()
{
var cursorPath = Path.Combine(
Expand Down