Skip to content

Instantly share code, notes, and snippets.

@patriksvensson
Created May 29, 2023 19:16
Show Gist options
  • Save patriksvensson/1c320485b4a32e9dcc58a0abe38196bb to your computer and use it in GitHub Desktop.
Save patriksvensson/1c320485b4a32e9dcc58a0abe38196bb to your computer and use it in GitHub Desktop.
Implementation of a IAnsiConsole that writes to a StringBuffer
using Spectre.Console;
using Spectre.Console.Rendering;
using System.Text;
namespace SpectreVirtual;
public static class Program
{
public static void Main(string[] args)
{
// Create the virtual terminal
var console = VirtualTerminal.Create();
// Write some text
console.Write(new Panel("Hello World"));
// Get the output
var output = console.Output.ToString();
}
}
public sealed class VirtualTerminal : IAnsiConsole
{
private readonly IAnsiConsole _owner;
public Profile Profile => _owner.Profile;
public IAnsiConsoleCursor Cursor => _owner.Cursor;
public IAnsiConsoleInput Input { get; }
public StringWriter Output { get; }
public IExclusivityMode ExclusivityMode => _owner.ExclusivityMode;
public RenderPipeline Pipeline => _owner.Pipeline;
private VirtualTerminal(IAnsiConsole owner, StringWriter output)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
Input = new VirtualConsoleInput();
Output = output ?? throw new ArgumentNullException(nameof(output));
}
public static VirtualTerminal Create(int width = 80, int height = 24)
{
var output = new StringWriter();
var console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Yes,
Out = new VirtualConsoleOutput(output, width, height),
Interactive = InteractionSupport.Yes,
});
return new VirtualTerminal(console, output);
}
public void Clear(bool home)
{
_owner.Clear();
}
public void Write(IRenderable renderable)
{
_owner.Write(renderable);
}
}
public sealed class VirtualConsoleInput : IAnsiConsoleInput
{
public bool IsKeyAvailable()
{
return false;
}
public ConsoleKeyInfo? ReadKey(bool intercept)
{
return null;
}
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult<ConsoleKeyInfo?>(null);
}
}
public sealed class VirtualConsoleOutput : IAnsiConsoleOutput
{
public TextWriter Writer { get; }
public bool IsTerminal { get; }
public int Width { get; }
public int Height { get; }
public VirtualConsoleOutput(TextWriter writer, int width, int height)
{
Writer = writer;
IsTerminal = true;
Width = width;
Height = height;
}
public void SetEncoding(Encoding encoding)
{
}
}
@andrewmd5
Copy link

andrewmd5 commented May 30, 2023

No output with this solution, however, instead of using a 'StringWriter' I just made a TextWriter class that redirects to console and go output:

public sealed class VirtualTerminal : IAnsiConsole
{
    private readonly IAnsiConsole _owner;

    public Profile Profile => _owner.Profile;
    public IAnsiConsoleCursor Cursor => _owner.Cursor;
    public IAnsiConsoleInput Input { get; }
    public TextWriter Output { get; }
    public IExclusivityMode ExclusivityMode => _owner.ExclusivityMode;
    public RenderPipeline Pipeline => _owner.Pipeline;

    private VirtualTerminal(IAnsiConsole owner, TextWriter output)
    {
        _owner = owner ?? throw new ArgumentNullException(nameof(owner));

        Input = new VirtualConsoleInput();
        Output = output ?? throw new ArgumentNullException(nameof(output));
    }

    public static VirtualTerminal Create(int width = 80, int height = 24)
    {
        var consoleOutput = new ConsoleRedirectWriter(new StringWriter());

        var console = AnsiConsole.Create(new AnsiConsoleSettings
        {
            Ansi = AnsiSupport.Yes,
            Out = new VirtualConsoleOutput(consoleOutput, width, height),
            Interactive = InteractionSupport.No,
        });

        return new VirtualTerminal(console, consoleOutput);
    }

    public void Clear(bool home)
    {
        _owner.Clear();
    }

    public void Write(IRenderable renderable)
    {
        _owner.Write(renderable);
    }
}

public sealed class VirtualConsoleInput : IAnsiConsoleInput
{
    public bool IsKeyAvailable()
    {
        return false;
    }

    public ConsoleKeyInfo? ReadKey(bool intercept)
    {
        return null;
    }

    public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
    {
        return Task.FromResult<ConsoleKeyInfo?>(null);
    }
}

public sealed class VirtualConsoleOutput : IAnsiConsoleOutput
{
    public TextWriter Writer { get; }
    public bool IsTerminal { get; }
    public int Width { get; }
    public int Height { get; }

    public VirtualConsoleOutput(TextWriter writer, int width, int height)
    {
        Writer = writer;
        IsTerminal = true;
        Width = width;
        Height = height;
    }

    public void SetEncoding(Encoding encoding)
    {
    
    }
}

public class ConsoleRedirectWriter : TextWriter
{
    private readonly TextWriter originalWriter;

    public ConsoleRedirectWriter(TextWriter originalWriter)
    {
        this.originalWriter = originalWriter;
    }

    public override void Write(char value)
    {
        // Redirect the character to the console
        Console.Write(value);

        // Write the character to the original writer
        originalWriter.Write(value);
    }

    public override void Write(string? value)
    {
        // Redirect the string to the console
        Console.Write(value);

        // Write the string to the original writer
        originalWriter.Write(value);
    }

    public override void WriteLine(string? value)
    {
        // Redirect the string followed by a line break to the console
        Console.WriteLine(value);

        // Write the string followed by a line break to the original writer
        originalWriter.WriteLine(value);
    }

    public override Encoding Encoding => originalWriter.Encoding;
}

It doesn't account for Err vs. Out, but you put me on the right path.
Screenshot 2023-05-30 at 9 15 40 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment