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>
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
#include "core/ansi_parser.h"
|
||||
|
||||
namespace fn_term {
|
||||
|
||||
// Paleta xterm-16 en ABGR (little-endian: R,G,B,A en memoria = RGBA8888 en lectura).
|
||||
// Index 0-7 colores normales, 8-15 brillantes, 16 = default.
|
||||
const uint32_t kPalette16[17] = {
|
||||
0xFF000000, // 0 black
|
||||
0xFF0000AA, // 1 red
|
||||
0xFF00AA00, // 2 green
|
||||
0xFF00AAAA, // 3 yellow (dark)
|
||||
0xFFAA0000, // 4 blue
|
||||
0xFFAA00AA, // 5 magenta
|
||||
0xFFAAAA00, // 6 cyan
|
||||
0xFFAAAAAA, // 7 white (light grey)
|
||||
0xFF555555, // 8 bright black (dark grey)
|
||||
0xFF5555FF, // 9 bright red
|
||||
0xFF55FF55, // 10 bright green
|
||||
0xFF55FFFF, // 11 bright yellow
|
||||
0xFFFF5555, // 12 bright blue
|
||||
0xFFFF55FF, // 13 bright magenta
|
||||
0xFFFFFF55, // 14 bright cyan
|
||||
0xFFFFFFFF, // 15 bright white
|
||||
0xFFCCCCCC, // 16 default (light grey)
|
||||
};
|
||||
|
||||
AnsiParser::AnsiParser() {
|
||||
for (int i = 0; i < kMaxParams; i++) params_[i] = 0;
|
||||
}
|
||||
|
||||
void AnsiParser::reset() {
|
||||
state_ = State::Ground;
|
||||
cur_fg_ = kColorDefault;
|
||||
cur_bg_ = kColorDefault;
|
||||
cur_bold_ = 0;
|
||||
param_count_ = 0;
|
||||
cur_param_ = 0;
|
||||
for (int i = 0; i < kMaxParams; i++) params_[i] = 0;
|
||||
}
|
||||
|
||||
void AnsiParser::feed(const char* data, size_t n,
|
||||
const std::function<void(const AnsiEvent&)>& cb) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
process_byte(static_cast<unsigned char>(data[i]), cb);
|
||||
}
|
||||
}
|
||||
|
||||
void AnsiParser::flush_param() {
|
||||
if (param_count_ < kMaxParams) {
|
||||
params_[param_count_++] = cur_param_;
|
||||
}
|
||||
cur_param_ = 0;
|
||||
}
|
||||
|
||||
void AnsiParser::apply_sgr(const std::function<void(const AnsiEvent&)>& /*cb*/) {
|
||||
// Si no hay params → reset (SGR 0).
|
||||
int n = (param_count_ == 0) ? 1 : param_count_;
|
||||
const int* p = (param_count_ == 0) ? nullptr : params_;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
int code = (p ? p[i] : 0);
|
||||
if (code == 0) {
|
||||
// Reset todo
|
||||
cur_fg_ = kColorDefault;
|
||||
cur_bg_ = kColorDefault;
|
||||
cur_bold_ = 0;
|
||||
} else if (code == 1) {
|
||||
cur_bold_ = 1;
|
||||
} else if (code == 22) {
|
||||
cur_bold_ = 0;
|
||||
} else if (code >= 30 && code <= 37) {
|
||||
cur_fg_ = static_cast<uint8_t>(code - 30);
|
||||
} else if (code == 39) {
|
||||
cur_fg_ = kColorDefault;
|
||||
} else if (code >= 40 && code <= 47) {
|
||||
cur_bg_ = static_cast<uint8_t>(code - 40);
|
||||
} else if (code == 49) {
|
||||
cur_bg_ = kColorDefault;
|
||||
} else if (code >= 90 && code <= 97) {
|
||||
cur_fg_ = static_cast<uint8_t>(code - 90 + 8);
|
||||
} else if (code >= 100 && code <= 107) {
|
||||
cur_bg_ = static_cast<uint8_t>(code - 100 + 8);
|
||||
}
|
||||
// Otros códigos ignorados silenciosamente (v1 anti-scope).
|
||||
}
|
||||
}
|
||||
|
||||
void AnsiParser::dispatch_csi(unsigned char final_byte,
|
||||
const std::function<void(const AnsiEvent&)>& cb) {
|
||||
AnsiEvent ev;
|
||||
int p0 = (param_count_ > 0) ? params_[0] : 0;
|
||||
int p1 = (param_count_ > 1) ? params_[1] : 0;
|
||||
|
||||
switch (final_byte) {
|
||||
case 'H': case 'f': {
|
||||
// CUP: ESC [ row ; col H (1-based → convertir a 0-based)
|
||||
ev.type = AnsiEventType::CursorAbsolute;
|
||||
ev.cursor_abs.row = (p0 > 0 ? p0 - 1 : 0);
|
||||
ev.cursor_abs.col = (p1 > 0 ? p1 - 1 : 0);
|
||||
cb(ev);
|
||||
break;
|
||||
}
|
||||
case 'A': {
|
||||
ev.type = AnsiEventType::CursorMove;
|
||||
ev.cursor_rel.dir = CursorDir::Up;
|
||||
ev.cursor_rel.n = (p0 > 0 ? p0 : 1);
|
||||
cb(ev);
|
||||
break;
|
||||
}
|
||||
case 'B': {
|
||||
ev.type = AnsiEventType::CursorMove;
|
||||
ev.cursor_rel.dir = CursorDir::Down;
|
||||
ev.cursor_rel.n = (p0 > 0 ? p0 : 1);
|
||||
cb(ev);
|
||||
break;
|
||||
}
|
||||
case 'C': {
|
||||
ev.type = AnsiEventType::CursorMove;
|
||||
ev.cursor_rel.dir = CursorDir::Forward;
|
||||
ev.cursor_rel.n = (p0 > 0 ? p0 : 1);
|
||||
cb(ev);
|
||||
break;
|
||||
}
|
||||
case 'D': {
|
||||
ev.type = AnsiEventType::CursorMove;
|
||||
ev.cursor_rel.dir = CursorDir::Back;
|
||||
ev.cursor_rel.n = (p0 > 0 ? p0 : 1);
|
||||
cb(ev);
|
||||
break;
|
||||
}
|
||||
case 'J': {
|
||||
// ED: erase in display. Solo param=2 (clear screen) soportado en v1.
|
||||
if (p0 == 2 || p0 == 0) {
|
||||
ev.type = AnsiEventType::EraseDisplay;
|
||||
cb(ev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'K': {
|
||||
// EL: erase in line. Solo param=2 (clear entire line) soportado en v1.
|
||||
if (p0 == 2 || p0 == 0) {
|
||||
ev.type = AnsiEventType::EraseLine;
|
||||
cb(ev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
// SGR: select graphic rendition.
|
||||
apply_sgr(cb);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Secuencia CSI desconocida — ignorar silenciosamente.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnsiParser::process_byte(unsigned char c,
|
||||
const std::function<void(const AnsiEvent&)>& cb) {
|
||||
switch (state_) {
|
||||
|
||||
case State::Ground:
|
||||
if (c == 0x1B) {
|
||||
state_ = State::Escape;
|
||||
} else if (c == '\r') {
|
||||
AnsiEvent ev; ev.type = AnsiEventType::CarriageReturn; cb(ev);
|
||||
} else if (c == '\n') {
|
||||
AnsiEvent ev; ev.type = AnsiEventType::Newline; cb(ev);
|
||||
} else if (c == '\x08') {
|
||||
AnsiEvent ev; ev.type = AnsiEventType::Backspace; cb(ev);
|
||||
} else if (c >= 0x20 && c < 0x7F) {
|
||||
// ASCII imprimible.
|
||||
AnsiEvent ev;
|
||||
ev.type = AnsiEventType::Char;
|
||||
ev.cell.ch = static_cast<char32_t>(c);
|
||||
ev.cell.fg = cur_fg_;
|
||||
ev.cell.bg = cur_bg_;
|
||||
ev.cell.bold = cur_bold_;
|
||||
cb(ev);
|
||||
} else if (c >= 0xC0) {
|
||||
// Inicio de secuencia UTF-8 multi-byte.
|
||||
// En v1 mapeamos todo >= 0x80 a '?' para evitar complejidad Unicode.
|
||||
// TODO(0132): soporte Unicode completo en v2.
|
||||
AnsiEvent ev;
|
||||
ev.type = AnsiEventType::Char;
|
||||
ev.cell.ch = U'?';
|
||||
ev.cell.fg = cur_fg_;
|
||||
ev.cell.bg = cur_bg_;
|
||||
ev.cell.bold = cur_bold_;
|
||||
cb(ev);
|
||||
} else if (c >= 0x80 && c < 0xC0) {
|
||||
// Continuation byte de UTF-8 → ignorar (fragmento de multi-byte).
|
||||
}
|
||||
// Otros control bytes (0x00-0x1F excl \r\n\x08\x1B) → ignorar.
|
||||
break;
|
||||
|
||||
case State::Escape:
|
||||
if (c == '[') {
|
||||
state_ = State::CsiEntry;
|
||||
param_count_ = 0;
|
||||
cur_param_ = 0;
|
||||
} else {
|
||||
// Secuencia ESC desconocida (no-CSI) → volver a Ground.
|
||||
state_ = State::Ground;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::CsiEntry:
|
||||
// Primer byte del CSI: puede ser un dígito, ';' o el final byte.
|
||||
if (c >= '0' && c <= '9') {
|
||||
cur_param_ = c - '0';
|
||||
state_ = State::CsiParam;
|
||||
} else if (c == ';') {
|
||||
// Parámetro vacío → valor 0.
|
||||
flush_param();
|
||||
cur_param_ = 0;
|
||||
state_ = State::CsiParam;
|
||||
} else if (c >= 0x40 && c <= 0x7E) {
|
||||
// Byte final inmediato sin parámetros.
|
||||
dispatch_csi(c, cb);
|
||||
state_ = State::Ground;
|
||||
} else if (c == '?') {
|
||||
// Modos privados (e.g. ESC[?25l cursor hide) → ignorar hasta final byte.
|
||||
// Permanecemos en CsiEntry esperando el final byte.
|
||||
} else {
|
||||
// Byte inesperado → abortar CSI.
|
||||
state_ = State::Ground;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::CsiParam:
|
||||
if (c >= '0' && c <= '9') {
|
||||
cur_param_ = cur_param_ * 10 + (c - '0');
|
||||
} else if (c == ';') {
|
||||
flush_param();
|
||||
cur_param_ = 0;
|
||||
} else if (c >= 0x40 && c <= 0x7E) {
|
||||
// Byte final: flush último param y despachar.
|
||||
flush_param();
|
||||
dispatch_csi(c, cb);
|
||||
state_ = State::Ground;
|
||||
} else {
|
||||
// Byte inesperado → abortar.
|
||||
state_ = State::Ground;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fn_term
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
// ansi_parser — parser ANSI/VT100 minimo, byte-a-byte, sin heap allocs por evento.
|
||||
//
|
||||
// Soporta:
|
||||
// SGR: colores FG/BG 16 colores (30-37, 40-47, 90-97, 100-107), bold (1), reset (0).
|
||||
// CUP (H): cursor absolute position row,col.
|
||||
// CUU (A), CUD (B), CUF (C), CUB (D): cursor relative moves.
|
||||
// ED (J): erase in display (param=2 → clear screen).
|
||||
// EL (K): erase in line (param=2 → clear line).
|
||||
// Carriage Return (\r), Newline (\n), Backspace (\x08).
|
||||
// Text: caracteres imprimibles (excl. control bytes).
|
||||
//
|
||||
// No soportado (v1, anti-scope):
|
||||
// 256/24-bit color, italics, underline, Unicode wide, OSC, DCS, SOS, PM, APC,
|
||||
// CSI sequences > 16 parametros, character sets (SI/SO), private modes.
|
||||
//
|
||||
// Uso:
|
||||
// fn_term::AnsiParser p;
|
||||
// p.feed(data, n, [](const fn_term::AnsiEvent& ev) { /* handle */ });
|
||||
//
|
||||
// Thread-safety: NO. Cada instancia debe usarse desde un solo hilo.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace fn_term {
|
||||
|
||||
// Codigos de color ANSI → index 0-15 en paleta CGA/xterm-16.
|
||||
// 0-7: colores normales (black, red, green, yellow, blue, magenta, cyan, white)
|
||||
// 8-15: colores brillantes (idem + bright)
|
||||
// 16: color por defecto (FG o BG)
|
||||
static constexpr uint8_t kColorDefault = 16;
|
||||
|
||||
// Paleta xterm-16 en RGBA8888 (A=0xFF), misma que la mayoria de terminales.
|
||||
// Acceso: kPalette16[index], index in [0,15].
|
||||
extern const uint32_t kPalette16[17]; // [16] = color "default" (blanco/negro)
|
||||
|
||||
// Una celda del terminal virtual.
|
||||
struct AnsiCell {
|
||||
char32_t ch = U' '; // codepoint Unicode (solo BMP en v1)
|
||||
uint8_t fg = kColorDefault; // indice paleta 0-16 (16 = default)
|
||||
uint8_t bg = kColorDefault;
|
||||
uint8_t bold = 0;
|
||||
uint8_t _pad = 0;
|
||||
};
|
||||
|
||||
// Tipos de evento emitidos por el parser.
|
||||
enum class AnsiEventType : uint8_t {
|
||||
Char, // un caracter imprimible (AnsiEvent.cell.ch valido)
|
||||
CursorMove, // AnsiEvent.row / .col delta o absoluto segun subtype
|
||||
CursorAbsolute, // CUP: posicion absoluta 0-based (row, col)
|
||||
EraseDisplay, // ED(2): limpiar pantalla completa
|
||||
EraseLine, // EL(2): limpiar linea actual completa
|
||||
CarriageReturn, // \r
|
||||
Newline, // \n
|
||||
Backspace, // \x08
|
||||
};
|
||||
|
||||
// Subtipos de CursorMove.
|
||||
enum class CursorDir : uint8_t { Up, Down, Forward, Back };
|
||||
|
||||
struct AnsiEvent {
|
||||
AnsiEventType type;
|
||||
union {
|
||||
AnsiCell cell; // type == Char
|
||||
struct {
|
||||
CursorDir dir;
|
||||
int n; // pasos (>= 1)
|
||||
} cursor_rel; // type == CursorMove
|
||||
struct {
|
||||
int row; // 0-based
|
||||
int col; // 0-based
|
||||
} cursor_abs; // type == CursorAbsolute
|
||||
// EraseDisplay, EraseLine, CarriageReturn, Newline, Backspace: sin datos extra.
|
||||
};
|
||||
|
||||
AnsiEvent() : type(AnsiEventType::Char), cell{} {}
|
||||
};
|
||||
|
||||
// Clase principal. Stateful — mantiene el estado del parser entre llamadas a feed().
|
||||
class AnsiParser {
|
||||
public:
|
||||
AnsiParser();
|
||||
~AnsiParser() = default;
|
||||
AnsiParser(const AnsiParser&) = delete;
|
||||
AnsiParser& operator=(const AnsiParser&) = delete;
|
||||
|
||||
// Procesa `n` bytes de `data`. Emite eventos via `cb` en orden.
|
||||
// cb puede ser llamada 0 o más veces por feed().
|
||||
// Sin alloc heap por byte ni por evento.
|
||||
void feed(const char* data, size_t n,
|
||||
const std::function<void(const AnsiEvent&)>& cb);
|
||||
|
||||
// Resetea el estado del parser (útil al limpiar pantalla).
|
||||
void reset();
|
||||
|
||||
// Atributos SGR actuales (se actualizan al procesar secuencias SGR).
|
||||
uint8_t current_fg() const { return cur_fg_; }
|
||||
uint8_t current_bg() const { return cur_bg_; }
|
||||
uint8_t current_bold() const { return cur_bold_; }
|
||||
|
||||
private:
|
||||
enum class State : uint8_t {
|
||||
Ground, // estado normal: procesar texto
|
||||
Escape, // recibido ESC
|
||||
CsiEntry, // recibido ESC [
|
||||
CsiParam, // acumulando parametros CSI
|
||||
};
|
||||
|
||||
State state_ = State::Ground;
|
||||
uint8_t cur_fg_ = kColorDefault;
|
||||
uint8_t cur_bg_ = kColorDefault;
|
||||
uint8_t cur_bold_ = 0;
|
||||
|
||||
// Buffer de parametros CSI (max 16 params de 4 digitos cada uno).
|
||||
static constexpr int kMaxParams = 16;
|
||||
int params_[kMaxParams];
|
||||
int param_count_ = 0;
|
||||
int cur_param_ = 0; // valor del param que se esta acumulando
|
||||
|
||||
void process_byte(unsigned char c,
|
||||
const std::function<void(const AnsiEvent&)>& cb);
|
||||
void flush_param();
|
||||
void dispatch_csi(unsigned char final_byte,
|
||||
const std::function<void(const AnsiEvent&)>& cb);
|
||||
void apply_sgr(const std::function<void(const AnsiEvent&)>& cb);
|
||||
};
|
||||
|
||||
} // namespace fn_term
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: ansi_parser
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "class fn_term::AnsiParser { void feed(const char* data, size_t n, const std::function<void(const fn_term::AnsiEvent&)>& cb); void reset(); uint8_t current_fg() const; uint8_t current_bg() const; uint8_t current_bold() const; }"
|
||||
description: "Parser ANSI/VT100 minimo byte-a-byte sin alloc heap por evento. Soporta SGR colores FG/BG 16-color + bold + reset, cursor moves (CUP/CUU/CUD/CUF/CUB), erase display/line (ED 2, EL 2), CR/LF/BS. Statemachine simple con 4 estados. Emite AnsiEvent via callback."
|
||||
tags: [ansi, vt100, terminal, parser, pure, state-machine, cpp-dashboard-viz]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: [cstddef, cstdint, functional]
|
||||
tested: true
|
||||
tests:
|
||||
- "SGR reset sets default colors"
|
||||
- "SGR fg color 31 sets red"
|
||||
- "SGR bg color 44 sets blue background"
|
||||
- "SGR bright fg 91 sets bright red"
|
||||
- "SGR bold sets bold flag"
|
||||
- "cursor CUU moves up N"
|
||||
- "cursor CUF moves forward N"
|
||||
- "cursor CUP absolute position"
|
||||
- "erase display ED 2"
|
||||
- "erase line EL 2"
|
||||
- "mixed text and SGR sequence"
|
||||
- "newline and carriage return"
|
||||
test_file_path: "cpp/tests/test_ansi_parser.cpp"
|
||||
file_path: "cpp/functions/core/ansi_parser.cpp"
|
||||
framework: ""
|
||||
params:
|
||||
- name: data
|
||||
desc: "Puntero al buffer de bytes a procesar (output crudo de PTY/ConPTY)"
|
||||
- name: n
|
||||
desc: "Numero de bytes en data"
|
||||
- name: cb
|
||||
desc: "Callback invocado por cada evento emitido. Sin alloc — el AnsiEvent vive en el stack del parser"
|
||||
output: "Sin retorno directo. Eventos emitidos via callback: AnsiEventType::Char (caracter + atributos SGR actuales), CursorMove (relativo), CursorAbsolute (CUP), EraseDisplay, EraseLine, CarriageReturn, Newline, Backspace"
|
||||
notes: "Usado por terminal_panel_cpp_viz como paso de parseo del output PTY. Anti-scope v1: sin 256/24-bit color, sin italics/underline, sin Unicode wide, sin OSC/DCS. UTF-8 multi-byte se mapea a '?' en v1."
|
||||
---
|
||||
|
||||
# ansi_parser
|
||||
|
||||
Parser ANSI/VT100 minimo para el modulo `terminal_panel`. Sin heap allocs por byte procesado — la maquina de estados vive en el objeto y los `AnsiEvent` se emiten por callback en el stack del caller.
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```cpp
|
||||
#include "core/ansi_parser.h"
|
||||
|
||||
fn_term::AnsiParser parser;
|
||||
std::string output;
|
||||
|
||||
// Procesar output crudo de PTY:
|
||||
parser.feed(pty_buf, bytes_read, [&](const fn_term::AnsiEvent& ev) {
|
||||
if (ev.type == fn_term::AnsiEventType::Char) {
|
||||
// ev.cell.ch = codepoint, ev.cell.fg = color index 0-16
|
||||
output += static_cast<char>(ev.cell.ch);
|
||||
} else if (ev.type == fn_term::AnsiEventType::Newline) {
|
||||
output += '\n';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando procesas output crudo de un PTY (Linux forkpty) o ConPTY (Windows) y necesitas extraer texto + atributos de color para renderizar en ImGui con `PushStyleColor`. Es la capa de parseo de `terminal_panel`.
|
||||
|
||||
## Secuencias soportadas (v1)
|
||||
|
||||
| Tipo | Secuencia | AnsiEventType |
|
||||
|------|-----------|---------------|
|
||||
| Texto ASCII | bytes 0x20-0x7E | Char |
|
||||
| CR | `\r` (0x0D) | CarriageReturn |
|
||||
| LF | `\n` (0x0A) | Newline |
|
||||
| BS | `\x08` | Backspace |
|
||||
| SGR reset | `ESC[0m` o `ESC[m` | (actualiza estado interno) |
|
||||
| SGR bold | `ESC[1m` | (actualiza estado interno) |
|
||||
| SGR FG 16 | `ESC[30-37m`, `ESC[90-97m` | (actualiza estado interno) |
|
||||
| SGR BG 16 | `ESC[40-47m`, `ESC[100-107m` | (actualiza estado interno) |
|
||||
| Cursor UP | `ESC[nA` | CursorMove (Up, n) |
|
||||
| Cursor DOWN | `ESC[nB` | CursorMove (Down, n) |
|
||||
| Cursor FWD | `ESC[nC` | CursorMove (Forward, n) |
|
||||
| Cursor BACK | `ESC[nD` | CursorMove (Back, n) |
|
||||
| CUP | `ESC[r;cH` | CursorAbsolute (0-based) |
|
||||
| ED(2) | `ESC[2J` | EraseDisplay |
|
||||
| EL(2) | `ESC[2K` | EraseLine |
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Anti-scope v1: no 256-color (`ESC[38;5;Nm`), no 24-bit color, no italics/underline, no curses pesados.
|
||||
- UTF-8 multi-byte: bytes de continuacion 0x80-0xBF ignorados; inicio 0xC0+ emite `?`. Soporte completo en v2.
|
||||
- No thread-safe: cada instancia debe usarse desde un solo hilo (el reader thread del PTY).
|
||||
- `kPalette16[16]` es el color "default" (gris claro). El caller decide si usar el color del tema o la paleta fija.
|
||||
Reference in New Issue
Block a user