using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Avalonia.Media.Imaging; using Avalonia.Platform; 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 bool SupportOpenFolder { get; } public ExternalTool(string name, string icon, string execFile, Func> optionsGenerator, bool supportOpenFolder) { Name = name; ExecFile = execFile; SupportOpenFolder = supportOpenFolder; _optionsGenerator = optionsGenerator; try { var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/ExternalToolIcons/{icon}.png", UriKind.RelativeOrAbsolute)); IconImage = new Bitmap(asset); } catch { // ignore } } public List MakeLaunchOptions(string repo) { return _optionsGenerator?.Invoke(repo); } public void Launch(string args) { if (File.Exists(ExecFile)) { Process.Start(new ProcessStartInfo() { FileName = ExecFile, Arguments = args, UseShellExecute = false, CreateNoWindow = true, }); } } private Func> _optionsGenerator = null; } public class VisualStudioInstance { [JsonPropertyName("displayName")] public string DisplayName { get; set; } = string.Empty; [JsonPropertyName("productPath")] public string ProductPath { get; set; } = string.Empty; [JsonPropertyName("isPrerelease")] public bool IsPrerelease { get; set; } = false; } public class JetBrainsState { [JsonPropertyName("tools")] public List Tools { get; set; } = new List(); } public class JetBrainsTool { [JsonPropertyName("productCode")] public string ProductCode { get; set; } [JsonPropertyName("displayName")] public string DisplayName { get; set; } [JsonPropertyName("displayVersion")] public string DisplayVersion { get; set; } [JsonPropertyName("installLocation")] public string InstallLocation { get; set; } [JsonPropertyName("launchCommand")] public string LaunchCommand { get; set; } } public class ExternalToolCustomization { [JsonPropertyName("tools")] public Dictionary Tools { get; set; } = new Dictionary(); [JsonPropertyName("excludes")] public List Excludes { get; set; } = new List(); } public class ExternalToolsFinder { public List Tools { get; private set; } = new List(); public ExternalToolsFinder() { var customPathsConfig = Path.Combine(Native.OS.BasicDirectories.ConfigDir, "external_editors.json"); try { if (File.Exists(customPathsConfig)) { using var stream = File.OpenRead(customPathsConfig); _customization = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.ExternalToolCustomization); } } catch { // Ignore } _customization ??= new ExternalToolCustomization(); } public void TryAdd(string name, string icon, Func finder, Func> optionsGenerator = null, bool supportOpenFolder = true) { if (_customization.Excludes.Contains(name)) return; if (_customization.Tools.TryGetValue(name, out var customPath) && File.Exists(customPath)) { Tools.Add(new ExternalTool(name, icon, customPath, optionsGenerator, supportOpenFolder)); } else { var path = finder(); if (!string.IsNullOrEmpty(path) && File.Exists(path)) Tools.Add(new ExternalTool(name, icon, path, optionsGenerator, supportOpenFolder)); } } public void VSCode(Func platformFinder) { TryAdd("Visual Studio Code", "vscode", platformFinder, GenerateVSCodeLaunchOptions); } public void VSCodeInsiders(Func platformFinder) { TryAdd("Visual Studio Code - Insiders", "vscode_insiders", platformFinder, GenerateVSCodeLaunchOptions); } public void VSCodium(Func platformFinder) { TryAdd("VSCodium", "codium", platformFinder, GenerateVSCodeLaunchOptions); } public void SublimeText(Func platformFinder) { TryAdd("Sublime Text", "sublime_text", platformFinder); } public void Zed(Func platformFinder) { TryAdd("Zed", "zed", platformFinder); } public void Cursor(Func platformFinder) { TryAdd("Cursor", "cursor", platformFinder); } public void FindJetBrainsFromToolbox(Func platformFinder) { var supported = new List { "AI", "CL", "DB", "DL", "DS", "GO", "IC", "IU", "JB", "PC", "PS", "PY", "QA", "QD", "RD", "RM", "RR", "WRS", "WS" }; var state = Path.Combine(platformFinder(), "state.json"); if (File.Exists(state)) { try { using var stream = File.OpenRead(state); var stateData = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.JetBrainsState); foreach (var tool in stateData.Tools) { if (!supported.Contains(tool.ProductCode)) continue; Tools.Add(new ExternalTool( $"{tool.DisplayName} {tool.DisplayVersion}", $"JetBrains/{tool.ProductCode}", Path.Combine(tool.InstallLocation, tool.LaunchCommand), null, true)); } } catch { // Ignore exceptions. } } } private List GenerateVSCodeLaunchOptions(string path) { var root = new DirectoryInfo(path); if (!root.Exists) return null; var options = new List(); var prefixLen = root.FullName.Length; root.WalkFiles(f => { if (f.EndsWith(".code-workspace", StringComparison.OrdinalIgnoreCase)) { var display = f.Substring(prefixLen).TrimStart(Path.DirectorySeparatorChar); options.Add(new(display, f.Quoted())); } }, 2); return options; } private ExternalToolCustomization _customization = null; } }