mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-10-27 02:51:17 +00:00
Updated ICompatibleLogger to accept params object[] for flexible logging. Refactored TraceLogger to support the new interface and improved formatting. Added JsNative.cs to encapsulate ChakraCore P/Invoke interop, and updated JsCore to use JsNative for all native calls. Modified all resource tools to accept and use ICompatibleLogger for consistent logging. Updated project file to include new and updated sources.
214 lines
7.6 KiB
C#
214 lines
7.6 KiB
C#
// Completion.cs
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
|
// https://github.com/gnh1201/welsonjs
|
|
//
|
|
using Microsoft.Win32;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Linq;
|
|
using System.Net.Http;
|
|
using System.Collections.Concurrent;
|
|
|
|
namespace WelsonJS.Launcher.ResourceTools
|
|
{
|
|
public class Completion : IResourceTool
|
|
{
|
|
private readonly ResourceServer Server;
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ICompatibleLogger _logger;
|
|
private const string Prefix = "completion/";
|
|
private readonly ConcurrentBag<string> DiscoveredExecutables = new ConcurrentBag<string>();
|
|
|
|
public Completion(ResourceServer server, HttpClient httpClient, ICompatibleLogger logger)
|
|
{
|
|
Server = server;
|
|
|
|
_httpClient = httpClient;
|
|
_logger = logger;
|
|
|
|
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromInstalledSoftware));
|
|
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromPathVariable));
|
|
Task.Run(async () => await SafeDiscoverAsync(DiscoverFromProgramDirectories));
|
|
}
|
|
|
|
public bool CanHandle(string path)
|
|
{
|
|
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public async Task HandleAsync(HttpListenerContext context, string path)
|
|
{
|
|
await Task.Delay(0);
|
|
|
|
string word = path.Substring(Prefix.Length);
|
|
|
|
try
|
|
{
|
|
List<CompletionItem> completionItems = DiscoveredExecutables
|
|
.Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1)
|
|
.Take(100) // Limit the number of results
|
|
.Select(exec => new CompletionItem
|
|
{
|
|
Label = Path.GetFileName(exec),
|
|
Kind = "Text",
|
|
Documentation = $"An executable file: {exec}",
|
|
InsertText = exec
|
|
})
|
|
.ToList();
|
|
|
|
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)
|
|
))
|
|
);
|
|
|
|
Server.ServeResource(context, response.ToString(), "application/xml");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Server.ServeResource(context, $"<error>Failed to try completion. {ex.Message}</error>", "application/xml", 500);
|
|
}
|
|
}
|
|
|
|
private void DiscoverFromInstalledSoftware()
|
|
{
|
|
const string registryPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
|
|
|
|
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath))
|
|
{
|
|
if (key == null) return;
|
|
|
|
foreach (string subKeyName in key.GetSubKeyNames())
|
|
{
|
|
RegistryKey subKey = key.OpenSubKey(subKeyName);
|
|
if (subKey == null) continue;
|
|
|
|
using (subKey)
|
|
{
|
|
string installLocation = subKey.GetValue("InstallLocation") as string;
|
|
if (!string.IsNullOrEmpty(installLocation))
|
|
{
|
|
SearchAllExecutables(installLocation);
|
|
}
|
|
|
|
string uninstallString = subKey.GetValue("UninstallString") as string;
|
|
if (!string.IsNullOrEmpty(uninstallString))
|
|
{
|
|
var match = Regex.Match(uninstallString, @"(?<=""|^)([a-zA-Z]:\\[^""]+\.exe)", RegexOptions.IgnoreCase);
|
|
if (match.Success && File.Exists(match.Value))
|
|
{
|
|
AddDiscoveredExecutables(new List<string> { match.Value });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DiscoverFromPathVariable() {
|
|
var paths = (Environment.GetEnvironmentVariable("PATH") ?? string.Empty)
|
|
.Split(';')
|
|
.Select(p => p.Trim())
|
|
.Where(p => !string.IsNullOrEmpty(p));
|
|
|
|
foreach (string path in paths)
|
|
{
|
|
SearchAllExecutables(path, SearchOption.TopDirectoryOnly);
|
|
}
|
|
}
|
|
|
|
private void DiscoverFromProgramDirectories()
|
|
{
|
|
string windir = Environment.GetEnvironmentVariable("WINDIR");
|
|
string programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
|
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
|
|
|
var paths = new[]
|
|
{
|
|
// Default directory
|
|
Path.Combine(Program.GetAppDataPath(), "bin"),
|
|
|
|
// Standard program installation directories
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
|
|
|
// .NET Framework directories
|
|
Path.Combine(windir, "Microsoft.NET", "Framework"),
|
|
Path.Combine(windir, "Microsoft.NET", "Framework64"),
|
|
|
|
// Chocolatey package directory
|
|
Path.Combine(programData, "chocolatey", "lib"),
|
|
|
|
// Scoop apps directory
|
|
Path.Combine(userProfile, "scoop", "apps")
|
|
};
|
|
|
|
foreach (string path in paths)
|
|
{
|
|
SearchAllExecutables(path);
|
|
}
|
|
}
|
|
|
|
private void SearchAllExecutables(string path, SearchOption searchOption = SearchOption.AllDirectories, int maxFiles = 1000)
|
|
{
|
|
if (!Directory.Exists(path))
|
|
{
|
|
_logger.Info("Directory does not exist: {0}", path);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var executableFiles = Directory.GetFiles(path, "*.exe", searchOption)
|
|
.Take(maxFiles)
|
|
.OrderByDescending(f => new FileInfo(f).Length)
|
|
.ToList();
|
|
|
|
AddDiscoveredExecutables(executableFiles);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Info("Error enumerating executables in '{0}': {1}", path, ex.Message);
|
|
}
|
|
}
|
|
|
|
private void AddDiscoveredExecutables(List<string> executableFiles)
|
|
{
|
|
foreach (var executableFile in executableFiles)
|
|
{
|
|
DiscoveredExecutables.Add(executableFile);
|
|
}
|
|
}
|
|
|
|
private async Task SafeDiscoverAsync(Action discoveryMethod)
|
|
{
|
|
try
|
|
{
|
|
await Task.Run(discoveryMethod);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error($"Discovery failed: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public class CompletionItem
|
|
{
|
|
public string Label { get; set; }
|
|
public string Kind { get; set; }
|
|
public string Documentation { get; set; }
|
|
public string InsertText { get; set; }
|
|
}
|
|
}
|