Console.Lib
1.4.91
See the version list below for details.
dotnet add package Console.Lib --version 1.4.91
NuGet\Install-Package Console.Lib -Version 1.4.91
<PackageReference Include="Console.Lib" Version="1.4.91" />
<PackageVersion Include="Console.Lib" Version="1.4.91" />
<PackageReference Include="Console.Lib" />
paket add Console.Lib --version 1.4.91
#r "nuget: Console.Lib, 1.4.91"
#:package Console.Lib@1.4.91
#addin nuget:?package=Console.Lib&version=1.4.91
#tool nuget:?package=Console.Lib&version=1.4.91
Console.Lib
A .NET library for building terminal applications with dock-based layouts, widgets, mouse/keyboard input, VT styling, and Sixel graphics rendering. AOT-compatible, targeting .NET 10.
Architecture overview
classDiagram
direction TB
class ITerminalViewport {
<<interface>>
}
class IVirtualTerminal {
<<interface>>
}
class VirtualTerminal
class TerminalViewport
class TerminalLayout {
+Dock(style, size) TerminalViewport
+Recompute() bool
}
class Panel {
+Dock(style, size) ITerminalViewport
+Fill() ITerminalViewport
+Add(widget) Panel
+RenderAll()
+Recompute() bool
}
class Widget {
<<abstract>>
+Render()*
+HitTest(pixelX, pixelY)
}
class TextBar
class ScrollableList~TItem~
class Canvas~TSurface~ {
+Render()*
+Render(clip)
}
class IRowFormatter {
<<interface>>
}
class Renderer~TSurface~ {
<<abstract>>
+Surface TSurface
}
class SixelRenderer~TSurface~ {
<<abstract>>
+EncodeSixel(output)*
}
class SixelEncoder {
+Encode()$
}
class MenuBase~T~ {
<<abstract>>
}
class VtStyle {
<<record struct>>
+Apply(colorMode) string
}
class ColorMode {
<<enum>>
}
class ConsoleInputEvent {
<<record struct>>
}
IVirtualTerminal --|> ITerminalViewport
VirtualTerminal ..|> IVirtualTerminal
TerminalViewport ..|> ITerminalViewport
TerminalViewport --> ITerminalViewport : parent
TerminalLayout --> TerminalViewport : creates
Panel --> TerminalLayout : uses
Widget --> ITerminalViewport : viewport
TextBar --|> Widget
ScrollableList --|> Widget
Canvas --|> Widget
ScrollableList ..> IRowFormatter : TItem
Panel --> Widget : children
SixelRenderer --|> Renderer
Canvas ..> SixelRenderer : Render
VtStyle --> ColorMode : Apply
MenuBase --> IVirtualTerminal : terminal
VirtualTerminal --> ConsoleInputEvent : produces
Terminal abstraction
ITerminalViewport
The core output interface. Represents a rectangular region that supports cursor positioning, text output, and stream access:
public interface ITerminalViewport
{
(int Column, int Row) Offset { get; }
(int Width, int Height) Size { get; }
void SetCursorPosition(int left, int top);
void Write(string text);
void WriteLine(string? text = null);
TermCell CellSize { get; }
(uint Width, uint Height) PixelSize { get; } // default: Size * CellSize
void Flush();
Stream OutputStream { get; }
ColorMode ColorMode => ColorMode.Sgr16; // default: 16-color SGR
}
TermCell holds the pixel dimensions of a single terminal character cell, queried from the terminal during initialization via the \e[16t control sequence.
TerminalViewportExtensions
Extension methods for ITerminalViewport:
// Overwrite the current line in-place using \r, padding with spaces to erase stale content.
// Does not advance to the next line — ideal for status prompts and progress indicators.
terminal.WriteInPlace("> waiting...");
IVirtualTerminal
Extends ITerminalViewport with full terminal lifecycle: initialization, input reading, alternate screen buffer, and Sixel capability detection.
public interface IVirtualTerminal : ITerminalViewport, IAsyncDisposable
{
Task InitAsync();
bool HasSixelSupport { get; }
bool HasColorSupport { get; }
void EnterAlternateScreen();
bool IsAlternateScreen { get; }
void Clear();
bool HasInput();
ConsoleInputEvent TryReadInput();
}
VirtualTerminal is the concrete implementation backed by System.Console. On initialization it:
- Sets UTF-8 encoding for stdin/stdout
- Sends a Device Attributes request (
\e[0c) to detect terminal capabilities (including Sixel support) - Sends a cell size query (
\e[16t) to determine pixel dimensions per character cell
When entering the alternate screen, it enables virtual terminal I/O and mouse input via WindowsConsoleInput (Windows only), then enables VT200 mouse tracking with SGR extended coordinates (\e[?1000h, \e[?1006h), parses SGR mouse events from raw stdin, and normalizes cell coordinates to pixel coordinates using the cell size.
In normal (non-alternate) screen mode, TryReadInput() uses Console.ReadKey(intercept: true) — keystrokes are not echoed, giving the caller full control over display feedback.
TerminalViewport
A sub-region of a parent viewport. Translates local coordinates to parent coordinates by adding column/row offsets. Clamps cursor positions to stay within bounds. Viewports can be nested — offsets compose through the parent chain.
var terminal = new VirtualTerminal();
// Create a 30x15 viewport starting at column 10, row 5
var viewport = new TerminalViewport(terminal, 10, 5, 30, 15);
viewport.SetCursorPosition(0, 0); // → terminal position (10, 5)
viewport.SetCursorPosition(3, 7); // → terminal position (13, 12)
Layout system
DockStyle
public enum DockStyle { Top, Bottom, Left, Right, Fill }
TerminalLayout
Computes viewport geometries using a dock-based algorithm. Edge-docked panels are allocated first in registration order, each consuming space from the remaining rectangle. The Fill panel receives whatever space remains.
var layout = new TerminalLayout(terminal);
var statusBar = layout.Dock(DockStyle.Bottom, 1); // 1 row at bottom
var sidebar = layout.Dock(DockStyle.Right, 24); // 24 columns on right
var main = layout.Dock(DockStyle.Fill); // remainder
For an 80x24 terminal, this produces:
statusBar: 80x1 at (0, 23)sidebar: 24x23 at (56, 0)main: 56x23 at (0, 0)
Recompute() recalculates all geometries after a terminal resize, returning true if the size actually changed.
Panel
Higher-level container that wraps TerminalLayout and manages a collection of widgets:
var panel = new Panel(terminal);
var statusBar = new TextBar(panel.Dock(DockStyle.Bottom, 1));
var history = new ScrollableList<MyRow>(panel.Dock(DockStyle.Right, 24));
var canvas = new Canvas(panel.Fill());
panel.Add(statusBar).Add(history).Add(canvas);
panel.RenderAll(); // renders all widgets
The two-step pattern (dock creates the viewport, then pass it to a widget constructor) keeps viewport ownership clear — each widget owns exactly one viewport from construction.
Widgets
All widgets inherit from Widget, which provides:
Viewport— theITerminalViewportthis widget renders toRender()— abstract method to draw the widget's current stateHitTest(pixelX, pixelY)— converts absolute pixel coordinates to viewport-local cell coordinates, returningnullif outside bounds
TextBar
Single-line status bar with left-aligned and right-aligned text, styled with VtStyle:
var bar = new TextBar(viewport);
bar.Text(" Ready")
.RightText("12.3ms ")
.Style(new VtStyle(SgrColor.BrightWhite, SgrColor.BrightBlack))
.Render();
ScrollableList<TItem>
Multi-row scrollable list with an optional header. Items must implement IRowFormatter:
public interface IRowFormatter
{
string FormatRow(int width, ColorMode colorMode);
}
Each item produces its own styled row string (including VT escape codes and padding to full width). The list handles scrolling and empty-row rendering:
var list = new ScrollableList<MyRow>(viewport)
.Header(" Items")
.HeaderStyle(new VtStyle(SgrColor.BrightWhite, SgrColor.BrightBlack))
.Items(rows)
.ScrollTo(startOffset)
.Render();
int visibleRows = list.VisibleRows; // data rows (excludes header)
Canvas<TSurface>
A generic widget that owns a SixelRenderer<TSurface> and renders it to a viewport. Provides full and partial Sixel output:
var canvas = new Canvas<MagickImage>(viewport, renderer);
var (pixelW, pixelH) = canvas.PixelSize;
canvas.Render(); // full Sixel blit
canvas.Render(clip); // partial blit for dirty region (pixel coordinates)
Render() positions the cursor at (0, 0) and calls renderer.EncodeSixel(stream). Render(RectInt clip) aligns the clip region's Y bounds to cell-height boundaries (since Sixel output must start at a character row), then calls renderer.EncodeSixel(startY, cropHeight, stream). Only vertical clipping is performed — the full image width is always emitted, since the Sixel protocol is a left-to-right band-based format with no horizontal skip.
Sixel graphics
SixelRenderer<TSurface>
Abstract class extending Renderer<TSurface> (from DIR.Lib) with Sixel encoding:
public abstract class SixelRenderer<TSurface>(TSurface surface) : Renderer<TSurface>(surface)
{
public abstract void EncodeSixel(Stream output);
public abstract void EncodeSixel(int startY, uint height, Stream output);
}
Concrete implementations (e.g., MagickImageRenderer in Chess.ImageMagick) provide the actual pixel-to-Sixel encoding by extracting raw pixel data and passing it to SixelEncoder.
SixelEncoder
High-performance encoder that converts raw pixel arrays to the Sixel terminal graphics format. Key design decisions:
- Frequency-based palette: When more than 256 unique colors exist, the most frequent colors get exact palette slots (preserving large solid areas like board tiles). Remaining colors map to their nearest palette entry.
- Precomputed sixel grid: A single row-major pass builds sixel bits for all colors simultaneously, then each color encodes from a contiguous memory slice. This is cache-friendly and avoids the naive O(colors × rows × width) approach.
- ArrayPool allocation: All large buffers (index map, sixel grid, palette, output buffer) are rented from
ArrayPool<byte>.Shared, eliminating GC pressure from repeated allocations. - Partial encoding: Supports vertical slicing without image cloning — the caller extracts the pixel slice and passes the cropped dimensions.
Performance vs ImageMagick's built-in Sixel writer:
| Scenario | ImageMagick | SixelEncoder | Speedup |
|---|---|---|---|
| Full | 127.3 ms | 9.1 ms | 14× |
| Partial | 127.9 ms | 1.6 ms | 79× |
Styling
VtStyle, SgrColor, and ColorMode
VtStyle stores foreground/background as RGBAColor32 (from DIR.Lib) and produces escape sequences via Apply(ColorMode):
public enum ColorMode : byte { None, Sgr16, TrueColor }
public readonly record struct VtStyle(RGBAColor32 Foreground, RGBAColor32 Background)
{
public const string Reset = "\e[0m";
public VtStyle(SgrColor foreground, SgrColor background); // convenience
public string Apply(ColorMode colorMode);
}
Apply(ColorMode.Sgr16) emits standard 16-color SGR codes (\e[97;40m). Apply(ColorMode.TrueColor) emits 24-bit sequences (\e[38;2;R;G;B;48;2;R;G;Bm). ToString() defaults to Sgr16 for safe fallback.
The 16 standard SgrColor values have well-known RGB mappings via SgrColor.ToRgba(). Arbitrary RGBAColor32 values are mapped back to the nearest SgrColor when using Sgr16 mode.
// Construct with SgrColor (convenience) or RGBAColor32 (full control)
var style = new VtStyle(SgrColor.BrightYellow, SgrColor.Blue);
var custom = new VtStyle(new RGBAColor32(0x1a, 0x1a, 0x2e, 0xff), new RGBAColor32(0xe0, 0xe0, 0xe0, 0xff));
// Widgets use Apply with the viewport's color mode
terminal.Write($"{style.Apply(terminal.ColorMode)}Highlighted text{VtStyle.Reset}");
ColorMode flows through the viewport chain: VirtualTerminal returns TrueColor when HasColorSupport is true (DA capability code 22), TerminalViewport delegates to its parent, and ITerminalViewport defaults to Sgr16. ColorMode.None suppresses all escape sequences for plain-text output.
Markdown rendering
MarkdownRenderer converts Markdown to VT-styled terminal output using Markdig. Supports headings, bold, italic, links, tables, lists, horizontal rules, and inline colored text.
Colors can be applied to individual words using [text]{color} syntax, where color is either a named SgrColor (e.g. red, BrightCyan) or a #RRGGBB hex literal:
This has a [warning]{red} and a [custom tint]{#FF8800}.
Colors are resolved at render time based on the active ColorMode — in None mode, no escape sequences are emitted. Structural element colors (headings, links, bullets) are configurable via MarkdownTheme.
MarkdownWidget wraps the renderer as a scrollable viewport widget with automatic re-rendering on resize.
Input handling
ConsoleInputEvent
A unified input event that may contain a mouse event, a key press, or both:
public readonly record struct ConsoleInputEvent(MouseEvent? Mouse, ConsoleKey Key, ConsoleModifiers Modifiers);
public readonly record struct MouseEvent(int Button, int X, int Y, bool IsRelease);
Mouse coordinates are in pixels (normalized using TermCell dimensions). Button encoding follows the X11/SGR convention: 0 = left, 1 = middle, 2 = right, 64/65 = scroll up/down.
VirtualTerminal.TryReadInput() parses SGR mouse sequences (\e[<Pb;Px;Py M/m) in alternate screen mode, and falls back to Console.ReadKey in normal mode. It also parses CSI sequences for arrow keys, function keys, Home/End, Delete, PageUp/PageDown, and SS3 sequences for F1-F4.
Menus
MenuBase<T>
Abstract base for fullscreen menus with arrow-key navigation, digit shortcuts, and mouse click support. In alternate screen mode, renders a centered menu with resize handling. In normal mode, falls back to a simple numbered list.
public class MyMenu(IVirtualTerminal terminal, TimeProvider timeProvider)
: MenuBase<string>(terminal, timeProvider)
{
protected override async Task<string> ShowAsyncCore(CancellationToken ct)
{
var choice = await ShowMenuAsync("Title", "Pick one:", ["A", "B", "C"], ct);
return choice switch { 0 => "A", 1 => "B", _ => "C" };
}
}
Platform support
- Windows:
WindowsConsoleInputenables virtual terminal I/O and mouse tracking via Win32SetConsoleMode. Restores original console mode on dispose. - Unix/macOS: VT100 escape sequences work natively. Mouse tracking uses the same SGR extended format.
Terminal capability detection
During InitAsync(), VirtualTerminal sends a Primary Device Attributes request (\e[0c). The response contains capability codes parsed into the TerminalCapability enum:
| Code | Capability | Effect |
|---|---|---|
| 4 | Sixel graphics | Enables HasSixelSupport |
| 22 | Color | Enables HasColorSupport, sets ColorMode to TrueColor |
| 18 | Windowing | — |
| 1 | 132 columns | — |
Unknown capability codes are silently ignored.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.