From 160d0559e8d6529e82377fb40ab2801da64ccd09 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 11 Jun 2026 20:40:18 +0200 Subject: [PATCH] Refactor CLI command system to support async execution and central command history --- Processes/CliProcess.cs | 38 +++++++++++------------ Processes/TerminalProcess.cs | 32 ++++++++++--------- Processing/ProcessManager.cs | 50 +++++++++++++++++++++++------- UI/CLI/CommandManager.cs | 16 ++++++++-- UI/CLI/Commands/ClearCommand.cs | 3 +- UI/CLI/Commands/HaltCommand.cs | 3 +- UI/CLI/Commands/HelpCommand.cs | 4 ++- UI/CLI/Commands/ProcessCommands.cs | 14 ++++++--- UI/CLI/Commands/RebootCommand.cs | 3 +- UI/CLI/Commands/StartGuiCommand.cs | 5 +-- UI/CLI/Commands/StopGuiCommand.cs | 2 +- UI/CLI/Commands/ViewProcessLogs.cs | 3 +- UI/CLI/ICommand.cs | 2 +- UI/GUI/Windows/WindowManager.cs | 2 +- 14 files changed, 114 insertions(+), 63 deletions(-) diff --git a/Processes/CliProcess.cs b/Processes/CliProcess.cs index 6f3ad73..8e17677 100644 --- a/Processes/CliProcess.cs +++ b/Processes/CliProcess.cs @@ -7,20 +7,23 @@ internal class CliProcess() : Process("Cli") { private string currentInput = string.Empty; - private readonly List commandHistory = []; private int historyIndex = -1; + private bool commandRunning = false; + internal override void Start(string[] args) { Console.Clear(); Console.WriteLine("Welcome RemSox!"); Console.WriteLine("Type 'help' to see available commands."); Console.Write("> "); + + historyIndex = CommandManager.GetCommandHistory().Count; } - internal override void Tick() + internal override async void Tick() { - while (Console.KeyAvailable) + while (Console.KeyAvailable && !commandRunning) { ConsoleKeyInfo key = Console.ReadKey(intercept: true); @@ -29,14 +32,9 @@ internal class CliProcess() : Process("Cli") case ConsoleKey.Enter: Console.WriteLine(); - if (!string.IsNullOrWhiteSpace(currentInput) && (commandHistory.Count == 0 || commandHistory[^1] != currentInput)) - { - commandHistory.Add(currentInput); - } + await HandleCommand(currentInput); - historyIndex = commandHistory.Count; - - HandleCommand(currentInput); + historyIndex = CommandManager.GetCommandHistory().Count; currentInput = string.Empty; Console.Write("> "); @@ -50,10 +48,10 @@ internal class CliProcess() : Process("Cli") } break; case ConsoleKey.UpArrow: - if (commandHistory.Count > 0) + if (CommandManager.GetCommandHistory().Count > 0) { historyIndex = Math.Max(historyIndex - 1, 0); - currentInput = commandHistory[historyIndex]; + currentInput = CommandManager.GetCommandHistory()[historyIndex]; Console.Write("\r> " + currentInput + new string('#', Console.WindowWidth - currentInput.Length - 2)); Console.CursorLeft = 0; Console.CursorTop--; @@ -61,10 +59,10 @@ internal class CliProcess() : Process("Cli") } break; case ConsoleKey.DownArrow: - if (commandHistory.Count > 0) + if (CommandManager.GetCommandHistory().Count > 0) { - historyIndex = Math.Min(historyIndex + 1, commandHistory.Count - 1); - currentInput = commandHistory[historyIndex]; + historyIndex = Math.Min(historyIndex + 1, CommandManager.GetCommandHistory().Count - 1); + currentInput = CommandManager.GetCommandHistory()[historyIndex]; Console.Write("\r> " + currentInput + new string('#', Console.WindowWidth - currentInput.Length - 2)); Console.CursorLeft = 0; Console.CursorTop--; @@ -82,20 +80,22 @@ internal class CliProcess() : Process("Cli") } } - private static void HandleCommand(string input) + private async Task HandleCommand(string input) { if (string.IsNullOrWhiteSpace(input)) { return; } - bool handled = CommandManager.TryExecute( - input, - line => Console.WriteLine(line)); + commandRunning = true; + + bool handled = await CommandManager.TryExecute(input, Console.WriteLine); if (!handled) { Console.WriteLine($"\"{input}\" is not a command"); } + + commandRunning = false; } } \ No newline at end of file diff --git a/Processes/TerminalProcess.cs b/Processes/TerminalProcess.cs index 169f86b..2fc7a0f 100644 --- a/Processes/TerminalProcess.cs +++ b/Processes/TerminalProcess.cs @@ -13,7 +13,6 @@ public class TerminalProcess() : Process("Terminal") { private Window window = null!; private readonly List history = []; - private readonly List commandHistory = []; private int historyIndex = -1; private string currentInput = ""; private readonly List textLines = []; @@ -28,6 +27,8 @@ public class TerminalProcess() : Process("Terminal") PrintLine("RemSox GUI Terminal v1.0"); PrintLine("Type 'help' for commands."); + historyIndex = CommandManager.GetCommandHistory().Count; + window.Flush(); window.OnKeyEvent += HandleKey; } @@ -46,18 +47,12 @@ public class TerminalProcess() : Process("Terminal") window.OnKeyEvent -= HandleKey; } - private void HandleKey(KeyEvent keyEvent) + private async void HandleKey(KeyEvent keyEvent) { if (keyEvent.Key == ConsoleKeyEx.Enter) { string cmd = currentInput; - if (!string.IsNullOrWhiteSpace(currentInput) && (commandHistory.Count == 0 || commandHistory[^1] != currentInput)) - { - commandHistory.Add(currentInput); - } - - historyIndex = commandHistory.Count; PrintLine("> " + cmd); currentInput = ""; @@ -77,12 +72,19 @@ public class TerminalProcess() : Process("Terminal") } else { - bool found = CommandManager.TryExecute(cmd, PrintLine); - if (!found) + window.OnKeyEvent -= HandleKey; + + bool handled = await CommandManager.TryExecute(cmd, PrintLine); + + if (!handled) { PrintLine($"\"{cmd}\" is not a command"); } + + window.OnKeyEvent += HandleKey; } + + historyIndex = CommandManager.GetCommandHistory().Count; } UpdateDisplay(); } @@ -96,19 +98,19 @@ public class TerminalProcess() : Process("Terminal") } else if (keyEvent.Key == ConsoleKeyEx.UpArrow) { - if (commandHistory.Count > 0) + if (CommandManager.GetCommandHistory().Count > 0) { historyIndex = Math.Max(historyIndex - 1, 0); - currentInput = commandHistory[historyIndex]; + currentInput = CommandManager.GetCommandHistory()[historyIndex]; UpdateDisplay(); } } else if (keyEvent.Key == ConsoleKeyEx.DownArrow) { - if (commandHistory.Count > 0) + if (CommandManager.GetCommandHistory().Count > 0) { - historyIndex = Math.Min(historyIndex + 1, commandHistory.Count - 1); - currentInput = commandHistory[historyIndex]; + historyIndex = Math.Min(historyIndex + 1, CommandManager.GetCommandHistory().Count - 1); + currentInput = CommandManager.GetCommandHistory()[historyIndex]; UpdateDisplay(); } } diff --git a/Processing/ProcessManager.cs b/Processing/ProcessManager.cs index beb04db..e4ca41d 100644 --- a/Processing/ProcessManager.cs +++ b/Processing/ProcessManager.cs @@ -8,7 +8,7 @@ namespace RemSox.Processing; public static class ProcessManager { - private static readonly ConcurrentDictionary processes = new(); + private static readonly ConcurrentDictionary processes = new(); private static readonly ConcurrentDictionary> processesByType = new(); @@ -57,15 +57,15 @@ public static class ProcessManager logger.Log($"Process {process.Name} (ID: {process.Id}) terminated with an exception: {ex}", LogSeverity.Error); } - _ = processes.TryAdd(id, (process, new ProcessMetrics())); + _ = processes.TryAdd(id, (process, new ProcessMetrics(), new TaskCompletionSource())); logger.Log($"Spawned process {process.Name} of type {typeof(T).Name} with ID {id}.", LogSeverity.Info); return id; } - public static void StopProcess(int processId, bool waitForExit = false) + public static void StopProcess(int processId) { - if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics) entry)) + if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) { return; } @@ -76,18 +76,21 @@ public static class ProcessManager public static async Task StopProcessAndWaitAsync(int processId) { - if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics) entry)) + if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) { return; } logger.Log($"Requesting stop of process {entry.Process.Name} (ID: {entry.Process.Id}).", LogSeverity.Info); + entry.Process.RequestStop(); + + await entry.ExitSource.Task; } public static void StopAllProcesses() { - foreach ((Process Process, ProcessMetrics Metrics) entry in processes.Values) + foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values) { StopProcess(entry.Process.Id); } @@ -95,9 +98,29 @@ public static class ProcessManager processes.Clear(); } + public static void WaitForProcessExit(int processId) + { + if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) + { + return; + } + + entry.ExitSource.Task.Wait(); + } + + public static async Task WaitForProcessExitAsync(int processId) + { + if (!processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) + { + return; + } + + await entry.ExitSource.Task; + } + public static Process? GetProcess(int processId) { - if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics) entry)) + if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) { return entry.Process; } @@ -116,7 +139,7 @@ public static class ProcessManager { foreach (int processId in set) { - if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics) entry) && entry.Process is T typedProcess) + if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry) && entry.Process is T typedProcess) { yield return typedProcess; } @@ -126,7 +149,7 @@ public static class ProcessManager public static IEnumerable GetAllProcesses() { - foreach ((Process Process, ProcessMetrics Metrics) entry in processes.Values) + foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values) { yield return entry.Process; } @@ -134,7 +157,7 @@ public static class ProcessManager public static bool TryGetProcess(int processId, out Process? process) { - if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics) entry)) + if (processes.TryGetValue(processId, out (Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry)) { process = entry.Process; return true; @@ -161,7 +184,7 @@ public static class ProcessManager internal static void TickAllProcesses() { - foreach ((Process Process, ProcessMetrics Metrics) entry in processes.Values) + foreach ((Process Process, ProcessMetrics Metrics, TaskCompletionSource ExitSource) entry in processes.Values) { if (entry.Process.IsRunning) { @@ -193,7 +216,10 @@ public static class ProcessManager { process.Stop(); - _ = processes.TryRemove(process.Id, out _); + if (processes.TryRemove(process.Id, out var processEntry)) + { + _ = processEntry.ExitSource.TrySetResult(); + } if (processesByType.TryGetValue(process.GetType(), out ConcurrentHashSet? set)) { diff --git a/UI/CLI/CommandManager.cs b/UI/CLI/CommandManager.cs index 8aa79ab..baa30a9 100644 --- a/UI/CLI/CommandManager.cs +++ b/UI/CLI/CommandManager.cs @@ -4,6 +4,8 @@ public static class CommandManager { private static readonly Dictionary commands = new(StringComparer.OrdinalIgnoreCase); + private static readonly List commandHistory = []; + public static void RegisterCommand(ICommand command) { commands[command.Name] = command; @@ -22,7 +24,12 @@ public static class CommandManager return commands.Values.OrderBy(command => command.Name); } - public static bool TryExecute(string input, Action printLine) + public static IList GetCommandHistory() + { + return commandHistory; + } + + public static async Task TryExecute(string input, Action printLine) { string trimmedInput = input.Trim(); @@ -31,6 +38,11 @@ public static class CommandManager return false; } + if (commandHistory.Count == 0 || commandHistory[^1] != trimmedInput) + { + commandHistory.Add(trimmedInput); + } + foreach (KeyValuePair entry in commands .OrderByDescending(entry => entry.Key.Length)) { @@ -49,7 +61,7 @@ public static class CommandManager arguments = trimmedInput[commandName.Length..].TrimStart(); } - entry.Value.Execute(arguments, printLine); + await entry.Value.ExecuteAsync(arguments, printLine); return true; } diff --git a/UI/CLI/Commands/ClearCommand.cs b/UI/CLI/Commands/ClearCommand.cs index b30ad77..7b2c448 100644 --- a/UI/CLI/Commands/ClearCommand.cs +++ b/UI/CLI/Commands/ClearCommand.cs @@ -6,8 +6,9 @@ public sealed class ClearCommand : ICommand public string Description => "Clear the screen"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { Console.Clear(); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/UI/CLI/Commands/HaltCommand.cs b/UI/CLI/Commands/HaltCommand.cs index a207b46..8548f67 100644 --- a/UI/CLI/Commands/HaltCommand.cs +++ b/UI/CLI/Commands/HaltCommand.cs @@ -6,9 +6,10 @@ public sealed class ShutdownCommand : ICommand public string Description => "Shutdown the system"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { printLine("Shutting down system..."); Sys.Power.Shutdown(); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/UI/CLI/Commands/HelpCommand.cs b/UI/CLI/Commands/HelpCommand.cs index 563d6f4..b2b71fc 100644 --- a/UI/CLI/Commands/HelpCommand.cs +++ b/UI/CLI/Commands/HelpCommand.cs @@ -6,7 +6,7 @@ public sealed class HelpCommand : ICommand public string Description => "Show this help message"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { printLine("Available commands:"); @@ -14,5 +14,7 @@ public sealed class HelpCommand : ICommand { printLine($" {command.Name,-12} - {command.Description}"); } + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/UI/CLI/Commands/ProcessCommands.cs b/UI/CLI/Commands/ProcessCommands.cs index 6411e0f..bd039c7 100644 --- a/UI/CLI/Commands/ProcessCommands.cs +++ b/UI/CLI/Commands/ProcessCommands.cs @@ -9,7 +9,7 @@ public sealed class SpawnProcessCommand : ICommand public string Description => "Spawn a new process"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { arguments = arguments?.Trim(); @@ -29,6 +29,7 @@ public sealed class SpawnProcessCommand : ICommand printLine("Usage: spawn "); printLine("Available processes: test, terminal"); } + return Task.CompletedTask; } } @@ -38,7 +39,7 @@ public sealed class ListProcessesCommand : ICommand public string Description => "List running processes"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { IEnumerable processes = ProcessManager.GetAllProcesses(); @@ -48,6 +49,8 @@ public sealed class ListProcessesCommand : ICommand { printLine($" ID: {process.Id}, Name: {process.Name}"); } + + return Task.CompletedTask; } } @@ -57,23 +60,24 @@ public sealed class StopProcessCommand : ICommand public string Description => "Stop a process by ID"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { string? idText = arguments; if (string.IsNullOrWhiteSpace(idText)) { printLine("Usage: stop "); - return; + return Task.CompletedTask; } if (int.TryParse(idText, out int processId)) { ProcessManager.StopProcess(processId); printLine($"Stopped process with ID {processId}"); - return; + return Task.CompletedTask; } printLine("Invalid process ID"); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/UI/CLI/Commands/RebootCommand.cs b/UI/CLI/Commands/RebootCommand.cs index 24542ac..edcf373 100644 --- a/UI/CLI/Commands/RebootCommand.cs +++ b/UI/CLI/Commands/RebootCommand.cs @@ -6,9 +6,10 @@ public class RebootCommand : ICommand public string Description => "Reboot the system"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { printLine("Rebooting system..."); Sys.Power.Reboot(); + return Task.CompletedTask; } } diff --git a/UI/CLI/Commands/StartGuiCommand.cs b/UI/CLI/Commands/StartGuiCommand.cs index e0b7e89..e3f145a 100644 --- a/UI/CLI/Commands/StartGuiCommand.cs +++ b/UI/CLI/Commands/StartGuiCommand.cs @@ -8,14 +8,14 @@ public class StartGuiCommand : ICommand public string Name => "start-gui"; public string Description => "Starts the Graphical User Interface (Desktop Process)"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { printLine("Starting Desktop Process..."); if (ProcessManager.IsProcessRunning()) { printLine("Desktop Process is already running."); - return; + return Task.CompletedTask; } foreach (Process process in ProcessManager.GetProcessesOfType()) @@ -24,5 +24,6 @@ public class StartGuiCommand : ICommand } _ = ProcessManager.SpawnProcess(); + return Task.CompletedTask; } } diff --git a/UI/CLI/Commands/StopGuiCommand.cs b/UI/CLI/Commands/StopGuiCommand.cs index 4b70360..f6d18da 100644 --- a/UI/CLI/Commands/StopGuiCommand.cs +++ b/UI/CLI/Commands/StopGuiCommand.cs @@ -8,7 +8,7 @@ public class StopGuiCommand : ICommand public string Name => "stop-gui"; public string Description => "Stops the Graphical User Interface (Desktop Process)"; - public async void Execute(string? arguments, Action printLine) + public async Task ExecuteAsync(string? arguments, Action printLine) { printLine("Stopping Desktop Process..."); diff --git a/UI/CLI/Commands/ViewProcessLogs.cs b/UI/CLI/Commands/ViewProcessLogs.cs index cd9531a..a869d38 100644 --- a/UI/CLI/Commands/ViewProcessLogs.cs +++ b/UI/CLI/Commands/ViewProcessLogs.cs @@ -9,7 +9,7 @@ public class ViewProcessLogs : ICommand public string Description => "View logs for a process"; - public void Execute(string? arguments, Action printLine) + public Task ExecuteAsync(string? arguments, Action printLine) { if (int.TryParse(arguments, out int processId)) { @@ -21,6 +21,7 @@ public class ViewProcessLogs : ICommand IEnumerable logs = ProcessManager.GetLogs(); PrintLogs(logs, printLine); } + return Task.CompletedTask; } private static void PrintLogs(IEnumerable logs, Action printLine) diff --git a/UI/CLI/ICommand.cs b/UI/CLI/ICommand.cs index 14caecf..96a55ee 100644 --- a/UI/CLI/ICommand.cs +++ b/UI/CLI/ICommand.cs @@ -19,5 +19,5 @@ public interface ICommand /// /// The arguments provided to the command. /// A delegate to stream output lines to the current console or terminal. - void Execute(string? arguments, Action printLine); + Task ExecuteAsync(string? arguments, Action printLine); } diff --git a/UI/GUI/Windows/WindowManager.cs b/UI/GUI/Windows/WindowManager.cs index 13fa2fd..f831208 100644 --- a/UI/GUI/Windows/WindowManager.cs +++ b/UI/GUI/Windows/WindowManager.cs @@ -32,7 +32,7 @@ public static class WindowManager /// /// Processes input, updates interaction state, and triggers rendering of all windows. /// - public static void Update() + internal static void Update() { Point pointerPosition = new(MouseManager.X, MouseManager.Y); bool leftButtonDown = MouseManager.LeftButton;