diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs index ad4488a..bc6a95c 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs @@ -17,19 +17,16 @@ namespace WelsonJS.Launcher.ResourceTools private readonly ResourceServer Server; private readonly HttpClient _httpClient; private const string Prefix = "completion/"; - private List Executables = new List(); + private List DiscoverdExecutables = new List(); public Completion(ResourceServer server, HttpClient httpClient) { Server = server; _httpClient = httpClient; - new Task(() => - { - Executables.AddRange(GetInstalledSoftwareExecutables()); - Executables.AddRange(GetExecutablesFromPath()); - Executables.AddRange(GetExecutablesFromNetFx()); - }).Start(); + Task.Run(() => DiscoverFromInstalledSoftware()); + Task.Run(() => DiscoverFromPathVariable()); + Task.Run(() => DiscoverFromProgramDirectories()); } public bool CanHandle(string path) @@ -45,7 +42,7 @@ namespace WelsonJS.Launcher.ResourceTools try { - CompletionItem[] completionItems = Executables + CompletionItem[] completionItems = DiscoverdExecutables .Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1) .Take(100) // Limit the number of results .Select(exec => new CompletionItem @@ -70,137 +67,109 @@ namespace WelsonJS.Launcher.ResourceTools } catch (Exception ex) { - Server.ServeResource(context, $"Failed to process completion request. {ex.Message}", "application/xml", 500); + Server.ServeResource(context, $"Failed to try completion. {ex.Message}", "application/xml", 500); } } - private List GetInstalledSoftwareExecutables() + private void DiscoverFromInstalledSoftware() { - List executables = new List(); - string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; + const string registryPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey)) + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath)) { - 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; + if (key == null) return; - List executablePaths = FindExecutables(installLocation, uninstallString); - executables.AddRange(executablePaths); + 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)) + { + DiscoverdExecutables.Add(match.Value); + } } } } } - - return executables; } - private List FindExecutables(string installLocation, string uninstallString) - { - List executables = new List(); + private void DiscoverFromPathVariable() { + var paths = (Environment.GetEnvironmentVariable("PATH") ?? string.Empty) + .Split(';') + .Select(p => p.Trim()) + .Where(p => !string.IsNullOrEmpty(p)); - if (!string.IsNullOrEmpty(installLocation) && Directory.Exists(installLocation)) + foreach (string path in paths) { - try - { - List executableFiles = Directory.GetFiles(installLocation, "*.exe", SearchOption.AllDirectories) - .OrderByDescending(f => new FileInfo(f).Length) - .ToList(); - executables.AddRange(executableFiles); - } - catch (Exception ex) - { - Debug.WriteLine($"Error enumerating executables in '{installLocation}': {ex}"); - } + SearchAllExecutables(path, SearchOption.TopDirectoryOnly); } - - if (!string.IsNullOrEmpty(uninstallString)) - { - if (TryParseExecutablePath(uninstallString, out string executablePath)) - { - executables.Add(executablePath); - } - } - - return executables; } - private static bool TryParseExecutablePath(string s, out string path) + private void DiscoverFromProgramDirectories() { - Match match = Regex.Match(s, @"(?<=""|^)([a-zA-Z]:\\[^""]+\.exe)", RegexOptions.IgnoreCase); - - if (match.Success) - { - path = match.Value; - return true; - } - - path = null; - return false; - } - - private List GetExecutablesFromPath() - { - List executables = new List(); - string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';'); - - if (paths != null) - { - foreach (string path in paths) - { - if (Directory.Exists(path)) - { - try - { - executables.AddRange(Directory.GetFiles(path, "*.exe", SearchOption.TopDirectoryOnly)); - } - catch (Exception ex) - { - Debug.WriteLine($"Error enumerating executables in '{path}': {ex}"); - } - } - } - } - - return executables; - } - - private List GetExecutablesFromNetFx() - { - List executables = new List(); - string windir = Environment.GetEnvironmentVariable("WINDIR"); + string programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - if (!string.IsNullOrEmpty(windir)) + var paths = new[] { - string[] paths = new string[] - { - Path.Combine(windir, "Microsoft.NET", "Framework"), - Path.Combine(windir, "Microsoft.NET", "Framework64") - }; + // Default directory + Path.Combine(Program.GetAppDataPath(), "bin"), - foreach (string path in paths) - { - if (Directory.Exists(path)) - { - try - { - executables.AddRange(Directory.GetFiles(path, "*.exe", SearchOption.AllDirectories)); - } - catch (Exception ex) - { - Debug.WriteLine($"Error enumerating executables in '{path}': {ex}"); - } - } - } + // 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) + { + if (!Directory.Exists(path)) + { + Trace.TraceWarning("Directory does not exist: {0}", path); + return; } - return executables; + try + { + var executableFiles = Directory.GetFiles(path, "*.exe", searchOption) + .OrderByDescending(f => new FileInfo(f).Length) + .ToList(); + + DiscoverdExecutables.AddRange(executableFiles); + } + catch (Exception ex) + { + Trace.TraceWarning("Error enumerating executables in '{0}': {1}", path, ex.Message); + } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html index 4691acd..237869b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html @@ -170,6 +170,31 @@ function Editor({ editorRef }) { const containerRef = React.useRef(null); + const getSuggestions = (word, range) => axios.get(`/completion/${encodeURIComponent(word)}`) + .then(response => { + const parser = new XMLParser(); + const result = parser.parse(response.data); + + const suggestions = ((item) => Array.isArray(item) ? item : [item])(result.suggestions.item).map((item) => { + return { + label: item.label, + kind: monaco.languages.CompletionItemKind.Text, + documentation: item.documentation || "", + insertText: '"' + item.insertText + '"', + range: range + }; + }); + + return { + suggestions: suggestions + }; + }) + .catch(function () { + return { + suggestions: [] + }; + }); + React.useEffect(() => { if (!containerRef.current) return; @@ -180,6 +205,20 @@ language: 'javascript' }); + monaco.languages.registerCompletionItemProvider('javascript', { + provideCompletionItems: function (model, position) { + const word = model.getWordUntilPosition(position); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn + }; + + return getSuggestions(word.word, range); + } + }); + editorRef.current = instance; }); }, []); @@ -234,14 +273,13 @@ } function App() { - const serverPrefix = "http://localhost:3000/"; const editorRef = React.useRef(null); const promptEditorRef = React.useRef(null); const settingsRef = React.useRef({}); const fileNameRef = React.useRef('sayhello.js'); const promptMessagesRef = React.useRef([]); - const fetchSettings = () => axios.get(`${serverPrefix}settings`) + const fetchSettings = () => axios.get(`/settings`) .then(response => { const parser = new XMLParser(); const result = parser.parse(response.data); @@ -259,38 +297,6 @@ } }; - const getSuggestions = (word) => axios.get(`${serverPrefix}completion/${encodeURIComponent(word)}`) - .then(response => { - const parser = new XMLParser(); - const result = parser.parse(response.data); - - if (!result.suggestions || !result.suggestions.item) { - return { - suggestions: [] - }; - } - - const items = Array.isArray(result.suggestions.item) ? result.suggestions.item : [result.suggestions.item]; - const suggestions = items.map(function (item) { - return { - label: item.label, - kind: monaco.languages.CompletionItemKind.Text, - documentation: item.documentation || "", - insertText: '"' + item.insertText + '"', - range: range - }; - }); - - return { - suggestions: suggestions - }; - }) - .catch(function () { - return { - suggestions: [] - }; - }); - const pushPromptMessage = (role, content) => { promptMessagesRef.current.push({ role: role, @@ -442,7 +448,7 @@ }; const getTargetByUrl = async (urlPart) => { - const response = await fetch(`${serverPrefix}devtools/json`); + const response = await fetch(`/devtools/json`); const targets = await response.json(); const target = targets.find(target => target.url.includes(urlPart)); @@ -542,7 +548,7 @@ return; } - axios.get(`${serverPrefix}whois/${hostname}`).then(response => { + axios.get(`/whois/${hostname}`).then(response => { const responseText = DOMPurify.sanitize(response.data, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] }); appendTextToEditor(`/*\nHostname:${hostname}\n\n${responseText}\n*/`); pushPromptMessage("system", responseText); @@ -558,7 +564,7 @@ return; } - axios.get(`${serverPrefix}dns-query/${hostname}`).then(response => { + axios.get(`/dns-query/${hostname}`).then(response => { const responseText = response.data; appendTextToEditor(`/*\nHostname:${hostname}\n\n${responseText}\n*/`); pushPromptMessage("system", responseText); @@ -583,7 +589,7 @@ const apiPrefix = settingsRef.current.CitiApiPrefix; const ip = encodeURIComponent(hostname.trim()); - axios.get(`${serverPrefix}citi-query/${hostname}`).then(response => { + axios.get(`/citi-query/${hostname}`).then(response => { const parser = new XMLParser(); const result = parser.parse(response.data); const data = JSON.parse(result.json);