Biblioteca de clases para C# / .NET 10 para construir TUIs (Terminal User Interfaces) interactivas, estéticas y fluidas.
TermFlow.Net te permite construir desde simples barras de progreso hasta aplicaciones full-screen con menús, exploradores de archivos, paneles de logs en vivo y consolas estilo chat — todo con soporte nativo para secuencias ANSI, teclado, rueda del mouse y redimensionamiento automático.
- TermFlow.Net
| Requisito | Detalle |
|---|---|
| .NET SDK | 10.0+ |
| Sistema operativo | Windows 10/11 o Linux |
| Terminal | Cualquiera con soporte ANSI/VT100 |
| Fuente (recomendada) | Una fuente monoespaciada con buena cobertura Unicode |
ℹ️ En Windows,
Engine.Setup()activa automáticamenteENABLE_VIRTUAL_TERMINAL_PROCESSINGvía P/Invoke para garantizar el render ANSI, incluso en terminales que no lo habilitan por defecto.
⚠️ Fuente: los glifos usados (▶ ✔ ✖ ⚠ ● ┌ ┐ └ ┘) son Unicode estándar, no Nerd Font.consolas(por defecto en cmd/PowerShell de Windows 10) no los renderiza. Usá Cascadia Code, JetBrains Mono, Fira Code o cualquier monoespaciada con buena cobertura Unicode. La app funciona igual aunque algunos caracteres se vean mal.
Actualmente TermFlow.Net se distribuye como código fuente. Para usarla en tu proyecto:
# 1. Cloná el repositorio
git clone https://github.com/1R1an1/TermFlow.Net.git
# 2. Compilá en Release
cd TermFlow.Net
dotnet build -c Release
# 3. Referenciala desde tu proyecto
dotnet add reference ../TermFlow.Net/TermFlow.Net.csprojAlternativamente, podés copiar las carpetas Core/ y Components/ directamente a tu solución.
using TermFlow.Components.InLine;
using TermFlow.Components.FullScreen;
using TermFlow.Core;
// 1. Inicializá el motor (UTF-8 + ANSI + hooks de cierre)
Engine.Setup();
// 2. Encabezado y mensaje informativo
TextViewer.WriteHeader("Mi Aplicación");
TextViewer.Info("Iniciando proceso...");
// 3. Barra de progreso con ETA y velocidad automáticos
await ProgressBarDisplay.RunAsync("Descargando", maxValue: 100, async (p) =>
{
for (int i = 0; i <= 100; i += 5)
{
p.Value = i;
await Task.Delay(50);
}
});
// 4. Menú de selección única full-screen
string[] items = { "Opción A", "Opción B", "Opción C" };
int elegido = await Menu.SelectOneAsync("Elegí una opción", items);
TextViewer.Success($"Elegiste: {items[elegido]}");El repositorio incluye un proyecto de demostración interactivo (TermFlow.Demo/) con 14 demos que cubren todos los componentes de la biblioteca. Útil para ver TermFlow.Net en acción antes de integrarlo a tu proyecto.
# Desde la raíz del repo
dotnet run --project TermFlow.Demo/TermFlow.Demo.csprojAparece un menú full-screen desde donde podés elegir qué componente probar (TextViewer, Menu, SearchList, TreeExplorer, LivePanel, LiveConsole, etc.). Cada demo muestra su resultado inline y vuelve al menú al presionar Enter.
TermFlow.Net se organiza en cuatro capas separadas:
┌─────────────────────────────────────────────────┐
│ Components (UI) │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ InLine │ │ FullScreen │ │
│ │ (fluido) │ │ (alternate screen buf) │ │
│ └──────────────┘ └─────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Base │
│ TermCanvas · CanvasExt · LineEdit · │
│ InputRouter │
├─────────────────────────────────────────────────┤
│ Core (Motor) │
│ Engine · InputReader · ScrollState │
│ AnsiColor · AnsiStringHelper │
│ ThemeColors · ConsoleGlyphs │
└─────────────────────────────────────────────────┘
- Core: primitivas de bajo nivel (ANSI, lectura de input, matemática de scroll, helpers de strings con ANSI, tema central).
- Base: logica reutilizable construida sobre
Core(canvas virtual, editor de línea, enrutador de teclas, etc). - Components/InLine: componentes que se imprimen en el flujo normal de la consola, sin tomar el control total.
- Components/FullScreen: componentes que entran al alternate buffer y toman control de toda la pantalla.
Todos los componentes InLine detectan automáticamente si
LivePanelestá activo y redirigen su salida al panel en lugar de imprimir directamente.
Componentes que se integran al flujo normal de la consola. Todos son compatibles con LivePanel: si el panel está activo, redirigen su salida automáticamente.
Mensajes con estilo semántico, encabezados simples y figlet ASCII.
TextViewer.Info("Procesando..."); // ● Procesando...
TextViewer.Success("OK"); // ✔ OK
TextViewer.Warn("Atención"); // ⚠ Atención
TextViewer.Error("Falló"); // ✖ Falló
TextViewer.WriteHeader("Sección 1"); // Subrayado con ───Entrada de texto (con soporte opcional de ocultar el texto), preguntas sí/no y "presionar para continuar".
string nombre = await TextInput.ReadStringAsync("Nombre: ");
string password = await TextInput.ReadStringAsync("Token: ", isPassword: true);
bool confirmar = await TextInput.AskAsync("¿Continuar?");
TextInput.PressToContinue();Con
isPassword: true, el texto ingresado se enmascara con*en pantalla. El contenido real se devuelve igual, solo cambia cómo se muestra.
Tablas auto-ajustables con bordes Unicode. El ancho de cada columna se calcula a partir del contenido.
TableView.Show(
headers: new[] { "ID", "Servidor", "IP", "Estado" },
rows: new List<string[]>
{
new[] { "001", "Hub", "127.0.0.1:8080", "ONLINE" },
new[] { "002", "Backup", "192.168.1.50:9000", "OFFLINE" }
}
);Spinner animado Braille mientras corre una tarea de fondo.
await SpinnerDisplay.RunAsync("Instalando dependencias...", async (token) =>
{
await Task.Delay(3000, token);
});Barra de progreso con cálculo automático de velocidad (B/s, KB/s, MB/s) y ETA.
await ProgressBarDisplay.RunAsync(
description: "Descargando ISO",
maxValue: 1024L * 1024 * 500,
workerTask: async (p) =>
{
for (long i = 0; i <= 1024L * 1024 * 500; i += 1024 * 100)
{
p.Value = i;
await Task.Delay(10);
}
},
showSpeed: true
);Toman el control total de la pantalla usando el alternate buffer (no rompen el historial previo de la consola). Al salir, restauran el estado original.
Menú de selección única o múltiple con scroll y navegación estilo Vim.
string[] items = ["Ana", "Bruno", "Carla"];
// Selección única
int idx = await Menu.SelectOneAsync("Clientes", items);
// Selección múltiple con pre-selección
int[] elegidos = await Menu.SelectMultiAsync("Permisos", items,
preselected: [true, false, true]);Buscador en vivo con selección única o múltiple.
int idx = await SearchList.FilterOneAsync("Buscar cliente", clientes);
int[] idxs = await SearchList.FilterMultiAsync("Etiquetas", tags);Explorador jerárquico con soporte para directorios físicos o estructuras virtuales. Soporta filtros (todo / solo carpetas / solo archivos), DeniedPaths (rutas bloqueadas), HiddenPaths (rutas ocultas), MinDepth (profundidad mínima) y IgnoreSymlinks, todos configurables vía ExplorerOptions. La selección múltiple tiene herencia de marcas (marcar una carpeta marca todos sus hijos).
// Exploración física
string archivo = await TreeExplorer.ExploreOneAsync("Abrir archivo", @"/home/user/docs");
string[] archivos = await TreeExplorer.ExploreMultiAsync("Seleccionar logs",
@"/var/log", new ExplorerOptions { Filter = ExplorerFilter.OnlyFiles });
// Exploración virtual (rutas en memoria)
string[] virtuales = await TreeExplorer.ExploreMultiAsync(
"Seleccionar nodos",
new[] { "s3/bucket1", "s3/bucket2", "ec2/i-123" },
virtualRoot: "aws"
);
// Origen de datos personalizado (implementando IExplorerDataSource)
string[] custom = await TreeExplorer.ExploreMultiAsync(
"Mi origen", miDataSource, new ExplorerOptions { Filter = ExplorerFilter.All });
// Con opciones avanzadas
var opts = new ExplorerOptions {
Filter = ExplorerFilter.OnlyFiles,
DeniedPaths = ["/var/log/secure"],
HiddenPaths = ["/var/log/.hidden"],
MinDepth = 1,
IgnoreSymlinks = true
};
string[] filtrados = await TreeExplorer.ExploreMultiAsync("Logs accesibles", "/var/log", opts);Panel de logs dinámico a pantalla completa. Combina logs estáticos con líneas dinámicas actualizables (spinners, barras de progreso, etc.). Usa caché de wrapping y un loop de render reactivo accionado por semáforo para minimizar CPU.
LivePanel.Start(maxLogs: 500);
long id = LivePanel.AddDynamic("Procesando...");
// ... más tarde, actualizar la línea dinámica:
LivePanel.UpdateLine(id, "Procesando... 50%");
LivePanel.UpdateDecorations(id, prefix: AnsiColor.Dim, suffix: ThemeColors.Reset);
TextViewer.Success("Tarea completada");
LivePanel.Stop();Consola interactiva estilo chat con historial scrollable, barra divisoria inteligente que avisa cuando hay mensajes nuevos abajo. Puede detenerse programáticamente con Stop() (equivalente a presionar Escape o escribir /exit).
var console = new LiveConsole();
ThemeColors.Primary = AnsiColor.Cyan + AnsiColor.Bold;
await console.RunAsync(">>> ", async (input) =>
{
if (input == "/help")
console.WriteLog($"{ThemeColors.Bright}Comandos{ThemeColors.Reset}: /help, /exit");
else
console.WriteLog($"{ThemeColors.Primary}Echo{ThemeColors.Reset}: {input}");
});
// Salir con /exit, Escape o console.Stop() desde otro hilo| Clase | Responsabilidad |
|---|---|
Engine |
Inicializa UTF-8, ANSI nativo en Windows, y gestiona la entrada/salida del alternate buffer. |
InputReader |
Lector de bajo nivel: decodifica teclas comunes y secuencias ANSI SGR del mouse (scroll up/down), filtrando clicks fantasma. |
ScrollState |
Matemática de cursor + ventana de scroll, con detección automática de resize. |
AnsiColor |
Wrapper tipado para secuencias ANSI. Soporta composición con + y conversión implícita a string. |
AnsiStringHelper |
Extensiones para envolver, truncar y medir strings respetando códigos ANSI. |
ThemeColors |
Paleta semántica central (Success, Warning, Error, Info, etc.). Modificable en runtime. |
ConsoleGlyphs |
Catálogo de glifos Unicode (┌ ┐ └ ┘ ─ │ ✔ ⚠ ● ▶). Modificable en runtime. |
Capa intermedia entre Core y Components. Piezas reutilizables construidas sobre las primitivas de Core.
Motor de renderizado intermedio (Canvas Virtual). Mantiene un mapa de la pantalla en memoria con double buffer y dirty tracking diferencial: en cada Flush() solo se emiten las celdas que cambiaron respecto al frame anterior, generando un único string ANSI optimizado para un solo Console.Write. Thread-safe con redimensionamiento automático opcional vía hilo de monitoreo. Utilizado por todos los componentes fullscreen.
using var canvas = new TermCanvas(automaticResize: true, resizeCanvas: false, resizeIntervalms: 100,
onResize: (c, _) => { /* re-renderizar */ });
canvas.WriteAt(2, 1, "Hola", ThemeColors.Primary);
canvas.ClearLine(3);
canvas.Flush();Extensiones de método sobre TermCanvas para primitivas visuales comunes: bordes, encabezados con subrayado automático, listas con selección y limpieza de filas sobrantes, escritura con limpieza del resto de la línea, etc. Usadas internamente por todos los componentes FullScreen.
canvas.DrawBorder(5, 5, 50, 20, color: ThemeColors.Dim);
canvas.WriteHeader(2, 1, "Título", lineColor: ThemeColors.Dim);
canvas.DrawList(items, 2, 4, visibleRows, scroll, formatter: (item, i) => /* ... */);
canvas.WriteAtAndClear(2, 3, "texto que se corta limpio al final");Lógica de editor de línea: buffer de texto, cursor, navegación con flechas, Ctrl+Flechas (salto de palabras), Inicio/Fin, Backspace/Delete. Utilizado por TextInput, LiveConsole y SearchList.
Enrutador fluent de teclas a acciones. Permite registrar binds (Bind, BindConfirm, BindCancel, BindNavigate, BindScroll, BindSelect, BindChar, BindUnhandled) y genera automáticamente un footer contextual con los atajos activos.
var router = new InputRouter()
.BindConfirm(() => { /* Enter */ })
.BindCancel(() => { /* Esc/q */ })
.BindNavigate(() => cursor--, () => cursor++)
.Bind("g/G", "extremos", () => cursor = 0, 'g')
.BindChar("", "", () => cursor = items.Length - 1, 'G');Toda la estética se controla desde dos clases estáticas centralizadas, modificables en runtime:
// Cambiar la paleta semántica
ThemeColors.Primary = AnsiColor.BrightCyan + AnsiColor.Bold;
ThemeColors.Success = AnsiColor.BrightGreen;
ThemeColors.Selector = AnsiColor.BrightYellow;
ThemeColors.Warning = AnsiColor.BrightRed;
// Cambiar los glifos usados por todos los componentes
ConsoleGlyphs.Indicator = "→";
ConsoleGlyphs.Checked = "✓";
ConsoleGlyphs.Unchecked = "□";
ConsoleGlyphs.Horizontal = '━';
ConsoleGlyphs.TopLeft = '┏';Componer estilos ANSI es seguro y simple gracias al operador +:
AnsiColor miEstilo = AnsiColor.BgBlue + AnsiColor.BrightWhite + AnsiColor.Bold;
Console.Write($"{miEstilo}Texto{ThemeColors.Reset}");Las extensiones de
AnsiStringHelper(GetVisualLength,WrapText,Truncate,StripAnsi) permiten medir y recortar texto con ANSI sin romper los códigos de color.
TermFlow.Net/
├── TermFlow.Net.csproj
├── Core/
│ ├── Engine.cs # Setup ANSI/UTF-8 + alternate buffer
│ ├── InputReader.cs # Decoder de teclas + mouse SGR
│ ├── ScrollState.cs # Matemática de scroll
│ ├── AnsiColor.cs # Wrapper de secuencias ANSI
│ ├── AnsiStringHelper.cs # Wrap/truncate/medida con ANSI
│ ├── ThemeColors.cs # Paleta semántica
│ └── ConsoleGlyphs.cs # Glifos Unicode
├── Base/
│ ├── TermCanvas.cs # Canvas Virtual con Dirty Tracking + Double Buffer
│ ├── CanvasExt.cs # Extensiones: DrawBorder, WriteHeader, DrawList, etc.
│ ├── LineEdit.cs # Logica de editor de línea (buffer + cursor)
│ └── InputRouter.cs # Binds fluent + footer contextual
├── Components/
│ ├── InLine/
│ │ ├── TextViewer.cs # Info/Success/Warn/Error/Figlet
│ │ ├── TextInput.cs # ReadString/Ask/PressToContinue
│ │ ├── TableView.cs # Tabla con bordes
│ │ ├── SpinnerDisplay.cs # Spinner Braille async
│ │ └── ProgressBarDisplay.cs # Barra con ETA + velocidad
│ └── FullScreen/
│ ├── Menu.cs # SelectOne / SelectMulti
│ ├── SearchList.cs # FilterOne / FilterMulti
│ ├── LivePanel.cs # Panel de logs dinámico
│ ├── LiveConsole.cs # Consola estilo chat
│ └── TreeExplorer/
│ ├── TreeExplorer.cs # Motor central
│ ├── TreeExplorerModel.cs # Interfaces + ExplorerEntry
│ ├── TreeExplorer.PhysicalDataSource.cs # Implementación sobre FS
│ └── TreeExplorer.VirtualDataSource.cs # Implementación in-memory
└── TermFlow.Demo/ # Proyecto de demo interactivo (no es parte de la biblioteca)
├── TermFlow.Demo.csproj # Referencia al csproj principal
├── TermCanvasDemo.cs # Demo para testear TermCanvas
└── Program.cs # Menú de tests de todos los componentes
TermFlow.Net usa secuencias ANSI estándar. Probado en:
| Terminal | Estado |
|---|---|
| Windows Terminal (Win 10/11) | ✅ Recomendado en Windows |
| cmd.exe y powershell (Windows 10+) | consolas por defecto algunos glifos se ven como ? |
| Kitty | ✅ |
| GNOME Terminal | ✅ |
| Alacritty | ✅ |
| cmd.exe (Windows 7-) |
⚠️ Fuente: los glifos usados (▶ ✔ ✖ ⚠ ● ┌ ┐ └ ┘ ─ │) son Unicode estándar, no Nerd Font. Cambiá la fuente de la terminal a Cascadia Code, JetBrains Mono, Fira Code o cualquier monoespaciada con buena cobertura Unicode. Ver Requisitos para más detalle.
⚠️ La captura del mouse usa el modo SGR (1006). Algunos terminales muy antiguos pueden no soportarlo correctamente; en ese caso los clicks podrían no registrarse pero el teclado seguirá funcionando.
Toda la API pública y privada (clases, métodos, parámetros y excepciones) está documentada con comentarios XML de C# /// <summary>, disponible en el IntelliSense (VS Code, Visual Studio y Rider) al usar la librería. La documentación está en español.
Este proyecto está bajo la licencia Mozilla Public License 2.0 (MPL-2.0).
SPDX-License-Identifier: MPL-2.0
Copyright (c) 2026 1R1an1
Ver LICENSE para el texto completo.