07252c0172
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>
112 lines
4.1 KiB
C++
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
|