Files
fn_registry/cpp/functions/viz/terminal_panel/terminal_panel.h
T
egutierrez 07252c0172 feat(0132): cpp terminal_panel module + ansi_parser
Nuevo modulo reutilizable terminal_panel (fn_term) para ImGui:

Sub-fn ansi_parser_cpp_core (cpp/functions/core/):
- Parser ANSI/VT100 byte-a-byte sin heap allocs por evento
- SGR colores FG/BG 16-color + bold + reset
- Cursor moves CUU/CUD/CUF/CUB + CUP absoluto
- Erase ED(2)/EL(2), CR/LF/BS
- Statemachine 4 estados, thread-unsafe por diseno
- 21 tests unitarios (57 assertions), todos pasan

terminal_panel_cpp_viz (cpp/functions/viz/terminal_panel/):
- terminal_panel.cpp: render ImGui + process_output con list clipper
- terminal_panel_linux.cpp: forkpty + reader thread no-blocking
- terminal_panel_windows.cpp: ConPTY CreatePseudoConsole (SDK >= 17763)
- Scrollback circular configurable (default 5000 lineas)
- Toolbar: clear, copy, reset, scroll-lock + status indicator
- readonly mode: sin input box, send() es no-op
- uses_functions: ansi_parser_cpp_core, logger_cpp_core

Tests:
- test_ansi_parser.cpp: 21 test cases, 57 assertions (PASS)
- test_terminal_panel_smoke.cpp: 3 test cases (PASS: spawn echo hello,
  process exits cleanly, readonly ignores send)

CMake:
- cpp/tests/CMakeLists.txt: add test_ansi_parser + test_terminal_panel_smoke
- primitives_gallery (sub-repo): ver commit separado en apps/primitives_gallery

Pendiente (anti-scope v1):
- Windows ConPTY: stub funcional que compila; join() del reader thread
  via std::thread no implementado (usa CreateThread detached)
- ANSI 256/24-bit color, italics, Unicode wide
- Curses pesados (vim, htop, top) — cursor visible basic solo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 23:35:11 +02:00

112 lines
4.1 KiB
C++

#pragma once
// terminal_panel — emulador TTY embebible en ImGui.
//
// Arranca un proceso hijo via PTY (Linux: forkpty) o ConPTY (Windows) y
// renderiza su output en un child window ImGui con soporte basico de ANSI:
// colores FG/BG 16-color, bold, cursor pos, clear screen/line.
//
// Uso basico:
// static fn_term::TerminalPanel term;
// term.shell = "/bin/bash";
//
// if (!term.is_open()) fn_term::open(term);
// fn_term::render(term);
// if (!term.readonly) fn_term::send(term, "ls\n");
// // Al cerrar:
// fn_term::close(term);
//
// Thread-safety: open/render/send/close deben llamarse desde el hilo ImGui.
// El reader thread interno es gestionado por la implementacion.
//
// Plataformas:
// Linux/macOS: terminal_panel_linux.cpp (forkpty + read no-blocking en thread)
// Windows: terminal_panel_windows.cpp (ConPTY CreatePseudoConsole)
#include "core/ansi_parser.h"
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
namespace fn_term {
// Una linea del scrollback: vector de celdas ya parseadas.
using TermLine = std::vector<AnsiCell>;
// Configuracion y estado del panel.
struct TerminalPanel {
// --- Config (set antes de open(), no cambiar en vivo) ---
std::string shell; // "" → auto-detect (/bin/bash linux, cmd.exe windows)
std::string cwd; // "" → directorio actual del proceso padre
std::vector<std::string> env; // KEY=VAL adicionales al entorno heredado
int scrollback_lines = 5000; // max filas en el ring buffer
bool readonly = false; // si true, no reenvía input del teclado
// --- Estado interno (gestionado por open/close/render) ---
// No modificar directamente.
// Proceso hijo
int child_pid = -1; // Linux: PID del hijo; -1 si no abierto
int master_fd = -1; // Linux: fd del extremo master del PTY
void* proc_handle = nullptr; // Windows: HANDLE del proceso hijo (HANDLE)
void* pty_handle = nullptr; // Windows: HPCON (ConPTY handle)
void* pipe_read = nullptr; // Windows: HANDLE pipe de lectura
void* pipe_write = nullptr; // Windows: HANDLE pipe de escritura (→ stdin del hijo)
// Reader thread
std::thread reader_thread;
std::atomic<bool> reader_running{false};
// Scrollback buffer (protegido por mutex)
mutable std::mutex buf_mutex;
std::vector<TermLine> lines; // buffer circular de lineas
int cur_row = 0; // fila del cursor dentro de `lines`
int cur_col = 0; // columna del cursor
bool scroll_to_bottom = true;
// Parser ANSI (solo lo toca el reader thread)
AnsiParser parser;
// Flag: proceso hijo terminó
std::atomic<bool> process_exited{false};
int exit_code = 0;
// ctor/dtor
TerminalPanel();
~TerminalPanel();
TerminalPanel(const TerminalPanel&) = delete;
TerminalPanel& operator=(const TerminalPanel&) = delete;
bool is_open() const { return master_fd >= 0 || pipe_read != nullptr; }
};
// Abre el proceso hijo y arranca el reader thread.
// Llama una sola vez antes del primer render.
// Si falla, loguea via fn_log::log_error y deja is_open() == false.
void open(TerminalPanel& panel);
// Renderiza el terminal en el area disponible de ImGui.
// Debe llamarse dentro de un frame ImGui activo.
// Dibuja toolbar (clear, copy, reset, scroll-lock) + scrollback + input.
void render(TerminalPanel& panel);
// Envía texto al stdin del proceso hijo.
// No-op si !is_open() o readonly.
void send(TerminalPanel& panel, const std::string& text);
// Cierra el proceso hijo, espera al reader thread y libera recursos.
void close(TerminalPanel& panel);
// ---- Internals usados por los backends Linux/Windows ----
// (No llamar directamente desde apps.)
// Procesa un chunk de bytes del PTY y los añade al scrollback.
// Llamado desde el reader thread. Thread-safe via buf_mutex.
void process_output(TerminalPanel& panel, const char* data, size_t n);
} // namespace fn_term