From b1cabe9fbb1eb2495554f18e4c6b1d25129131e5 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 15 Mar 2025 00:07:28 +0900 Subject: [PATCH] Add some code for #183 --- .../WelsonJS.Launcher/ExecutablesCollector.cs | 115 ++++++++++++++++++ .../WelsonJS.Launcher/ResourceServer.cs | 60 +++++++++ .../WelsonJS.Launcher.csproj | 2 + 3 files changed, 177 insertions(+) create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/ExecutablesCollector.cs diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ExecutablesCollector.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ExecutablesCollector.cs new file mode 100644 index 0000000..4970fa6 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ExecutablesCollector.cs @@ -0,0 +1,115 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace WelsonJS.Launcher +{ + public class ExecutablesCollector + { + private List executables = new List(); + + public ExecutablesCollector() + { + executables.AddRange(GetInstalledSoftwareExecutables()); + executables.AddRange(GetExecutablesFromPath()); + } + + public List GetExecutables() + { + return executables; + } + + private List GetInstalledSoftwareExecutables() + { + List executables = new List(); + string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; + + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey)) + { + if (key != null) + { + foreach (string subKeyName in key.GetSubKeyNames()) + { + using (RegistryKey subKey = key.OpenSubKey(subKeyName)) + { + string installLocation = subKey?.GetValue("InstallLocation") as string; + string uninstallString = subKey?.GetValue("UninstallString") as string; + + List executablePaths = FindExecutables(installLocation, uninstallString); + executables.AddRange(executablePaths); + } + } + } + } + + return executables; + } + + private List FindExecutables(string installLocation, string uninstallString) + { + List executables = new List(); + + if (!string.IsNullOrEmpty(installLocation) && Directory.Exists(installLocation)) + { + try + { + List executableFiles = Directory.GetFiles(installLocation, "*.exe", SearchOption.AllDirectories) + .OrderByDescending(f => new FileInfo(f).Length) + .ToList(); + executables.AddRange(executableFiles); + } + catch (Exception) { } + } + + if (!string.IsNullOrEmpty(uninstallString)) + { + if (TryParseExecutablePath(uninstallString, out string executablePath)) + { + executables.Add(executablePath); + } + } + + return executables; + } + + private static bool TryParseExecutablePath(string s, out string path) + { + Match match = Regex.Match(s, @"(?<=""|^)([a-zA-Z]:\\[^""\s]+\.exe)"); + + if (match.Success) + { + path = match.Value; + return true; + } + + path = null; + return false; + } + + private List GetExecutablesFromPath() + { + List executables = new List(); + string pathEnv = Environment.GetEnvironmentVariable("PATH"); + + if (!string.IsNullOrEmpty(pathEnv)) + { + foreach (string path in pathEnv.Split(';')) + { + if (Directory.Exists(path)) + { + try + { + executables.AddRange(Directory.GetFiles(path, "*.exe", SearchOption.TopDirectoryOnly)); + } + catch (Exception) { } + } + } + } + + return executables; + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index 09ddf0e..55dc8f3 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using System.Xml.Linq; namespace WelsonJS.Launcher { @@ -18,6 +21,7 @@ namespace WelsonJS.Launcher private bool _isRunning; private string _prefix; private string _resourceName; + private ExecutablesCollector _executablesCollector; public ResourceServer(string prefix, string resourceName) { @@ -25,6 +29,7 @@ namespace WelsonJS.Launcher _listener = new HttpListener(); _listener.Prefixes.Add(prefix); _resourceName = resourceName; + _executablesCollector = new ExecutablesCollector(); } public string GetPrefix() @@ -81,15 +86,62 @@ namespace WelsonJS.Launcher { string path = context.Request.Url.AbsolutePath.TrimStart('/'); + // Serve the favicon.ico file if ("favicon.ico".Equals(path, StringComparison.OrdinalIgnoreCase)) { ServeResource(context, GetResource("favicon"), "image/x-icon"); return; } + // Serve the code completion (word suggestion) + if (path.StartsWith("completion/", StringComparison.OrdinalIgnoreCase)) + { + ServeCompletion(context, path.Substring("completion/".Length)); + return; + } + + // Serve a resource ServeResource(context, GetResource(_resourceName), "text/html"); } + private void ServeCompletion(HttpListenerContext context, string word) + { + int statusCode = 200; + + List executables = _executablesCollector.GetExecutables(); + + CompletionItem[] completionItems = executables + .Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1) + .Select(exec => new CompletionItem + { + label = Path.GetFileName(exec), + kind = "Text", + documentation = "An executable file", + insertText = exec + }) + .ToArray(); + + XElement response = new XElement("suggestions", + completionItems.Select(item => new XElement("item", + new XElement("label", item.label), + new XElement("kind", item.kind), + new XElement("documentation", item.documentation), + new XElement("insertText", item.insertText) + )) + ); + + byte[] data = Encoding.UTF8.GetBytes( + "\r\n" + + response.ToString() + ); + + context.Response.StatusCode = statusCode; + context.Response.ContentType = "application/xml"; + context.Response.ContentLength64 = data.Length; + context.Response.OutputStream.Write(data, 0, data.Length); + context.Response.OutputStream.Close(); + } + private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html") { int statusCode = 200; @@ -159,4 +211,12 @@ namespace WelsonJS.Launcher } } } + + public class CompletionItem + { + public string label { get; set; } + public string kind { get; set; } + public string documentation { get; set; } + public string insertText { get; set; } + } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 756c646..85c3ec2 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -68,6 +68,7 @@ + @@ -76,6 +77,7 @@ EnvForm.cs + Form