From 5895e2508a293c1d1380faa82a3d00e350f8063e Mon Sep 17 00:00:00 2001 From: Condorra Date: Sun, 17 Nov 2024 18:39:34 +1100 Subject: [PATCH] Start help system. --- Cargo.lock | 28 +++++----- Cargo.toml | 2 +- help.json | 26 ++++++++++ index.html | 1 + src/logging.rs | 4 +- src/lua_engine.rs | 5 ++ src/lua_engine/help.rs | 90 +++++++++++++++++++++++++++++++++ src/lua_engine/named_closure.rs | 35 +++++++++++++ 8 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 help.json create mode 100644 src/lua_engine/help.rs create mode 100644 src/lua_engine/named_closure.rs diff --git a/Cargo.lock b/Cargo.lock index 7a23121..ad00a97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,9 +781,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1443,9 +1443,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -1454,9 +1454,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -1481,9 +1481,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1491,9 +1491,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -1504,15 +1504,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index a8c4a64..21b74c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc92429217 unicode-segmentation = "1.11.0" unicode-width = "0.1.13" wasm-bindgen = "0.2.92" -web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement"] } +web-sys = { version = "0.3.72", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration", "HtmlAnchorElement", "XmlHttpRequest"] } yew = { version = "0.21.0", features = ["csr"] } minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "0c8c6d4f0cf445adf7bb957811081a1b710bd933" } thiserror = "1.0.63" diff --git a/help.json b/help.json new file mode 100644 index 0000000..f0021ea --- /dev/null +++ b/help.json @@ -0,0 +1,26 @@ +{ + "_index": "Try #help command_name (leave off the # from command names) for command help.\r\n\r\n\"#help getting started\" has tips to get started.", + "act": "Use #act to set a trigger that will automatically run commands based on a match to the MUD's output. Example: #act {^The (.*) attacks you.} {\"Hey $1, you're going to wish you hadn't done that!\"}.\r\nUse #act by itself to list triggers. See #unact for info on how to remove triggers.", + "alias": "Use #alias to set a command alias.\r\nFor example, #alias {^hi} {#echo Hello} will make typing 'hi' run the echo command.\r\nUse #alias by itself to list aliases. See also: #unalias", + "connect_mud": "Use #connect_mud to connect to a MUD. Provide a name for the connection and the WebSocket server URL, e.g. #connect_mud ex wss://example.com/ws.", + "copy_paste": "Use Ctrl Insert to copy and Shift Insert to paste in most browsers.", + "create_script": "Use the '+' icon in the script browser to create a new script. You will need to name it first.", + "delay": "Use #delay to schedule a command to run after a specified time (in seconds).\r\nFor example, #delay 5 {#echo Ding!} will run the echo command after 5 seconds.\r\nOptionally give it a name with a third argument.\r\nUse #delay by itself to list delays.\r\nSee also: #undelay", + "deletelogs": "Use #deletelogs to permanently delete logs.\r\nExample: #deletelogs mylog 2024-10-01 2024-10-10\r\nThe first argument is the log name. Optionally, follow with a second and third argument to give the date range - or just one date to only specify the end of the date range.\r\nSee also: #downloadlogs", + "downloadlogs": "Use #downloadlogs to download logs within a date range. Example: #downloadlogs mylog 2024-10-01 2024-10-10.", + "editor": "Use #editor to open the script editor where you can manage Lua scripts.", + "getting started": "Use #help connect_mud to learn how to connect if you aren't already.\r\nSend basic commands to the current MUD just by typing them in. Use a number like #5 n to repeat a command. Use semicolons to send commands in sequence, e.g. n;#4 e\r\nCopy and paste using Ctrl+Insert and Shift+Insert", + "hsplit": "Use #hsplit to split the screen horizontally. For example, #hsplit {} 2 will create a horizontal split with frame 2.", + "include": "To run a script, use #include scriptname. Example: #include myscript.lua.", + "listlogs": "Use #listlogs to list stored logs and the number of entries for each logname.", + "log": "Use #log to start logging a MUD's output. Example: #log mymud mylog will start logging for the 'mymud' connection.", + "lua_scripting": "Scripts use Lua code. Use commands like 'commands.command(\"command to send\")' to execute MUD commands. For example, to set an alias, use 'commands.alias(\"^hi\", \"#echo Hello\")'.", + "panel_merge": "Use #panel_merge followed by the frame path to merge panels. For example, #panel_merge {} will merge the current panel with the top-level split.", + "send_multiple_commands": "To send several commands in quick succession, separate them with a semicolon. For example, use 'n;e' to go north and then east.", + "tick": "Use #tick to set a recurring command to run at a specified time interval (in seconds).\r\nFor example, #tick 60 {#echo Cockadoodledoo!} will repeat every 60 seconds.\r\nOptionally give it a name with a third argument.\r\nUse #tick by itself to list active ticks.\r\nSee also: #untick", + "unact": "Use #unact followed by the trigger pattern to delete a trigger. Example: #unact {^The (.*) attacks you.}.", + "unalias": "Use #unalias followed by the alias pattern to delete it. For example, #unalias {^hi}.", + "undelay": "Use #undelay followed by the delay name to cancel it. Example: #undelay ding to cancel a delay named 'ding'.", + "untick": "Use #untick followed by the tick name to cancel it. Example: #untick rooster to cancel a tick named 'rooster'.", + "vsplit": "Use #vsplit to split the screen vertically. For example, #vsplit {} 2 will create a vertical split with frame 2." +} diff --git a/index.html b/index.html index b50f756..777b2fd 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@ + diff --git a/src/logging.rs b/src/logging.rs index 6c54076..ec5cead 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -377,8 +377,8 @@ async fn immediate_download_logs_refutable( let buf_bytes = buf.as_bytes(); let buf_array = Uint8Array::new_with_length(buf_bytes.len() as u32); buf_array.copy_from(buf_bytes); - let mut blobprops: BlobPropertyBag = Default::default(); - blobprops.type_("text/plain"); + let blobprops: BlobPropertyBag = Default::default(); + blobprops.set_type("text/plain"); let blob = Blob::new_with_u8_array_sequence_and_options( &[&buf_array].into_iter().collect::(), &blobprops, diff --git a/src/lua_engine.rs b/src/lua_engine.rs index 575e335..9864ab2 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -1,5 +1,6 @@ use self::{frameroutes::*, frames::*, muds::*, storage::*}; use anyhow::Error; +use help::{cmd_help, load_help}; use muds::telopt::configure_telopt_table; use piccolo::{ async_callback::{AsyncSequence, Locals}, @@ -29,7 +30,9 @@ pub struct LuaState { mod frameroutes; pub mod frames; +pub mod help; pub mod muds; +pub mod named_closure; pub mod storage; impl LuaState { @@ -217,6 +220,7 @@ pub fn install_lua_globals( register_command!(echo_frame); register_command!(echo_frame_raw); register_command!(editor); + register_stateless_command!(cmd_help, "help"); register_command!(hsplit); register_stateless_command!(cmd_include, "include"); register_command!(cmd_list_logs, "listlogs"); @@ -387,6 +391,7 @@ fn lua_global_initialisation(global_memo: &GlobalMemoCell) -> Result<(), String> Function::Callback(ensure_frame_instance(ctx, &FrameId(1))), (), ); + let _ = load_help(global_memo, ctx, "help.json"); }); lua_engine_ref .interp diff --git a/src/lua_engine/help.rs b/src/lua_engine/help.rs new file mode 100644 index 0000000..fc787c2 --- /dev/null +++ b/src/lua_engine/help.rs @@ -0,0 +1,90 @@ +use std::collections::BTreeMap; + +use crate::GlobalMemoCell; +use anyhow::Result; +use piccolo::{Callback, CallbackReturn, Context, FromValue, Function, Table, Value, Variadic}; +use std::rc::Rc; +use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; +use web_sys::{console, XmlHttpRequest}; + +use super::named_closure::{release_named_global_closure, store_named_global_closure}; + +pub fn load_help(global_memo: &GlobalMemoCell, ctx: Context, url: &str) -> Result<()> { + let xhr: Rc = XmlHttpRequest::new() + .map_err(|_e| anyhow::Error::msg("Error creating XmlHttpRequest"))? + .into(); + let global_memo = global_memo.clone(); + let xhr_cb = xhr.clone(); + let load_callback: Closure = Closure::new(move || { + let _ = global_memo.lua_engine.borrow_mut().interp.try_enter(|ctx| { + release_named_global_closure(ctx, "_help_load_callback"); + if let Ok(Some(txt)) = xhr_cb.response_text() { + match serde_json::from_str::>(&txt) { + Err(_) => console::log_1(&JsValue::from_str( + "Loading help failed because result wasn't a String -> String map", + )), + Ok(d) => { + let help_tbl: Value = ctx.get_global("help")?; + let help_tbl = if help_tbl.is_nil() { + let tbl = Table::new(&ctx); + ctx.set_global("help", tbl); + tbl + } else { + Table::from_value(ctx, help_tbl)? + }; + for (k, v) in d.iter() { + help_tbl.set(ctx, k.clone(), v.clone())?; + } + } + } + } + Ok(()) + }); + }); + xhr.set_onload(Some(load_callback.as_ref().unchecked_ref())); + store_named_global_closure(ctx, "_help_load_callback", load_callback); + xhr.open("GET", url) + .map_err(|_e| anyhow::Error::msg("Error opening XmlHttpRequest"))?; + xhr.send() + .map_err(|_e| anyhow::Error::msg("Error sending XmlHttpRequest"))?; + + Ok(()) +} + +pub(super) fn cmd_help<'gc>(ctx: Context<'gc>) -> Callback<'gc> { + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let help: Value = ctx.get_global("help")?; + let echo: Function = ctx.get_global::("commands")?.get(ctx, "echo")?; + let args: Variadic> = stack.consume(ctx)?; + let mut path: String = args.join(" "); + if let Some(p) = path.strip_prefix('#') { + path = p.to_owned(); + } + path = path.trim().to_lowercase(); + + if path.is_empty() { + path = "_index".to_owned(); + } + + if help.is_nil() { + stack.into_back(ctx, "Help has not (yet) loaded successfully"); + return Ok(CallbackReturn::Call { + function: echo, + then: None, + }); + } + let help: Table = Table::from_value(ctx, help)?; + let help_value: Value = help.get(ctx, path)?; + let help_value = if help_value.is_nil() { + "No help available on that.".to_owned() + } else { + String::from_value(ctx, help_value)? + }; + stack.into_back(ctx, help_value); + + Ok(CallbackReturn::Call { + function: echo, + then: None, + }) + }) +} diff --git a/src/lua_engine/named_closure.rs b/src/lua_engine/named_closure.rs new file mode 100644 index 0000000..1240d01 --- /dev/null +++ b/src/lua_engine/named_closure.rs @@ -0,0 +1,35 @@ +use gc_arena::Collect; +use gc_arena::Gc; +use gc_arena::Rootable; +use piccolo::{Context, UserData, Value}; +use std::rc::Rc; +use wasm_bindgen::prelude::Closure; + +#[derive(Collect)] +#[collect(require_static)] +struct ClosureCollect { + #[allow(unused)] + inner: Rc>, +} +pub fn store_named_global_closure<'gc, F: 'static + ?Sized>( + ctx: Context<'gc>, + name: &'static str, + closure: Closure, +) { + ctx.set_global( + name, + UserData::new::>]>( + &ctx, + Gc::new( + &ctx, + ClosureCollect { + inner: Rc::new(closure), + }, + ), + ), + ); +} + +pub fn release_named_global_closure(ctx: Context, name: &'static str) { + ctx.set_global(name, Value::Nil); +}