spectreconsole / spectre.console

A .NET library that makes it easier to create beautiful console applications.
https://spectreconsole.net
MIT License
9.43k stars 498 forks source link

Allow AnsiConsoleFactory to use a user provided IAnsiConsoleInput #1497

Open Astn opened 7 months ago

Astn commented 7 months ago

Is Your Feature Request Related to a Problem? Please Describe.

I've encountered an issue where I am unable to bind my input back to the IAnsiConsole when utilizing AnsiConsoleFactory. My application is a Blazor app, meaning the input mechanism I employ does not originate from StandardInput. Instead, it's an adaptation of browser-sourced keyboard events.

Describe the Solution You'd Like

I propose the introduction of the ability to provide a custom implementation of IAnsiConsoleInput through a property in AnsiConsoleSettings. This adjustment would enable the construction of my console using AnsiConsoleFactory, allowing for more flexibility and integration with my Blazor application.

Describe Alternatives You've Considered

Initially, I attempted to develop my own version of AnsiConsoleFactory. However, I quickly discovered a complex chain of dependencies among classes designated as internal, which significantly hampers the practicality of leveraging the extensive utility offered by the built-in AnsiConsoleFactory.

Additional Context

I discovered that with a few minor modifications, it was possible to integrate my custom input in a manner that is fully backward compatible.

Here is the approach I took: https://github.com/Astn/spectre.console/commit/68fc35d99710e4dd9aada550dc781adb95f0164f

Subject: [PATCH] Allow ConsoleInput to be passed in.
---
Index: src/Spectre.Console/AnsiConsoleFactory.cs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/Spectre.Console/AnsiConsoleFactory.cs b/src/Spectre.Console/AnsiConsoleFactory.cs
--- a/src/Spectre.Console/AnsiConsoleFactory.cs (revision 8da05bcc176741565ddace97bcc59f58587734eb)
+++ b/src/Spectre.Console/AnsiConsoleFactory.cs (revision 68fc35d99710e4dd9aada550dc781adb95f0164f)
@@ -59,9 +59,15 @@
             settings.Enrichment,
             settings.EnvironmentVariables);

-        return new AnsiConsoleFacade(
+        settings.InputBuilder ??= (p) => new DefaultInput(p);
+        settings.Input ??= settings.InputBuilder(profile);
+        settings.ExclusivityMode ??= new DefaultExclusivityMode();
+        var acf =  new AnsiConsoleFacade(
             profile,
-            settings.ExclusivityMode ?? new DefaultExclusivityMode());
+            settings.ExclusivityMode,
+            settings.Input);
+
+        return acf;
     }

     private static (bool Ansi, bool Legacy) DetectAnsi(AnsiConsoleSettings settings, System.IO.TextWriter buffer)
Index: src/Spectre.Console/AnsiConsoleSettings.cs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/Spectre.Console/AnsiConsoleSettings.cs b/src/Spectre.Console/AnsiConsoleSettings.cs
--- a/src/Spectre.Console/AnsiConsoleSettings.cs    (revision 8da05bcc176741565ddace97bcc59f58587734eb)
+++ b/src/Spectre.Console/AnsiConsoleSettings.cs    (revision 68fc35d99710e4dd9aada550dc781adb95f0164f)
@@ -21,6 +21,13 @@
     /// </summary>
     public IAnsiConsoleOutput? Out { get; set; }

+
+    public Func<Profile, IAnsiConsoleInput>? InputBuilder { get; set; }
+    /// <summary>
+    /// Gets or sets the input.
+    /// </summary>
+    public IAnsiConsoleInput? Input { get; set; }
+
     /// <summary>
     /// Gets or sets a value indicating whether or not the
     /// terminal is interactive or not.
Index: src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs b/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
--- a/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs    (revision 8da05bcc176741565ddace97bcc59f58587734eb)
+++ b/src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs    (revision 68fc35d99710e4dd9aada550dc781adb95f0164f)
@@ -12,12 +12,12 @@
     public IExclusivityMode ExclusivityMode { get; }
     public RenderPipeline Pipeline { get; }

-    public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode)
+    public AnsiConsoleFacade(Profile profile, IExclusivityMode exclusivityMode, IAnsiConsoleInput input)
     {
         _renderLock = new object();

         Profile = profile ?? throw new ArgumentNullException(nameof(profile));
-        Input = new DefaultInput(Profile);
+        Input = input;
         ExclusivityMode = exclusivityMode ?? throw new ArgumentNullException(nameof(exclusivityMode));
         Pipeline = new RenderPipeline();

Thoughts?


Please upvote :+1: this issue if you are interested in it.

phil-scott-78 commented 7 months ago

few years back I went down this route when I wanted to try and see what running spectre.console in blazor would look like. I came up with a pretty similar solution to this with a fork and it worked well. I'd be in favor

here's that repo for what its worth - https://github.com/phil-scott-78/try-spectre/blob/main/src/TrySpectre/Pages/Index.razor