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>
296 lines
No EOL
5.6 KiB
Text
296 lines
No EOL
5.6 KiB
Text
// mudlib: Lil
|
|
// file: user.c
|
|
// purpose: is the representation of an interactive (user) in the MUD
|
|
|
|
#include <globals.h>
|
|
#include <tui.h>
|
|
|
|
inherit BASE;
|
|
// TUI support: tui_readline() prompts, tui_open() full-screen apps, and the
|
|
// window_size/terminal_type applies (see /std/tui/README.md).
|
|
inherit TUI_TERMINAL;
|
|
|
|
private string name;
|
|
|
|
#ifdef __INTERACTIVE_CATCH_TELL__
|
|
void catch_tell(string str) {
|
|
receive(str);
|
|
}
|
|
#endif
|
|
|
|
// replace this with a functioning version.
|
|
|
|
string
|
|
query_cwd()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
// logon: move this to /single/login.c when login.c gets written.
|
|
|
|
void
|
|
logon()
|
|
{
|
|
write("Welcome to Lil.\n> ");
|
|
}
|
|
|
|
// query_name: called by various objects needing to know this user's name.
|
|
|
|
string
|
|
query_name()
|
|
{
|
|
return name;
|
|
}
|
|
|
|
void
|
|
set_name(string arg)
|
|
{
|
|
// may wish to add security to prevent just anyone from changing
|
|
// someone else's name.
|
|
name = arg;
|
|
}
|
|
|
|
// called by the present() efun (and some others) to determine whether
|
|
// an object is referred as an 'arg'.
|
|
|
|
int
|
|
id(string arg)
|
|
{
|
|
return (arg == query_name()) || base::id(arg);
|
|
}
|
|
|
|
#ifndef __OLD_ED__
|
|
void
|
|
write_prompt() {
|
|
switch (query_ed_mode()) {
|
|
case 0:
|
|
case -2:
|
|
write(":");
|
|
break;
|
|
|
|
case -1:
|
|
write("> ");
|
|
break;
|
|
|
|
default:
|
|
write("*\b");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
start_ed(string file) {
|
|
write(ed_start(file, 0));
|
|
}
|
|
#endif
|
|
|
|
#ifdef __NO_ADD_ACTION__
|
|
void
|
|
exec_command(string arg) {
|
|
string *parts = explode(arg, " ");
|
|
string verb = sizeof(parts) ? parts[0] : "";
|
|
string rest = implode(parts[1..], " ");
|
|
string cmd_path = COMMAND_PREFIX + verb;
|
|
object cobj = load_object(cmd_path);
|
|
|
|
if (cobj) {
|
|
cobj->main(rest);
|
|
} else {
|
|
// maybe call an emote/soul daemon here
|
|
}
|
|
}
|
|
|
|
void
|
|
process_input(string arg) {
|
|
#ifndef __OLD_ED__
|
|
if (query_ed_mode() != -1) {
|
|
if (arg[0] != '!') {
|
|
write(ed_cmd(arg));
|
|
return;
|
|
}
|
|
arg = arg[1..];
|
|
}
|
|
#endif
|
|
exec_command(arg);
|
|
}
|
|
#else
|
|
string
|
|
process_input(string arg)
|
|
{
|
|
#ifndef __OLD_ED__
|
|
if (query_ed_mode() != -1) {
|
|
if (arg[0] != '!') {
|
|
write(ed_cmd(arg));
|
|
return 0;
|
|
}
|
|
arg = arg[1..];
|
|
}
|
|
#endif
|
|
// possible to modify player input here before driver parses it.
|
|
return arg;
|
|
}
|
|
|
|
int
|
|
commandHook(string arg)
|
|
{
|
|
string cmd_path;
|
|
object cobj;
|
|
|
|
cmd_path = COMMAND_PREFIX + query_verb();
|
|
|
|
cobj = load_object(cmd_path);
|
|
if (cobj) {
|
|
return (int)cobj->main(arg);
|
|
} else {
|
|
// maybe call an emote/soul daemon here
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// init: called by the driver to give the object a chance to add some
|
|
// actions (see the MudOS "applies" documentation for a better description).
|
|
|
|
void
|
|
init()
|
|
{
|
|
// using "" as the second argument to add_action() causes the driver
|
|
// to call commandHook() for those user inputs not matched by other
|
|
// add_action defined commands (thus 'commandHook' becomes the default
|
|
// action for those verbs without an explicitly associated action).
|
|
if (this_object() == this_player()) {
|
|
add_action("commandHook", "", 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// create: called by the driver after an object is compiled.
|
|
|
|
void
|
|
create()
|
|
{
|
|
#ifdef __PACKAGE_UIDS__
|
|
seteuid(0); // so that login.c can export uid to us
|
|
#endif
|
|
}
|
|
|
|
// receive_message: called by the message() efun.
|
|
|
|
void
|
|
receive_message(string newclass, string msg)
|
|
{
|
|
// the meaning of 'class' is at the mudlib's discretion
|
|
receive(msg);
|
|
}
|
|
|
|
// setup: used to configure attributes that aren't known by this_object()
|
|
// at create() time such as living_name (and so can't be done in create()).
|
|
|
|
void
|
|
setup()
|
|
{
|
|
set_heart_beat(1);
|
|
#ifdef __PACKAGE_UIDS__
|
|
seteuid(getuid(this_object()));
|
|
#endif
|
|
#ifndef __NO_WIZARDS__
|
|
enable_wizard();
|
|
#endif
|
|
#ifndef __NO_ADD_ACTION__
|
|
set_living_name(query_name());
|
|
enable_commands();
|
|
add_action("commandHook", "", 1);
|
|
#else
|
|
set_this_player(this_object());
|
|
#endif
|
|
}
|
|
|
|
// net_dead: called by the gamedriver when an interactive player loses
|
|
// hir network connection to the mud.
|
|
|
|
#ifndef __NO_ENVIRONMENT__
|
|
void tell_room(object ob, string msg) {
|
|
foreach (ob in all_inventory(ob) - ({ this_object() }))
|
|
tell_object(ob, msg);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
net_dead()
|
|
{
|
|
tui_destroy();
|
|
set_heart_beat(0);
|
|
#ifndef __NO_ENVIRONMENT__
|
|
tell_room(environment(), query_name() + " is link-dead.\n");
|
|
#endif
|
|
}
|
|
|
|
// reconnect: called by the login.c object when a netdead player reconnects.
|
|
|
|
void
|
|
reconnect()
|
|
{
|
|
set_heart_beat(1);
|
|
#ifndef __NO_ENVIRONMENT__
|
|
tell_room(environment(), "Reconnected.\n");
|
|
tell_room(environment(), query_name() + " has reconnected.\n");
|
|
#endif
|
|
}
|
|
|
|
void window_size(int width, int height) {
|
|
if (!tui_active()) receive(sprintf("TELNET NAWS: %dx%d\n", width, height));
|
|
::window_size(width, height); // let /std/tui/terminal cache it
|
|
}
|
|
|
|
// receive_environ
|
|
void receive_environ(string var, string value) {
|
|
string msg = sprintf("TELNET ENV received: %s=%s\n", var, value);
|
|
receive(msg);
|
|
}
|
|
|
|
void mxp_enable() {
|
|
receive("<MXP negotiation: enabled.>\n");
|
|
}
|
|
|
|
void gmcp_enable() {
|
|
receive("<GMCP negotiation: enabled.>\n");
|
|
}
|
|
|
|
void gmcp(string req) {
|
|
receive("<GMCP request received.>\n");
|
|
receive(req);
|
|
receive("\n<GMCP request end.>\n");
|
|
}
|
|
|
|
void mxp_tag(string command) {
|
|
receive("<MXP TAG: " + command + ".>\n");
|
|
}
|
|
|
|
void act_mxp() {
|
|
efun::act_mxp();
|
|
}
|
|
|
|
void send_gmcp(string req) {
|
|
efun::send_gmcp(req);
|
|
}
|
|
|
|
void msp_enable() {
|
|
receive("<MSP negotiation: enabled.>\n");
|
|
}
|
|
|
|
void msp_oob(string req) {
|
|
efun::telnet_msp_oob(req);
|
|
}
|
|
|
|
void msdp_enable() {
|
|
receive("<MSDP negotiation: enabled.>\n");
|
|
}
|
|
|
|
void msdp(string req) {
|
|
receive("<MSDP request received.>\n");
|
|
receive(req);
|
|
receive("\n<MSDP request end.>\n");
|
|
}
|
|
|
|
void send_msdp_variable(string name, mixed value) {
|
|
efun::send_msdp_variable(name, value);
|
|
} |