From c9515a822d4909a047890c91a1702af0eef2f941 Mon Sep 17 00:00:00 2001 From: Tomi Eckert Date: Wed, 11 Mar 2026 14:19:57 +0100 Subject: [PATCH] feat: add new input processors (readline), add new figlet font --- AnchorCli.csproj | 4 + Assets/3d.flf | 818 ++++++++++++++++++++++++++++++++++++++++++++++ InputProcessor.cs | 125 +++++++ Program.cs | 18 +- ReplLoop.cs | 5 +- 5 files changed, 964 insertions(+), 6 deletions(-) create mode 100644 Assets/3d.flf create mode 100644 InputProcessor.cs diff --git a/AnchorCli.csproj b/AnchorCli.csproj index e3555cb..84b01f9 100644 --- a/AnchorCli.csproj +++ b/AnchorCli.csproj @@ -19,6 +19,10 @@ false + + + + diff --git a/Assets/3d.flf b/Assets/3d.flf new file mode 100644 index 0000000..8414f16 --- /dev/null +++ b/Assets/3d.flf @@ -0,0 +1,818 @@ +flf2a$ 8 8 20 -1 1 +3d font created by xero +$$@ +$$@ +$$@ +$$@ +$$@ +$$@ +$$@ +$$@@ + ██@ +░██@ +░██@ +░██@ +░██@ +░░ @ + ██@ +░░ @@ + █ █@ +░█ ░█@ +░ ░ @ + @ + @ + @ + @ + @@ + @ + ██ ██ @ + ████████████@ +░░░██░░░░██░ @ + ░██ ░██ @ + ████████████@ +░░░██░░░░██░ @ + ░░ ░░ @@ + █ @ + █████@ +░█░█░ @ +░█████@ +░░░█░█@ + █████@ +░░░█░ @ + ░ @@ + @ + ██ ██ @ +░░ ██ @ + ██ @ + ██ @ + ██ @ + ██ ██ @ +░░ ░░ @@ + ██ @ + █░ █ @ + ░ ██ @ + █░ █ █@ + █ ░ █ @ +░█ ░█ @ +░ ████ █@ + ░░░░ ░ @@ + ██@ +░░█@ + ░ @ + @ + @ + @ + @ + @@ + ██@ + ██ @ + ██ @ +░██ @ +░██ @ +░░██ @ + ░░██@ + ░░ @@ + ██ @ +░░██ @ + ░░██@ + ░██@ + ░██@ + ██ @ + ██ @ +░░ @@ + ██ @ + ██ ░██ ██ @ + ░░██ ░██ ██ @ + ██████████████@ +░░░██░░██░░██░ @ + ██ ░██ ░░██ @ + ░░ ░██ ░░ @ + ░░ @@ + @ + █ @ + ░█ @ + █████████@ + ░░░░░█░░░ @ + ░█ @ + ░ @ + @@ + @ + @ + @ + @ + @ + ██@ +░░█@ + ░ @@ + @ + @ + @ + █████@ +░░░░░ @ + @ + @ + @@ + @ + @ + @ + @ + @ + ██@ +░██@ +░░ @@ + ██@ + ██ @ + ██ @ + ██ @ + ██ @ + ██ @ + ██ @ +░░ @@ + ████ @ + █░░░██@ +░█ █░█@ +░█ █ ░█@ +░██ ░█@ +░█ ░█@ +░ ████ @ + ░░░░ @@ + ██ @ + ███ @ +░░██ @ + ░██ @ + ░██ @ + ░██ @ + ████@ +░░░░ @@ + ████ @ + █░░░ █@ +░ ░█@ + ███ @ + █░░ @ + █ @ +░██████@ +░░░░░░ @@ + ████ @ + █░░░ █@ +░ ░█@ + ███ @ + ░░░ █@ + █ ░█@ +░ ████ @ + ░░░░ @@ + ██ @ + █░█ @ + █ ░█ @ + ██████@ +░░░░░█ @ + ░█ @ + ░█ @ + ░ @@ + ██████@ +░█░░░░ @ +░█████ @ +░░░░░ █@ + ░█@ + █ ░█@ +░ ████ @ + ░░░░ @@ + ████ @ + █░░░ █@ +░█ ░ @ +░█████ @ +░█░░░ █@ +░█ ░█@ +░ ████ @ + ░░░░ @@ + ██████@ +░░░░░░█@ + ░█@ + █ @ + █ @ + █ @ + █ @ + ░ @@ + ████ @ + █░░░ █@ +░█ ░█@ +░ ████ @ + █░░░ █@ +░█ ░█@ +░ ████ @ + ░░░░ @@ + ████ @ + █░░░ █@ +░█ ░█@ +░ ████ @ + ░░░█ @ + █ @ + █ @ + ░ @@ + @ + @ + @ + @ + ██@ +░░ @ + ██@ +░░ @@ + @ + @ + @ + ██@ +░░ @ + ██@ +░░█@ + ░ @@ + ██@ + ██░ @ + ██░ @ + ██░ @ +░░ ██ @ + ░░ ██ @ + ░░ ██@ + ░░ @@ + @ + @ + ██████@ +░░░░░░ @ + ██████@ +░░░░░░ @ + @ + @@ + ██ @ +░░ ██ @ + ░░ ██ @ + ░░ ██@ + ██░ @ + ██░ @ + ██░ @ +░░ @@ + ████ @ + ██░░██@ +░██ ░██@ +░░ ██ @ + ██ @ + ░░ @ + ██ @ + ░░ @@ + ████ @ + █░░░ █@ +░█ ██░█@ +░█░█ ░█@ +░█░ ██ @ +░█ ░░ @ +░ █████@ + ░░░░░ @@ + ██ @ + ████ @ + ██░░██ @ + ██ ░░██ @ + ██████████@ +░██░░░░░░██@ +░██ ░██@ +░░ ░░ @@ + ██████ @ +░█░░░░██ @ +░█ ░██ @ +░██████ @ +░█░░░░ ██@ +░█ ░██@ +░███████ @ +░░░░░░░ @@ + ██████ @ + ██░░░░██@ + ██ ░░ @ +░██ @ +░██ @ +░░██ ██@ + ░░██████ @ + ░░░░░░ @@ + ███████ @ +░██░░░░██ @ +░██ ░██@ +░██ ░██@ +░██ ░██@ +░██ ██ @ +░███████ @ +░░░░░░░ @@ + ████████@ +░██░░░░░ @ +░██ @ +░███████ @ +░██░░░░ @ +░██ @ +░████████@ +░░░░░░░░ @@ + ████████@ +░██░░░░░ @ +░██ @ +░███████ @ +░██░░░░ @ +░██ @ +░██ @ +░░ @@ + ████████ @ + ██░░░░░░██@ + ██ ░░ @ +░██ @ +░██ █████@ +░░██ ░░░░██@ + ░░████████ @ + ░░░░░░░░ @@ + ██ ██@ +░██ ░██@ +░██ ░██@ +░██████████@ +░██░░░░░░██@ +░██ ░██@ +░██ ░██@ +░░ ░░ @@ + ██@ +░██@ +░██@ +░██@ +░██@ +░██@ +░██@ +░░ @@ + ██@ + ░██@ + ░██@ + ░██@ + ░██@ + ██ ░██@ +░░█████ @ + ░░░░░ @@ + ██ ██@ +░██ ██ @ +░██ ██ @ +░████ @ +░██░██ @ +░██░░██ @ +░██ ░░██@ +░░ ░░ @@ + ██ @ +░██ @ +░██ @ +░██ @ +░██ @ +░██ @ +░████████@ +░░░░░░░░ @@ + ████ ████@ +░██░██ ██░██@ +░██░░██ ██ ░██@ +░██ ░░███ ░██@ +░██ ░░█ ░██@ +░██ ░ ░██@ +░██ ░██@ +░░ ░░ @@ + ████ ██@ +░██░██ ░██@ +░██░░██ ░██@ +░██ ░░██ ░██@ +░██ ░░██░██@ +░██ ░░████@ +░██ ░░███@ +░░ ░░░ @@ + ███████ @ + ██░░░░░██ @ + ██ ░░██@ +░██ ░██@ +░██ ░██@ +░░██ ██ @ + ░░███████ @ + ░░░░░░░ @@ + ███████ @ +░██░░░░██@ +░██ ░██@ +░███████ @ +░██░░░░ @ +░██ @ +░██ @ +░░ @@ + ███████ @ + ██░░░░░██ @ + ██ ░░██ @ +░██ ░██ @ +░██ ██░██ @ +░░██ ░░ ██ @ + ░░███████ ██@ + ░░░░░░░ ░░ @@ + ███████ @ +░██░░░░██ @ +░██ ░██ @ +░███████ @ +░██░░░██ @ +░██ ░░██ @ +░██ ░░██@ +░░ ░░ @@ + ████████@ + ██░░░░░░ @ +░██ @ +░█████████@ +░░░░░░░░██@ + ░██@ + ████████ @ +░░░░░░░░ @@ + ██████████@ +░░░░░██░░░ @ + ░██ @ + ░██ @ + ░██ @ + ░██ @ + ░██ @ + ░░ @@ + ██ ██@ +░██ ░██@ +░██ ░██@ +░██ ░██@ +░██ ░██@ +░██ ░██@ +░░███████ @ + ░░░░░░░ @@ + ██ ██@ +░██ ░██@ +░██ ░██@ +░░██ ██ @ + ░░██ ██ @ + ░░████ @ + ░░██ @ + ░░ @@ + ██ ██@ +░██ ░██@ +░██ █ ░██@ +░██ ███ ░██@ +░██ ██░██░██@ +░████ ░░████@ +░██░ ░░░██@ +░░ ░░ @@ + ██ ██@ +░░██ ██ @ + ░░██ ██ @ + ░░███ @ + ██░██ @ + ██ ░░██ @ + ██ ░░██@ +░░ ░░ @@ + ██ ██@ +░░██ ██ @ + ░░████ @ + ░░██ @ + ░██ @ + ░██ @ + ░██ @ + ░░ @@ + ████████@ +░░░░░░██ @ + ██ @ + ██ @ + ██ @ + ██ @ + ████████@ +░░░░░░░░ @@ + █████@ +░██░░ @ +░██ @ +░██ @ +░██ @ +░██ @ +░█████@ +░░░░░ @@ + ██ @ +░░██ @ + ░░██ @ + ░░██ @ + ░░██ @ + ░░██ @ + ░░██@ + ░░ @@ + █████@ +░░░░██@ + ░██@ + ░██@ + ░██@ + ░██@ + █████@ +░░░░░ @@ + ██ @ + ██░ ██ @ + ██ ░░ ██@ +░░ ░░ @ + @ + @ + @ + @@ + @ + @ + @ + @ + @ + @ + █████@ +░░░░░ @@ + ██@ +░█ @ +░ @ + @ + @ + @ + @ + @@ + @ + @ + ██████ @ + ░░░░░░██ @ + ███████ @ + ██░░░░██ @ +░░████████@ + ░░░░░░░░ @@ + ██ @ +░██ @ +░██ @ +░██████ @ +░██░░░██@ +░██ ░██@ +░██████ @ +░░░░░ @@ + @ + @ + █████ @ + ██░░░██@ +░██ ░░ @ +░██ ██@ +░░█████ @ + ░░░░░ @@ + ██@ + ░██@ + ░██@ + ██████@ + ██░░░██@ +░██ ░██@ +░░██████@ + ░░░░░░ @@ + @ + @ + █████ @ + ██░░░██@ +░███████@ +░██░░░░ @ +░░██████@ + ░░░░░░ @@ + ████@ + ░██░ @ + ██████@ +░░░██░ @ + ░██ @ + ░██ @ + ░██ @ + ░░ @@ + @ + █████ @ + ██░░░██@ +░██ ░██@ +░░██████@ + ░░░░░██@ + █████ @ + ░░░░░ @@ + ██ @ +░██ @ +░██ @ +░██████ @ +░██░░░██@ +░██ ░██@ +░██ ░██@ +░░ ░░ @@ + ██@ +░░ @ + ██@ +░██@ +░██@ +░██@ +░██@ +░░ @@ + ██@ + ░░ @ + ██@ + ░██@ + ░██@ + ██░██@ +░░███ @ + ░░░ @@ + ██ @ +░██ @ +░██ ██@ +░██ ██ @ +░████ @ +░██░██ @ +░██░░██@ +░░ ░░ @@ + ██@ + ░██@ + ░██@ + ░██@ + ░██@ + ░██@ + ███@ +░░░ @@ + @ + @ + ██████████ @ +░░██░░██░░██@ + ░██ ░██ ░██@ + ░██ ░██ ░██@ + ███ ░██ ░██@ +░░░ ░░ ░░ @@ + @ + @ + ███████ @ +░░██░░░██@ + ░██ ░██@ + ░██ ░██@ + ███ ░██@ +░░░ ░░ @@ + @ + @ + ██████ @ + ██░░░░██@ +░██ ░██@ +░██ ░██@ +░░██████ @ + ░░░░░░ @@ + @ + ██████ @ +░██░░░██@ +░██ ░██@ +░██████ @ +░██░░░ @ +░██ @ +░░ @@ + @ + ████ @ + ██░░██ @ +░██ ░██ @ +░░█████ @ + ░░░░██ @ + ░███@ + ░░░ @@ + @ + @ + ██████@ +░░██░░█@ + ░██ ░ @ + ░██ @ +░███ @ +░░░ @@ + @ + @ + ██████@ + ██░░░░ @ +░░█████ @ + ░░░░░██@ + ██████ @ +░░░░░░ @@ + ██ @ + ░██ @ + ██████@ +░░░██░ @ + ░██ @ + ░██ @ + ░░██ @ + ░░ @@ + @ + @ + ██ ██@ +░██ ░██@ +░██ ░██@ +░██ ░██@ +░░██████@ + ░░░░░░ @@ + @ + @ + ██ ██@ +░██ ░██@ +░░██ ░██ @ + ░░████ @ + ░░██ @ + ░░ @@ + @ + @ + ███ ██@ +░░██ █ ░██@ + ░██ ███░██@ + ░████░████@ + ███░ ░░░██@ +░░░ ░░░ @@ + @ + @ + ██ ██@ +░░██ ██ @ + ░░███ @ + ██░██ @ + ██ ░░██@ +░░ ░░ @@ + @ + ██ ██@ + ░░██ ██ @ + ░░███ @ + ░██ @ + ██ @ + ██ @ + ░░ @@ + @ + @ + ██████@ +░░░░██ @ + ██ @ + ██ @ + ██████@ +░░░░░░ @@ + ███@ + ██░ @ + ░██ @ + ███ @ +░░░██ @ + ░██ @ + ░░███@ + ░░░ @@ + █@ +░█@ +░█@ +░ @ + █@ +░█@ +░█@ +░ @@ + ███ @ +░░░██ @ + ░██ @ + ░░███@ + ██░ @ + ░██ @ + ███ @ +░░░ @@ + ██ ███ @ +░░███░░██@ + ░░░ ░░ @ + @ + @ + @ + @ + @@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@@ diff --git a/InputProcessor.cs b/InputProcessor.cs new file mode 100644 index 0000000..be3898c --- /dev/null +++ b/InputProcessor.cs @@ -0,0 +1,125 @@ +using Spectre.Console; + +namespace AnchorCli +{ + internal class InputProcessor + { + private static void DisplayText(int left, string buffer, int index = -1, string placeholder = "", int viewportOffset = 0) + { + Console.CursorLeft = left; + + if (buffer.Length == 0 && index == 0) + { + AnsiConsole.Markup($"[grey dim]{placeholder}{new string(' ', Console.WindowWidth - 1 - left - placeholder.Length)}[/]"); + return; + } + + var visibleWidth = Console.WindowWidth - left - 1; + var displayStart = Math.Min(viewportOffset, Math.Max(0, buffer.Length - 1)); + var displayEnd = Math.Min(displayStart + visibleWidth, buffer.Length); + var displayBuffer = string.Concat(buffer.AsSpan(displayStart, displayEnd - displayStart), " "); + + for (var i = 0; i < displayBuffer.Length; i++) + { + var actualIndex = displayStart + i; + if (index != -1 && actualIndex == index) + { + Console.ForegroundColor = ConsoleColor.Black; + Console.BackgroundColor = ConsoleColor.White; + } + + Console.Write(displayBuffer[i]); + + if (index != -1 && actualIndex == index) + Console.ResetColor(); + } + + // Fill remaining space with spaces + var remainingSpaces = visibleWidth - displayBuffer.Length; + if (remainingSpaces > 0) + Console.Write(new string(' ', remainingSpaces)); + } + + public static string ReadLine(string placeholder = "") + { + Console.CursorVisible = false; + var buffer = string.Empty; + var index = 0; + var viewportOffset = 0; + var left = Console.CursorLeft; + + DisplayText(left, buffer, index, placeholder, viewportOffset); + + while (true) + { + var inputKey = Console.ReadKey(intercept: true); + switch (inputKey) + { + case { Key: ConsoleKey.Enter } when buffer.Length > 0: + DisplayText(left, buffer); + Console.WriteLine(); + return buffer; + + case { Key: ConsoleKey.Backspace } when index > 0: + index--; + buffer = buffer.Remove(index, 1); + break; + + case { Key: ConsoleKey.Delete } when index < buffer.Length: + buffer = buffer.Remove(index, 1); + break; + + case { Key: ConsoleKey.LeftArrow, Modifiers: ConsoleModifiers.Control }: + while (index > 0 && buffer[index - 1] == ' ') + index--; + while (index > 0 && buffer[index - 1] != ' ') + index--; + break; + + case { Key: ConsoleKey.RightArrow, Modifiers: ConsoleModifiers.Control }: + while (index < buffer.Length && buffer[index] == ' ') + index++; + while (index < buffer.Length && buffer[index] != ' ') + index++; + break; + + case { Key: ConsoleKey.LeftArrow } when index > 0: + index--; + break; + + case { Key: ConsoleKey.RightArrow } when index < buffer.Length: + index++; + break; + + case { Key: ConsoleKey.W, Modifiers: ConsoleModifiers.Control } when index > 0: + var deleteStart = index; + while (deleteStart > 0 && buffer[deleteStart - 1] == ' ') + deleteStart--; + while (deleteStart > 0 && buffer[deleteStart - 1] != ' ') + deleteStart--; + var charsToDelete = index - deleteStart; + buffer = buffer.Remove(deleteStart, charsToDelete); + index = deleteStart; + break; + + default: + var keyChar = inputKey.KeyChar; + if (!(char.IsLetterOrDigit(keyChar) || char.IsWhiteSpace(keyChar) || char.IsPunctuation(keyChar) || char.IsSymbol(keyChar))) + break; + + buffer = buffer.Insert(index, inputKey.KeyChar.ToString()); + index++; + break; + } + // Adjust viewport for scrolling + var visibleWidth = Console.WindowWidth - left - 1; + if (index < viewportOffset) + viewportOffset = index; + else if (index >= viewportOffset + visibleWidth) + viewportOffset = index - visibleWidth + 1; + + DisplayText(left, buffer, index, placeholder, viewportOffset); + } + } + } +} diff --git a/Program.cs b/Program.cs index e8fda46..32ab85b 100644 --- a/Program.cs +++ b/Program.cs @@ -56,9 +56,21 @@ if (ProviderFactory.IsOpenRouter(endpoint)) // ── Pretty header ─────────────────────────────────────────────────────── -AnsiConsole.Write( - new FigletText("anchor") - .Color(Color.CornflowerBlue)); +var fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("AnchorCli.Assets.3d.flf"); +if (fontStream != null) +{ + var font = FigletFont.Load(fontStream); + AnsiConsole.Write( + new FigletText(font, "anchor") + .Color(Color.CornflowerBlue)); +} +else +{ + AnsiConsole.Write( + new FigletText("anchor") + .Color(Color.CornflowerBlue)); +} + var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown"; diff --git a/ReplLoop.cs b/ReplLoop.cs index c93f1e4..f749ff7 100644 --- a/ReplLoop.cs +++ b/ReplLoop.cs @@ -22,7 +22,6 @@ internal sealed class ReplLoop public async Task RunAsync() { - AnsiConsole.MarkupLine("[dim]Type your message, or use [bold]/help[/] to see commands.[/]"); AnsiConsole.MarkupLine("[dim]Press [bold]Ctrl+C[/] to cancel the current response.[/]"); AnsiConsole.WriteLine(); @@ -36,7 +35,8 @@ internal sealed class ReplLoop while (true) { - string input = ReadLine.Read("❯ "); + AnsiConsole.Markup("[grey]❯ [/]"); + string input = InputProcessor.ReadLine("Type your message, or use [bold]/help[/] to see commands."); if (string.IsNullOrWhiteSpace(input)) continue; @@ -188,7 +188,6 @@ internal sealed class ReplLoop } AnsiConsole.Write(new Rule().RuleStyle(Style.Parse("grey dim"))); - AnsiConsole.WriteLine(); _session.History.Add(new ChatMessage(ChatRole.Assistant, fullResponse));