Alternative IAnsiConsole implementations #1500
Replies: 5 comments 5 replies
-
Interesting question.... I started looking into However, I see there are three implementations of it, namely: Each are interesting, but the
_console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Yes,
ColorSystem = (ColorSystemSupport)ColorSystem.TrueColor,
Out = new AnsiConsoleOutput(_writer),
Interactive = InteractionSupport.No,
ExclusivityMode = new NoopExclusivityMode(),
Enrichment = new ProfileEnrichment
{
UseDefaultEnrichers = false,
},
}); which is used to fulfill many of the public void Write(IRenderable renderable)
{
if (renderable is null)
{
throw new ArgumentNullException(nameof(renderable));
}
_recorded.Add(renderable);
_console.Write(renderable);
} Perhaps you could use these patterns to redirect the public void Write(IRenderable renderable)
{
//_console.Write(renderable); //nb. _console instantiated above in the call to AnsiConsole.Create()
//or
//WPFConsole.Write(renderable);
//or
//BlazorWPFConsole.Write(renderable);
} I'd be interested in seeing how you might get on with this. |
Beta Was this translation helpful? Give feedback.
-
Thanks @FrankRay78 - that's great info. It looks the key bit after that is understanding IRenderable. It looks like the thing to do is use the IRenderable.Render method to emit a set of Segments. Segments get you Style+Text so I can see how this could work for basic rendering. May see if I can knock up a quick prototype later and post back here. |
Beta Was this translation helpful? Give feedback.
-
Basic proof of concept - doesn't implement IAnsiConsole since that has quite a bit of "hair" but allows IRenderables to be displayed //Declare we can support 24 bit color and unicode but nothing else
public class BasicCapabilities : IReadOnlyCapabilities
{
public ColorSystem ColorSystem => ColorSystem.TrueColor;
public bool Unicode => true;
public bool Ansi => false;
public bool Links => false;
public bool Legacy => false;
public bool IsTerminal => false;
public bool Interactive => false;
}
//simple renderer for WPF richtextbox (fixed size, no decorations)
public class WpfRenderer
{
private readonly RenderOptions _options;
private readonly RichTextBox _textBox;
public WpfRenderer(RichTextBox textBox)
{
_textBox = textBox;
var consoleSize = new Size(80, 40); //HACK: hardcoded size for now
_options = new RenderOptions(new BasicCapabilities(), consoleSize);
}
public void Clear(bool home)
{
_textBox.Document.Blocks.Clear();
}
public void Write(IRenderable element)
{
_textBox.Document.LineHeight = 1;
foreach (var segment in element.Render(_options, _options.ConsoleSize.Width))
{
var style = segment.Style;
AppendText(_textBox, segment.Text,
BrushFromSpectreColor(style.Foreground, _textBox.Foreground),
BrushFromSpectreColor(style.Background, _textBox.Background)
//TODO - could take account of text styling here
);
}
}
private static Brush BrushFromSpectreColor(Spectre.Console.Color col, Brush fallback)
=> col == Spectre.Console.Color.Default ? fallback : new SolidColorBrush(Color.FromRgb(col.R, col.G, col.B));
private static void AppendText(RichTextBox box, string text, Brush foreground, Brush background)
{
var tr = new TextRange(box.Document.ContentEnd, box.Document.ContentEnd)
{
Text = text
};
tr.ApplyPropertyValue(TextElement.ForegroundProperty, foreground);
tr.ApplyPropertyValue(TextElement.BackgroundProperty, background);
}
} Usage.: private void TestButtonClicked(object sender, RoutedEventArgs e)
{
var c = new WpfRenderer(theRichTextBox);
c.Clear(true);
c.Write(new Markup("[green]Here's some basic text\r\n[/]"));
c.Write(new Markup("Here's some styled text\r\n",
new Style(Spectre.Console.Color.White , Spectre.Console.Color.Blue)));
var table = new Table();
table.AddColumn("First");
table.AddColumn("Second");
table.Rows.Add([new Markup("[green]good data[/]"), new Markup("[red]bad data[/]")]);
c.Write(table);
} Display: |
Beta Was this translation helpful? Give feedback.
-
I did get an IAnsiConsole implementation working which has the advantage that all the nice interface IRenderTarget {
RenderOptions Options {get;}
void Clear(bool home);
void Write(Segment segment);
void Write(IRenderable renderable) {foreach (var segment in renderable) Write(segment);}
} IAnsiConsole could then even inherit from this. The "basic" IAnsiConsole extensions could then be retargeted to this interface? Anyway, as I say quite possibly I'm missing something but exposing this layer would make it easier to implement different target surfaces. |
Beta Was this translation helpful? Give feedback.
-
Sorry, I don't really understand what that interface would do that IAnsiConsole isn't already doing. |
Beta Was this translation helpful? Give feedback.
-
I have an application that exists in both a "pure" CLI form and a more capable UI version that contains an "embedded" console which is actually just a WPF TextBox. In the future I might want to also create a Blazor version where the "console" is some kind of web control.
I currently solve this by creating a simple abstraction layer around System.Console. This is fine for basic text I/O with some coloring but since one of the main functions of the app is to output tabular data it would be really nice to use the Spectre rendering system.
So what I'm wondering is whether it's practical to create an IAnsiConsole implementation that sits on top of a WPF TextBox or similar control. I took a quick look at the interface and it seems to depend on a number of fairly complex types so it's a bit unclear what the minimum possible implementation would be and whether I'd have to provide (for example) some mapping system between VT100-style escape sequences and color-control commands for my target or whether these can be intercepted at some higher logical layer.
Anyway, would be interested to hear from anyone who's tried this or managed some kind of implementation... :-)
Beta Was this translation helpful? Give feedback.
All reactions