mirror of
https://github.com/fluffos/fluffos
synced 2026-08-12 18:26:06 -04:00
* Char-mode input: deliver real keystrokes (BS/DEL, whole UTF-8, raw ESC); fix NAWS lost at logon Five input-path fixes that make raw-keystroke (get_char) applications viable, found by building the LPC TUI library on top of them: - comm.cc: char mode delivered "" for Backspace/Delete (the byte was zeroed before delivery, making BS/DEL/NUL indistinguishable). The literal byte is now delivered; line-mode in-buffer editing unchanged. - comm.cc: char mode delivered one *byte* per callback, splitting a multi-byte UTF-8 character into 2-4 invalid one-byte strings. Extraction is now UTF-8 aware: a complete sequence arrives as one callback carrying one valid character; malformed bytes still go byte-at-a-time (no stalls). - comm.cc: the "no ansi" + "strip before process input" ESC->space rewrite (both default on) also applied to char mode, so arrow keys arrived as literal "[A". The rewrite is an anti-ANSI-injection protection for line-mode commands; char mode now always passes ESC through. - net/telnet.cc: each received chunk was u8_sanitize()d independently, so a UTF-8 character split across TCP segments became U+FFFD in any input mode. An incomplete trailing sequence (new u8_incomplete_tail(), GTest-covered) is now carried over in interactive_t and prepended to the next chunk. - net/telnet.cc + comm.cc: fast clients answer the initial DO NAWS while ip->ob is still the master object, so the window_size apply fired on the wrong object and the size was lost until the next resize. The last report is cached and replayed on the user object at logon. docs/efun/interactive/get_char.md documents the delivery contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpXv4yGCWpkzbkifyZy9EE * Add /std/tui: an LPC TUI library (readline + ncurses for the prompt line and full-screen apps) A terminal-UI toolkit in pure LPC, strictly layered (design + rationale in testsuite/std/tui/DESIGN.md; user docs in docs/concepts/general/tui.md): - ansi.lpc: escape builders plus the width toolkit the driver lacks (visible_width/wslice/wpad are ANSI-blind and wide-char aware). - keys.lpc: keystroke decoder state machine: the get_char byte stream -> key events (CSI/SS3 with xterm modifiers, Alt prefixes, bracketed paste as one event, SGR mouse, UTF-8, lone-ESC via caller-driven flush()). - readline.lpc: the line editor: emacs keymap (motion/kill/yank/ transpose), Up/Down history, C-r/C-s incremental search, Tab completion, masked mode, horizontal scrolling with wide-char aware viewport; repaints only its own line so it works at any scroll position. - screen.lpc: virtual cell grid + minimal-diff frame renderer with copy-on-write rows (frame cost tracks touched rows, not W x H); wide chars own two cells, boxes/fills/attrs as SGR param strings. - widget.lpc + w/ (label, list, textfield) + app.lpc: widget protocol, focus cycling, event routing; textfield embeds a readline engine. - terminal.lpc: the one impure module, inherited by the user object: get_char re-arm loop (I_NOECHO across re-arms), NAWS/TTYPE caching via the window_size/terminal_type applies, lone-ESC walltime timeout, guaranteed teardown. tui_readline() is a drop-in input_to() replacement; tui_open() runs full-screen apps on the alt screen. Everything below terminal.lpc is a pure state machine: 194 checks in single/tests/std/tui/ cover the decoder, editor sessions, history search, screen diffing and widgets headlessly. `tuidemo` / `tuidemo app` (command/tuidemo.lpc) demo both modes over any telnet client; the whole stack was verified end-to-end over a live connection including split-packet UTF-8, modifier keys, paste, resize and alternate-screen teardown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpXv4yGCWpkzbkifyZy9EE * /std/tui v2: pterm/blessed-inspired widgets, inline printers and prompts, showcases Reviewed the pterm (Go) and blessed widget catalogs and ported what earns its keep in a MUD; DESIGN.md is renamed to README.md and documents the full set (including what was deliberately not ported). New layers: - print.lpc — pterm-style printers that compose with plain write(): p_table (boxed, width-aware, header rule), p_tree, p_bars, p_spark, p_panel, p_bullets, p_header, p_progress, p_info/success/warn/error, and p_bigtext via the existing /std/bitmap_font. - menu.lpc + terminal glue — inline interactive prompts in the normal output flow: tui_select(), tui_multiselect() (Space toggles, list windows by height, collapses to a "? prompt: answer" line) and tui_confirm() (y/n with a default). New widgets (/std/tui/w/): table (columns + header + selection), tree (collapsible, arrows fold), checklist, radiolist, button, progress, spinner (app-driven tick()), and log (bottom-anchored scrollback pane). Showcases (command/tuidemo.lpc): `tuidemo select` (prompt chain), `tuidemo print` (all printers), `tuidemo dashboard` (animated spinner, progress bars, sparkline, live table and log on a call_out tick), and `tuidemo form` (textfield, radio group, checkboxes, buttons). Coverage: three new test files (print exact-output, menu sessions, widgets2) bring /std/tui to 8 files in the suite; the live-connection e2e run now drives all six showcases end-to-end (52 checks), including the select->multiselect->confirm chain, unattended dashboard animation, and full form entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpXv4yGCWpkzbkifyZy9EE * /std/tui: charts — braille canvas, line charts, vertical bars, heatmap Fills the chart gap left by v2 (only the horizontal p_bars existed): - canvas.lpc: a braille dot canvas (each cell is a 2x4 dot grid from the U+2800 block — the blessed-contrib/drawille technique), giving sub-cell resolution: c_set/c_unset/c_get, Bresenham c_line, and c_plot (scale a value series across the canvas), with per-cell colour. Renders as a string, per-row strings, or per-cell ({ ch, attr }) pairs for blitting into a screen. - print.lpc: p_chart (multi-series braille line chart with y-axis gutter and coloured legend), p_vbars (vertical bar chart with eighth-block partial tops and optional value row), p_heatmap (2D matrix as 256-colour cells, cool-to-hot ramp, optional axis labels). - w/chart.lpc: the live line-chart widget — add_series()/add_point() rolling history sized to the widget, auto or fixed y-range; replaces the dashboard's sparkline label with a real animating graph. - `tuidemo charts` showcase; chart docs in README.md and the docs page (heatmap removed from the not-ported list). Tests: single/tests/std/tui/charts.lpc pins the braille bit math with exact glyphs, line drawing, plot endpoints, exact p_vbars output, chart/heatmap structure, and the widget's rolling window. The live e2e run verifies `tuidemo charts` output and braille frames streaming from the dashboard chart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpXv4yGCWpkzbkifyZy9EE * docs: move the TUI page from Concepts to STDLIB /std/tui is a mudlib library like base64/break_string/json, so its page belongs under docs/stdlib/ with the rest of the /std modules, not under Concepts. Registered in stdlib/index.md; README file-layout pointer updated. (Concepts' sidebar is autogenerated, so the old entry disappears with the file.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpXv4yGCWpkzbkifyZy9EE --------- Co-authored-by: Claude <noreply@anthropic.com>
151 lines
5.8 KiB
Text
151 lines
5.8 KiB
Text
// tuidemo — showcases for /std/tui (see /std/tui/README.md).
|
|
//
|
|
// tuidemo readline prompt: editing, ↑/↓ history, Ctrl-R search,
|
|
// Tab completion; "quit" or Ctrl-D exits
|
|
// tuidemo select inline select / multiselect / confirm prompts
|
|
// tuidemo print the pterm-style inline printers, in plain line mode
|
|
// tuidemo charts vertical bars, braille line chart, heatmap, sparkline
|
|
// tuidemo app basic full-screen demo (list + textfield)
|
|
// tuidemo dashboard animated dashboard: spinner, progress, sparkline,
|
|
// live table and log (q quits)
|
|
// tuidemo form form demo: textfield, radio, checkboxes, buttons
|
|
|
|
#include <tui.h>
|
|
|
|
inherit TUI_PRINT;
|
|
|
|
private string *verbs = ({ "look", "listen", "inventory", "invite", "quit", "quaff" });
|
|
|
|
// --- readline showcase -------------------------------------------------
|
|
|
|
private string *complete_verb(string buf, int pos) {
|
|
string head = pos > 0 ? buf[0..pos - 1] : "";
|
|
|
|
if (strsrch(head, " ") != -1) return ({}); // complete only the first word
|
|
return verbs;
|
|
}
|
|
|
|
private void got_line(mixed line, int st) {
|
|
object me = this_player();
|
|
|
|
if (st != TUI_RL_DONE) {
|
|
write(st == TUI_RL_ABORT ? "[aborted]\n" : "[eof]\n");
|
|
return;
|
|
}
|
|
if (line == "quit") {
|
|
write("bye.\n");
|
|
return;
|
|
}
|
|
if (line != "") write("you typed: " + line + "\n");
|
|
me->tui_readline((: got_line :),
|
|
([ "prompt": "tui> ", "completer": (: complete_verb :) ]));
|
|
}
|
|
|
|
// --- select / multiselect / confirm showcase -----------------------------
|
|
|
|
private void confirmed(int yes, int st) {
|
|
write(yes ? p_success("Character created.") : p_warn("Discarded."));
|
|
}
|
|
|
|
private void chose_skills(int *idx, string *items, int st) {
|
|
if (st != TUI_RL_DONE) {
|
|
write("[cancelled]\n");
|
|
return;
|
|
}
|
|
this_player()->tui_confirm((: confirmed :),
|
|
"Create with " + implode(items, ", ") + "?", 1);
|
|
}
|
|
|
|
private void chose_class(int idx, string item, int st) {
|
|
if (st != TUI_RL_DONE) {
|
|
write("[cancelled]\n");
|
|
return;
|
|
}
|
|
this_player()->tui_multiselect((: chose_skills :), "Pick skills for the " + item + ":",
|
|
({ "swords", "archery", "alchemy", "lockpicking",
|
|
"diplomacy", "cartography" }),
|
|
([ "height": 4 ]));
|
|
}
|
|
|
|
// --- inline printers showcase ---------------------------------------------
|
|
|
|
private void show_print() {
|
|
write(p_bigtext("TUI", "36"));
|
|
write(p_header("the /std/tui inline printers", 62));
|
|
write("\n");
|
|
write(p_info("printers compose with write() — no full-screen mode needed"));
|
|
write(p_success("this line came from p_success()"));
|
|
write(p_warn("p_warn() looks like this"));
|
|
write(p_error("and p_error() like this"));
|
|
write("\n");
|
|
write(p_panel("panel", "Boxed content, width-aware —\nwide chars work: 你好世界",
|
|
([ "style": TUI_BOX_ROUND ])));
|
|
write(p_table(({ ({ "efun", "package", "notes" }),
|
|
({ "strwidth", "core", "display columns" }),
|
|
({ "terminal_colour", "contrib", "%^TOKEN%^ + wrapping" }),
|
|
({ "你好", "unicode", "double width" }) }),
|
|
([ "header": 1 ])));
|
|
write(p_tree(({ ({ "std", ({ ({ "tui", ({ "ansi.lpc", "keys.lpc", "readline.lpc",
|
|
({ "w", ({ "list.lpc", "table.lpc", "tree.lpc" }) }) }) }) }) }) })));
|
|
write("\n");
|
|
write(p_bars(({ ({ "orcs", 24 }), ({ "elves", 13 }), ({ "dwarves", 31 }) }),
|
|
([ "width": 30, "attr": "36" ])));
|
|
write("\nsparkline " + p_spark(({ 1, 3, 2, 8, 4, 6, 7, 2, 5, 8, 3, 1 })) + "\n");
|
|
write("progress " + p_progress(64, 30) + "\n\n");
|
|
write(p_bullets(({ "bullet lists", ({ 1, "nest by depth" }), ({ 2, "three markers" }) })));
|
|
}
|
|
|
|
// --- charts showcase ---------------------------------------------------------
|
|
|
|
private void show_charts() {
|
|
write(p_header("charts", 62));
|
|
write("\np_vbars — vertical bars with eighth-block tops:\n\n");
|
|
write(p_vbars(({ ({ "mon", 3 }), ({ "tue", 8 }), ({ "wed", 5 }),
|
|
({ "thu", 9 }), ({ "fri", 6 }) }),
|
|
([ "height": 5, "values": 1, "attr": "33" ])));
|
|
write("\np_chart — braille line chart, two series:\n\n");
|
|
write(p_chart(({ ({ "hp", ({ 50, 52, 60, 55, 70, 65, 80, 90, 85, 95 }), "32" }),
|
|
({ "mana", ({ 90, 80, 82, 70, 65, 60, 50, 55, 40, 35 }), "36" }) }),
|
|
([ "width": 50, "height": 8 ])));
|
|
write("\np_heatmap — logins per weekday/hour:\n\n");
|
|
write(p_heatmap(({ ({ 1, 3, 7, 4 }), ({ 2, 8, 9, 5 }), ({ 0, 4, 6, 2 }) }),
|
|
([ "ylabels": ({ "mon", "tue", "wed" }),
|
|
"xlabels": ({ "00", "06", "12", "18" }), "cellw": 4 ])));
|
|
write("\np_spark — inline sparkline: " +
|
|
p_spark(({ 1, 3, 2, 8, 4, 6, 7, 2, 5, 8, 3, 1 })) + "\n");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
int main(string arg) {
|
|
object me = this_player();
|
|
|
|
if (!me) return 0;
|
|
switch (arg) {
|
|
case "app":
|
|
me->tui_open(new("/clone/tuidemo_app"));
|
|
return 1;
|
|
case "dashboard":
|
|
me->tui_open(new("/clone/tuidemo_dash"));
|
|
return 1;
|
|
case "form":
|
|
me->tui_open(new("/clone/tuidemo_form"));
|
|
return 1;
|
|
case "print":
|
|
show_print();
|
|
return 1;
|
|
case "charts":
|
|
show_charts();
|
|
return 1;
|
|
case "select":
|
|
me->tui_select((: chose_class :), "Pick a class:",
|
|
({ "warrior", "mage", "thief", "cleric", "ranger" }));
|
|
return 1;
|
|
}
|
|
write("TUI readline demo: editing keys, Up/Down history, Ctrl-R search,\n"
|
|
"Tab completion. 'quit' or Ctrl-D exits.\n"
|
|
"Also try: tuidemo select | print | charts | app | dashboard | form\n");
|
|
me->tui_readline((: got_line :),
|
|
([ "prompt": "tui> ", "completer": (: complete_verb :) ]));
|
|
return 1;
|
|
}
|