diff --git a/Cargo.lock b/Cargo.lock
index 43ce28c..66a3a88 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -246,8 +246,7 @@ dependencies = [
[[package]]
name = "gc-arena"
version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cd70cf88a32937834aae9614ff2569b5d9467fa0c42c5d7762fd94a8de88266"
+source = "git+https://github.com/kyren/gc-arena.git?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9"
dependencies = [
"allocator-api2",
"gc-arena-derive",
@@ -258,8 +257,7 @@ dependencies = [
[[package]]
name = "gc-arena-derive"
version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c612a69f5557a11046b77a7408d2836fe77077f842171cd211c5ef504bd3cddd"
+source = "git+https://github.com/kyren/gc-arena.git?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9"
dependencies = [
"proc-macro2",
"quote",
@@ -813,8 +811,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "piccolo"
version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "003bf52de285e1ff1adcbc6572588db3849988ea660a2d55af3a2ffbc81f597f"
+source = "git+https://github.com/kyren/piccolo.git?rev=fcbaabc924292170d6549c55440ecbd0522b275a#fcbaabc924292170d6549c55440ecbd0522b275a"
dependencies = [
"ahash",
"allocator-api2",
diff --git a/Cargo.toml b/Cargo.toml
index 6343494..1cfac65 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,7 +9,7 @@ edition = "2021"
im = "15.1.0"
itertools = "0.13.0"
nom = "7.1.3"
-piccolo = "0.3.3"
+piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc924292170d6549c55440ecbd0522b275a" }
unicode-segmentation = "1.11.0"
unicode-width = "0.1.13"
wasm-bindgen = "0.2.92"
@@ -21,4 +21,4 @@ console_error_panic_hook = "0.1.7"
anyhow = "1.0.86"
serde = "1.0.209"
serde_json = "1.0.127"
-gc-arena = "0.5.3"
+gc-arena = { git = "https://github.com/kyren/gc-arena.git", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9" }
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..3a26366
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+edition = "2021"
diff --git a/src/lua_engine.rs b/src/lua_engine.rs
index a8d5759..628ad94 100644
--- a/src/lua_engine.rs
+++ b/src/lua_engine.rs
@@ -1,8 +1,8 @@
use self::{frames::*, muds::*};
use anyhow::Error;
use piccolo::{
- Closure, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor, StaticError, Table,
- Value, Variadic,
+ Callback, Closure, Context, Executor, ExternError, FromValue, Function, Lua, StashedExecutor,
+ Table, Value, Variadic,
};
use yew::UseStateSetter;
@@ -26,9 +26,9 @@ impl LuaState {
Ok(LuaState { interp, exec })
}
- fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), StaticError> {
+ fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), ExternError> {
self.interp.try_enter(|ctx| {
- let info_table = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?;
+ let info_table = Table::from_value(ctx, ctx.get_global("info")?)?;
info_table.set(
ctx,
ctx.intern_static(b"current_frame"),
@@ -65,9 +65,8 @@ impl LuaState {
) -> Result<(), String> {
self.interp
.try_enter(|ctx| {
- let commands =
- Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
- let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes()));
+ let commands = ctx.get_global::
("commands")?;
+ let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes()))?;
if command_value.is_nil() {
Err(anyhow::Error::msg("Unknown command"))?;
}
@@ -95,70 +94,84 @@ pub fn install_lua_globals(
global_memo: &GlobalMemoCell,
global_layout: UseStateSetter,
) -> Result<(), String> {
- global_memo
- .lua_engine
- .borrow_mut()
- .interp
- .try_enter(|ctx| {
- let cmd_table = Table::new(&ctx);
- macro_rules! register_command {
- ($sym: ident) => {
- cmd_table
- .set(
- ctx,
- ctx.intern_static(stringify!($sym).as_bytes()),
- $sym(ctx, &global_memo, &global_layout),
- )
- .map_err(|_| Error::msg("Can't add command"))?;
- };
- }
+ global_memo.lua_engine.borrow_mut().interp.try_enter(|ctx| {
+ let cmd_table = Table::new(&ctx);
+ macro_rules! register_command {
+ ($sym: ident) => {
+ cmd_table
+ .set(
+ ctx,
+ ctx.intern_static(stringify!($sym).as_bytes()),
+ $sym(ctx, &global_memo, &global_layout),
+ )
+ .map_err(|_| Error::msg("Can't add command"))?;
+ };
+ }
- register_command!(echo);
- register_command!(echo_frame);
- register_command!(echo_frame_raw);
- register_command!(connect_mud);
- register_command!(delete_mud);
- register_command!(close_mud);
- register_command!(sendmud_raw);
- register_command!(hsplit);
- register_command!(panel_merge);
- register_command!(vsplit);
- ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table)
- .map(|_| ())
- .map_err(|_| Error::msg("Can't set commands key"))?;
+ register_command!(echo);
+ register_command!(echo_frame);
+ register_command!(echo_frame_raw);
+ register_command!(connect_mud);
+ register_command!(delete_mud);
+ register_command!(close_mud);
+ register_command!(sendmud_raw);
+ register_command!(hsplit);
+ register_command!(panel_merge);
+ register_command!(vsplit);
+ ctx.set_global("commands", cmd_table);
+ let info_table = Table::new(&ctx);
+ ctx.set_global("info", info_table);
+ let muds_table = Table::new(&ctx);
+ ctx.set_global("muds", muds_table);
- let info_table = Table::new(&ctx);
- ctx.set_global(ctx.intern_static(b"info").into_value(ctx), info_table)
- .map(|_| ())
- .map_err(|_| Error::msg("Can't set info key"))?;
- let muds_table = Table::new(&ctx);
- ctx.set_global(ctx.intern_static(b"muds").into_value(ctx), muds_table)
- .map(|_| ())
- .map_err(|_| Error::msg("Can't set muds key"))?;
+ let handlers_table = Table::new(&ctx);
+ ctx.set_global("handlers", handlers_table);
+ macro_rules! register_handler {
+ ($sym: ident) => {
+ handlers_table
+ .set(
+ ctx,
+ ctx.intern_static(stringify!($sym).as_bytes()),
+ $sym(ctx, &global_memo),
+ )
+ .map_err(|_| Error::msg("Can't add handler"))?;
+ };
+ }
+ register_handler!(mudoutput);
+ register_handler!(mudoutput_line);
+ register_handler!(mudoutput_prompt);
+ register_handler!(mudoutput_will);
+ register_handler!(mudoutput_wont);
+ register_handler!(mudoutput_do);
+ register_handler!(mudoutput_dont);
+ register_handler!(mudoutput_subnegotiation);
- let handlers_table = Table::new(&ctx);
- ctx.set_global(
- ctx.intern_static(b"handlers").into_value(ctx),
- handlers_table,
- )
- .map(|_| ())
- .map_err(|_| Error::msg("Can't set handlers key"))?;
- macro_rules! register_handler {
- ($sym: ident) => {
- handlers_table
- .set(
- ctx,
- ctx.intern_static(stringify!($sym).as_bytes()),
- $sym(ctx, &global_memo),
- )
- .map_err(|_| Error::msg("Can't add handler"))?;
- };
- }
- register_handler!(mudoutput);
+ macro_rules! register_nop_handler {
+ ($sym: ident) => {
+ handlers_table
+ .set(
+ ctx,
+ ctx.intern_static(stringify!($sym).as_bytes()),
+ lua_nop(ctx),
+ )
+ .map_err(|_| Error::msg("Can't add handler"))?;
+ };
+ }
- Ok(())
- })
- .map_err(|e| e.to_string())?;
+ register_nop_handler!(mudoutput_break);
+ register_nop_handler!(mudoutput_sync);
+ register_nop_handler!(mudoutput_interrupt);
+ register_nop_handler!(mudoutput_abort_output);
+ register_nop_handler!(mudoutput_areyouthere);
+
+ Ok(())
+ });
Ok(())
}
+
+pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> {
+ Callback::from_fn(&ctx, |_ctx, _ex, _stack| {
+ Ok(piccolo::CallbackReturn::Return)
+ })
+}
diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs
index 0f6b335..c6f0558 100644
--- a/src/lua_engine/frames.rs
+++ b/src/lua_engine/frames.rs
@@ -16,7 +16,12 @@ pub fn echo_frame_raw<'gc, 'a>(
) -> Callback<'gc> {
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- let frame: TermFrame = try_unwrap_frame(ctx, &stack.pop_front())?;
+ let frame: TermFrame = try_unwrap_frame(
+ ctx,
+ &stack
+ .pop_front()
+ .ok_or_else(|| anyhow::Error::msg("Missing frame"))?,
+ )?;
let message: piccolo::String = stack.from_front(ctx)?;
let message_str = str::from_utf8(message.as_bytes())
.map_err(|_| "Expected message to echo to be UTF-8.".into_value(ctx))?;
@@ -31,14 +36,15 @@ pub fn echo_frame<'gc>(
_global_layout: &UseStateSetter,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- let commands: Table<'gc> =
- Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
- let function = Function::from_value(ctx, commands.get(ctx, "echo_frame_raw"))?;
- let frame_no: Value = stack.pop_front();
+ let commands: Table<'gc> = ctx.get_global("commands")?;
+ let function: Function = commands.get(ctx, "echo_frame_raw")?;
+ let frame_no: Value = stack
+ .pop_front()
+ .ok_or_else(|| anyhow::Error::msg("Missing frame number"))?;
let all_parts: Vec = stack
.consume::>>(ctx)?
.into_iter()
- .map(|v| format!("{}", v))
+ .map(|v| format!("{:?}", v))
.collect();
stack.push_front(frame_no);
let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes());
@@ -56,11 +62,10 @@ pub fn echo<'gc>(
_global_layout: &UseStateSetter,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- let commands: Table<'gc> =
- Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
- let function = Function::from_value(ctx, commands.get(ctx, "echo_frame"))?;
- let info: Table<'gc> = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?;
- let cur_frame = info.get(ctx, ctx.intern_static(b"current_frame"));
+ let commands: Table<'gc> = ctx.get_global("commands")?;
+ let function: Function = commands.get(ctx, "echo_frame")?;
+ let info: Table<'gc> = ctx.get_global("info")?;
+ let cur_frame: Value = info.get(ctx, ctx.intern_static(b"current_frame"))?;
stack.push_front(cur_frame);
Ok(piccolo::CallbackReturn::Call {
function,
diff --git a/src/lua_engine/muds.rs b/src/lua_engine/muds.rs
index be9af5a..a6058c8 100644
--- a/src/lua_engine/muds.rs
+++ b/src/lua_engine/muds.rs
@@ -1,6 +1,9 @@
use anyhow::Error;
use gc_arena::{Gc, Rootable};
-use piccolo::{self, Callback, Context, FromValue, Function, Table, UserData, Value};
+use piccolo::{
+ self, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, StashedValue, Table,
+ UserData, Value,
+};
use wasm_bindgen::JsValue;
use web_sys::console;
use yew::UseStateSetter;
@@ -8,6 +11,7 @@ use yew::UseStateSetter;
use crate::{
command_handler::debrace,
id_intern::{intern_id, unintern_id},
+ telnet::{parse_telnet_buf, TelnetOutput},
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
GlobalLayoutCell, GlobalMemoCell,
};
@@ -19,7 +23,7 @@ fn try_unwrap_socketid<'gc>(
value: &Value<'gc>,
) -> Result> {
let value = if let Ok(sockname) = String::from_value(ctx, *value) {
- let ret = Table::from_value(ctx, ctx.get_global("muds"))?.get(ctx, sockname);
+ let ret: Value<'gc> = ctx.get_global::>("muds")?.get(ctx, sockname)?;
if ret.is_nil() {
Err(Error::msg(
"Could not find a MUD connection with that name.",
@@ -30,7 +34,7 @@ fn try_unwrap_socketid<'gc>(
*value
};
Ok(UserData::from_value(ctx, value)?
- .downcast:: Gc<'gcb, WebSocketId>]>()?
+ .downcast::]>()?
.as_ref()
.clone())
}
@@ -43,9 +47,8 @@ pub fn handle_websocket_output(
engine
.interp
.try_enter(|ctx| {
- let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?;
- let input_fn =
- Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudoutput")))?;
+ let handlers: Table = ctx.get_global("handlers")?;
+ let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudoutput"))?;
ctx.fetch(&engine.exec).restart(
ctx,
input_fn,
@@ -77,9 +80,8 @@ pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Re
engine
.interp
.try_enter(|ctx| {
- let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?;
- let input_fn =
- Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudclose")))?;
+ let handlers: Table = ctx.get_global("handlers")?;
+ let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudclose"))?;
ctx.fetch(&engine.exec).restart(
ctx,
input_fn,
@@ -111,7 +113,12 @@ pub(super) fn sendmud_raw<'gc>(
) -> Callback<'gc> {
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- let mud: WebSocketId = try_unwrap_socketid(ctx, &stack.pop_front())?;
+ let mud: WebSocketId = try_unwrap_socketid(
+ ctx,
+ &stack
+ .pop_front()
+ .ok_or_else(|| anyhow::Error::msg("Missing MUD argument"))?,
+ )?;
let msg: piccolo::String = stack.from_front(ctx)?;
send_message_to_mud(&mud, msg.as_bytes(), &global_memo)?;
Ok(piccolo::CallbackReturn::Return)
@@ -136,8 +143,8 @@ pub(super) fn connect_mud<'gc>(
};
let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into();
- let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?;
- if !muds.get_value(name).is_nil() {
+ let muds: Table = ctx.get_global("muds")?;
+ if !muds.get_value(ctx, name).is_nil() {
Err(Error::msg(
"Attempt to create MUD connection using name that's already taken",
))?
@@ -170,7 +177,9 @@ pub(super) fn close_mud<'gc>(
) -> Callback<'gc> {
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- let mud_value: Value<'gc> = stack.pop_front();
+ let mud_value: Value<'gc> = stack
+ .pop_front()
+ .ok_or_else(|| anyhow::Error::msg("Missing MUD value argument"))?;
let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) {
None => Err(Error::msg("That MUD connection doesn't exist."))?,
@@ -197,8 +206,8 @@ pub(super) fn delete_mud<'gc>(
let name: String = stack.from_front(ctx)?;
let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into();
- let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?;
- let mud_value = muds.get_value(name);
+ let muds: Table = ctx.get_global("muds")?;
+ let mud_value = muds.get_value(ctx, name);
if mud_value.is_nil() {
Err(Error::msg(
"Attempt to delete MUD connection that wasn't found",
@@ -208,7 +217,7 @@ pub(super) fn delete_mud<'gc>(
let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
// Delete the MUD data if possible.
- let _ = muds.set_value(&ctx, mud_value, Value::Nil);
+ let _ = muds.set_raw(&ctx, mud_value, Value::Nil)?;
for (k, v) in muds.iter() {
match UserData::from_value(ctx, v) {
Err(_) => continue,
@@ -218,7 +227,7 @@ pub(super) fn delete_mud<'gc>(
_ => {}
},
}
- let _ = muds.set_value(&ctx, k, Value::Nil);
+ let _ = muds.set_raw(&ctx, k, Value::Nil);
}
unintern_id(ctx, &socket_id);
match global_memo.ws_registry.borrow_mut().remove(&socket_id) {
@@ -231,28 +240,124 @@ pub(super) fn delete_mud<'gc>(
})
}
-pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell) -> Callback<'gc> {
- let global_memo = global_memo.clone();
+pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
- // Temporary hack for testing... alias to echo
- let echo = Function::from_value(
+ let mud: Value<'gc> = stack
+ .pop_front()
+ .ok_or_else(|| anyhow::Error::msg("Missing argument to mudoutput"))?;
+ let output: &'gc [u8] = stack.from_front::>(ctx)?.as_bytes();
+
+ let conntab: Table<'gc> = Table::from_value(
ctx,
- Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?
- .get(ctx, ctx.intern_static(b"echo")),
+ Table::from_value(ctx, ctx.get_global("muds")?)?.get_value(ctx, mud),
)?;
- let mud: Value<'gc> = stack.pop_front();
- let output: piccolo::String<'gc> = stack.from_front(ctx)?;
+ let buf: &'gc [u8] =
+ piccolo::String::from_value(ctx, conntab.get(ctx, ctx.intern_static(b"buffer"))?)?
+ .as_bytes();
+ let mut cur_buf: Vec = [buf, output].concat();
- let conntab =
- Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?.get_value(mud);
- if conntab.is_nil() {
- Err(Error::msg("Received input from MUD not in muds table."))?
+ let handlers = Table::from_value(ctx, ctx.get_global("handlers")?)?;
+
+ let mut fns: Vec<(&'static [u8], Vec)> = vec![];
+ loop {
+ match parse_telnet_buf(&cur_buf) {
+ (new_buf, None) => {
+ conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern(&new_buf))?;
+
+ let seq = piccolo::async_sequence(&ctx, |locals, seq| async {
+ Ok(piccolo::SequenceReturn::Return)
+ });
+
+ return Ok(piccolo::CallbackReturn::Sequence(seq));
+ }
+ (new_buf, Some(cmd)) => {
+ cur_buf = new_buf;
+ match cmd {
+ TelnetOutput::Line(l) => fns.push((
+ b"mudoutput_line",
+ vec![ctx.stash(mud), ctx.stash(ctx.intern(&l).into_value(ctx))],
+ )),
+ TelnetOutput::Prompt(p) => fns.push((
+ b"mudoutput_prompt",
+ vec![ctx.stash(mud), ctx.stash(ctx.intern(&p).into_value(ctx))],
+ )),
+ TelnetOutput::Nop => {}
+ TelnetOutput::Break => fns.push((b"mudoutput_break", vec![ctx.stash(mud)])),
+ TelnetOutput::Sync => fns.push((b"mudoutput_sync", vec![ctx.stash(mud)])),
+ TelnetOutput::Interrupt => {
+ fns.push((b"mudoutput_interrupt", vec![ctx.stash(mud)]))
+ }
+
+ TelnetOutput::AbortOutput => {
+ fns.push((b"mudoutput_abort_output", vec![ctx.stash(mud)]))
+ }
+ TelnetOutput::AreYouThere => {
+ fns.push((b"mudoutput_areyouthere", vec![ctx.stash(mud)]))
+ }
+ TelnetOutput::Will(v) => fns.push((
+ b"mudoutput_will",
+ vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
+ )),
+ TelnetOutput::Wont(v) => fns.push((
+ b"mudoutput_wont",
+ vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
+ )),
+ TelnetOutput::Do(v) => fns.push((
+ b"mudoutput_do",
+ vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
+ )),
+ TelnetOutput::Dont(v) => fns.push((
+ b"mudoutput_dont",
+ vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
+ )),
+ TelnetOutput::Subnegotiation(t) => fns.push((
+ b"mudoutput_subnegotiation",
+ vec![ctx.stash(mud), ctx.stash(t.into_value(ctx))],
+ )),
+ }
+ }
+ }
}
-
- Ok(piccolo::CallbackReturn::Call {
- function: echo,
- then: None,
- })
})
}
+
+pub(super) fn mudoutput_line<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_prompt<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_will<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_wont<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_dont<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
+pub(super) fn mudoutput_subnegotiation<'gc>(
+ ctx: Context<'gc>,
+ _global_memo: &GlobalMemoCell,
+) -> Callback<'gc> {
+ Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
+}
diff --git a/src/main.rs b/src/main.rs
index 1ff7e17..22a013b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ pub mod lineengine;
pub mod lua_engine;
pub mod parsing;
pub mod split_panel;
+pub mod telnet;
pub mod term_split;
pub mod term_view;
pub mod websocket;
diff --git a/src/telnet.rs b/src/telnet.rs
new file mode 100644
index 0000000..18f0518
--- /dev/null
+++ b/src/telnet.rs
@@ -0,0 +1,288 @@
+use wasm_bindgen::JsValue;
+use web_sys::console;
+
+#[derive(Eq, PartialEq, Debug)]
+pub enum TelnetOutput {
+ Line(Vec),
+ Prompt(Vec), // Like a line but without a newline.
+ Nop,
+ Break,
+ Sync,
+ Interrupt,
+ AbortOutput,
+ AreYouThere,
+ Will(u8),
+ Wont(u8),
+ Do(u8),
+ Dont(u8),
+ Subnegotiation(Vec),
+}
+
+const IAC: u8 = 255;
+const NOP: u8 = 241;
+const DATA_MARK: u8 = 242;
+const BREAK: u8 = 243;
+const INTERRUPT: u8 = 244;
+const ABORT: u8 = 245;
+const AYT: u8 = 246;
+const ERASECHAR: u8 = 247;
+const ERASELINE: u8 = 248;
+const GOAHEAD: u8 = 249;
+const EOR: u8 = 239;
+const STARTSUB: u8 = 250;
+const ENDSUB: u8 = 240;
+const WILL: u8 = 251;
+const WONT: u8 = 252;
+const DO: u8 = 253;
+const DONT: u8 = 254;
+
+pub fn parse_telnet_buf(input: &[u8]) -> (Vec, Option) {
+ let mut ptr: &[u8] = input;
+ let mut textbuf: Vec = vec![];
+ loop {
+ match ptr.first() {
+ None => return (textbuf.to_owned(), None),
+ Some(b'\n') => {
+ textbuf.push(b'\n');
+ return (ptr[1..].to_owned(), Some(TelnetOutput::Line(textbuf)));
+ }
+ Some(&IAC) => {
+ ptr = &ptr[1..];
+ match ptr.first() {
+ None => {
+ textbuf.push(IAC);
+ return (textbuf, None);
+ }
+ Some(&NOP) => {}
+ Some(&DATA_MARK) => {}
+ Some(&BREAK) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Break));
+ }
+ Some(&INTERRUPT) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Interrupt));
+ }
+ Some(&ABORT) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::AbortOutput));
+ }
+ Some(&AYT) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::AreYouThere));
+ }
+ Some(&ERASECHAR) => {
+ if !textbuf.is_empty() {
+ textbuf = textbuf[0..textbuf.len() - 1].to_owned();
+ }
+ }
+ Some(&ERASELINE) => {
+ textbuf = vec![];
+ }
+ Some(&GOAHEAD) => {
+ if textbuf.len() > 1 {
+ return (ptr[1..].to_owned(), Some(TelnetOutput::Prompt(textbuf)));
+ }
+ }
+ Some(&EOR) => {
+ if textbuf.len() > 1 {
+ return (ptr[1..].to_owned(), Some(TelnetOutput::Prompt(textbuf)));
+ }
+ }
+ Some(&WILL) => {
+ ptr = &ptr[1..];
+ match ptr.first() {
+ None => {
+ textbuf.push(IAC);
+ textbuf.push(WILL);
+ return (textbuf, None);
+ }
+ Some(c) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Will(*c)));
+ }
+ }
+ }
+ Some(&WONT) => {
+ ptr = &ptr[1..];
+ match ptr.first() {
+ None => {
+ textbuf.push(IAC);
+ textbuf.push(WONT);
+ return (textbuf, None);
+ }
+ Some(c) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Wont(*c)));
+ }
+ }
+ }
+ Some(&DO) => {
+ ptr = &ptr[1..];
+ match ptr.first() {
+ None => {
+ textbuf.push(IAC);
+ textbuf.push(DO);
+ return (textbuf, None);
+ }
+ Some(c) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Do(*c)));
+ }
+ }
+ }
+ Some(&DONT) => {
+ ptr = &ptr[1..];
+ match ptr.first() {
+ None => {
+ textbuf.push(IAC);
+ textbuf.push(DONT);
+ return (textbuf, None);
+ }
+ Some(c) => {
+ textbuf.extend_from_slice(&ptr[1..]);
+ return (textbuf, Some(TelnetOutput::Dont(*c)));
+ }
+ }
+ }
+ Some(&STARTSUB) => {
+ match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) {
+ None => {
+ // Including the STARTSUB...
+ textbuf.push(IAC);
+ textbuf.extend_from_slice(ptr);
+ return (textbuf, None);
+ }
+ Some(end_idx) => {
+ textbuf.extend_from_slice(&ptr[end_idx + 2..]);
+ return (
+ textbuf,
+ Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())),
+ );
+ }
+ }
+ }
+ Some(c) => {
+ // Completely unexpected command. Warn and ignore.
+ console::log_1(&JsValue::from_str(&format!(
+ "Received unknown IAC command {}, assuming single byte and ignoring.",
+ c
+ )));
+ }
+ }
+ }
+ Some(c) => textbuf.push(*c),
+ }
+ ptr = &ptr[1..];
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parse_telnet_works() {
+ assert_eq!(parse_telnet_buf(b"Hello"), (b"Hello".to_vec(), None));
+ assert_eq!(
+ parse_telnet_buf(b"Hello\r\n"),
+ (
+ b"".to_vec(),
+ Some(TelnetOutput::Line(b"Hello\r\n".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello\r\nWorld"),
+ (
+ b"World".to_vec(),
+ Some(TelnetOutput::Line(b"Hello\r\n".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello> \xff\xf9World"), // Go Ahead after prompt.
+ (
+ b"World".to_vec(),
+ Some(TelnetOutput::Prompt(b"Hello> ".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello> \xff\xefWorld"), // End Of Record after prompt.
+ (
+ b"World".to_vec(),
+ Some(TelnetOutput::Prompt(b"Hello> ".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf1World\r\nWoo!"), // NOP in the middle.
+ (
+ b"Woo!".to_vec(),
+ Some(TelnetOutput::Line(b"Hello World\r\n".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf2World\r\nWoo!"), // DATA MARK in the middle.
+ (
+ b"Woo!".to_vec(),
+ Some(TelnetOutput::Line(b"Hello World\r\n".to_vec()))
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf3World\r\nWoo!"), // BREAK in the middle.
+ (b"Hello World\r\nWoo!".to_vec(), Some(TelnetOutput::Break))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf4World\r\nWoo!"), // INTERRUPT in the middle.
+ (
+ b"Hello World\r\nWoo!".to_vec(),
+ Some(TelnetOutput::Interrupt)
+ )
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff"), // Incomplete IAC command.
+ (b"Hello \xff".to_vec(), None)
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf5World!"), // Abort
+ (b"Hello World!".to_vec(), Some(TelnetOutput::AbortOutput))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xf6World!"), // Are You There
+ (b"Hello World!".to_vec(), Some(TelnetOutput::AreYouThere))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello B\xff\xf7World!"), // Erase Char
+ (b"Hello World!".to_vec(), None)
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Whoops \xff\xf8Hello World!"), // Erase Line
+ (b"Hello World!".to_vec(), None)
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfb\x01World!"), // Will
+ (b"Hello World!".to_vec(), Some(TelnetOutput::Will(1)))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfc\x02World!"), // Wont
+ (b"Hello World!".to_vec(), Some(TelnetOutput::Wont(2)))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfd\x03World!"), // Do
+ (b"Hello World!".to_vec(), Some(TelnetOutput::Do(3)))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfe\x04World!"), // Dont
+ (b"Hello World!".to_vec(), Some(TelnetOutput::Dont(4)))
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfablah"), // Partial subnegotiation
+ (b"Hello \xff\xfablah".to_vec(), None)
+ );
+ assert_eq!(
+ parse_telnet_buf(b"Hello \xff\xfablah\xff\xf0World!"), // Partial subnegotiation
+ (
+ b"Hello World!".to_vec(),
+ Some(TelnetOutput::Subnegotiation(b"blah".to_vec()))
+ )
+ );
+ }
+}
diff --git a/src/term_split.rs b/src/term_split.rs
index b03ff3b..59abf58 100644
--- a/src/term_split.rs
+++ b/src/term_split.rs
@@ -216,36 +216,30 @@ mod tests {
#[test]
fn modify_at_pathstr_works() {
use TermSplit::*;
- let mut t = Term {
+ let t = Term {
frame: TermFrame(1),
};
assert_eq!(
- t.modify_at_pathstr("", |v| {
- *v = Term {
+ t.modify_at_pathstr("", |_v| {
+ Ok(Term {
frame: TermFrame(2),
- };
- Ok(())
+ })
}),
- Ok(())
- );
- assert_eq!(
- t,
- Term {
- frame: TermFrame(2)
- }
+ Ok(Term {
+ frame: TermFrame(2),
+ })
);
assert_eq!(
- t.modify_at_pathstr("tlr", |v| {
- *v = Term {
+ t.modify_at_pathstr("tlr", |_v| {
+ Ok(Term {
frame: TermFrame(2),
- };
- Ok(())
+ })
}),
Err("In split path, found trailing junk tlr after addressing terminal".to_owned())
);
- let mut t = Vertical {
+ let t = Vertical {
top: Horizontal {
left: Horizontal {
left: Term {
@@ -277,26 +271,17 @@ mod tests {
.into(),
};
assert_eq!(
- t.modify_at_pathstr("tlr", |v| {
- *v = Term {
+ t.modify_at_pathstr("tlr", |_v| {
+ Ok(Term {
frame: TermFrame(2),
- };
- Ok(())
- }),
- Ok(())
- );
- assert_eq!(
- t.modify_at_pathstr("bb", |v| {
- *v = Term {
+ })
+ })
+ .and_then(|t| t.modify_at_pathstr("bb", |_v| {
+ Ok(Term {
frame: TermFrame(3),
- };
- Ok(())
- }),
- Ok(())
- );
- assert_eq!(
- t,
- Vertical {
+ })
+ })),
+ Ok(Vertical {
top: Horizontal {
left: Horizontal {
left: Term {
@@ -326,7 +311,7 @@ mod tests {
.into(),
}
.into(),
- }
+ })
);
}
}