diff --git a/src/command_handler.rs b/src/command_handler.rs index 882838d..4999513 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -1,10 +1,10 @@ use itertools::join; use crate::{ - echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, GlobalCell, TermFrame, + echo_to_term_frame, lua_state::LuaState, parsing::parse_commands, GlobalMemoCell, TermFrame, }; -fn debrace(inp: &str) -> &str { +pub fn debrace(inp: &str) -> &str { let v = inp.trim(); if v.starts_with("{") && v.ends_with("}") { &v[1..(v.len() - 1)] @@ -15,7 +15,7 @@ fn debrace(inp: &str) -> &str { fn reentrant_command_handler( lua_state: &mut LuaState, - globals: &GlobalCell, + globals: &GlobalMemoCell, term_frame: &TermFrame, command_in: &str, ) { @@ -58,7 +58,7 @@ fn reentrant_command_handler( } } -pub fn command_handler(globals: &GlobalCell, term_frame: &TermFrame, command_in: &str) { +pub fn command_handler(globals: &GlobalMemoCell, term_frame: &TermFrame, command_in: &str) { match globals.lua_engine.try_borrow_mut() { Err(_) => echo_to_term_frame( globals, diff --git a/src/lineengine/line.rs b/src/lineengine/line.rs index 2826c5a..9e59014 100644 --- a/src/lineengine/line.rs +++ b/src/lineengine/line.rs @@ -498,11 +498,6 @@ impl Readline { } pub fn handle_resize(&mut self, term_size: (u16, u16)) -> Result<(), ReadlineError> { - web_sys::console::log_3( - &"Doing resize event".into(), - &term_size.0.into(), - &term_size.1.into(), - ); self.line_state.handle_event( Event::Resize(term_size.0, term_size.1), &mut self.write_term, diff --git a/src/lua_state.rs b/src/lua_state.rs index abf5800..bcc2932 100644 --- a/src/lua_state.rs +++ b/src/lua_state.rs @@ -3,9 +3,13 @@ use piccolo::{ Callback, Closure, Context, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor, StaticError, Table, Value, Variadic, }; +use yew::UseStateSetter; -use crate::{echo_to_term_frame, GlobalCell, TermFrame}; -use std::{collections::VecDeque, str}; +use crate::{ + command_handler::debrace, echo_to_term_frame, GlobalLayoutCell, GlobalLayoutState, + GlobalMemoCell, TermFrame, +}; +use std::{collections::VecDeque, rc::Rc, str}; pub struct LuaState { pub interp: Lua, @@ -82,8 +86,11 @@ impl LuaState { } } -pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> { - global +pub fn install_lua_globals( + global_memo: &GlobalMemoCell, + global_layout: UseStateSetter, +) -> Result<(), String> { + global_memo .lua_engine .borrow_mut() .interp @@ -95,15 +102,16 @@ pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> { .set( ctx, ctx.intern_static(stringify!($sym).as_bytes()), - $sym(ctx, global.clone()), + $sym(ctx, &global_memo, &global_layout), ) .map_err(|_| Error::msg("Can't add command"))?; }; } - register_command!(echo_frame_raw); - register_command!(echo_frame); register_command!(echo); + register_command!(echo_frame); + register_command!(echo_frame_raw); + 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"))?; @@ -120,19 +128,28 @@ pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> { Ok(()) } -fn echo_frame_raw(ctx: Context, global: GlobalCell) -> Callback { +fn echo_frame_raw<'gc, 'a>( + ctx: Context<'gc>, + global_memo: &'a GlobalMemoCell, + _global_layout: &'a UseStateSetter, +) -> Callback<'gc> { + let global_memo = global_memo.clone(); Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { let frame_no: u64 = stack.from_front(ctx)?; 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))?; - echo_to_term_frame(&global, &TermFrame(frame_no), message_str) + echo_to_term_frame(&global_memo, &TermFrame(frame_no), message_str) .map_err(|m| m.into_value(ctx))?; Ok(piccolo::CallbackReturn::Return) }) } -fn echo_frame<'gc>(ctx: Context<'gc>, _global: GlobalCell) -> Callback<'gc> { +fn echo_frame<'gc>( + ctx: Context<'gc>, + _global_memo: &GlobalMemoCell, + _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")))?; @@ -153,7 +170,11 @@ fn echo_frame<'gc>(ctx: Context<'gc>, _global: GlobalCell) -> Callback<'gc> { }) } -fn echo<'gc>(ctx: Context<'gc>, _global: GlobalCell) -> Callback<'gc> { +fn echo<'gc>( + ctx: Context<'gc>, + _global_memo: &GlobalMemoCell, + _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")))?; @@ -167,3 +188,29 @@ fn echo<'gc>(ctx: Context<'gc>, _global: GlobalCell) -> Callback<'gc> { }) }) } + +fn vsplit<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_layout = global_layout.clone(); + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let path: String = stack.from_front(ctx)?; + let path: &str = debrace(&path); + let frame: u64 = stack.from_front(ctx)?; + let new_splits = global_memo + .layout + .borrow() + .term_splits + .vsplit(path, TermFrame(frame)) + .map_err(|e| e.into_value(ctx))?; + let new_layout = Rc::new(GlobalLayoutState { + term_splits: new_splits.clone(), + }); + global_layout.set(new_layout.clone()); + *(global_memo.layout.borrow_mut()) = new_layout; + Ok(piccolo::CallbackReturn::Return) + }) +} diff --git a/src/main.rs b/src/main.rs index 7bc4a27..8ae0775 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,38 +16,61 @@ use crate::split_panel::*; use crate::term_view::*; #[derive(Properties)] -pub struct GlobalState { +pub struct GlobalMemoState { // No strong references allowed between each of these groups of state. frame_registry: RefCell, lua_engine: RefCell, - term_splits: RefCell, -} -type GlobalCell = Rc; -// Used only for yew. Always equal since we don't want it to -// actually look into GlobalState, changes there should never -// cause a re-render. -impl PartialEq for GlobalState { + // A cache of the latest layout info (separate from the state). + // Updating this doesn't force a relayout, so only update the cache when + // you also emit a change to the real layout. + layout: RefCell>, +} +type GlobalMemoCell = Rc; + +// Used only for yew. Lua and Frame Registry excluded, as +// changes there should never cause a re-render. +impl PartialEq for GlobalMemoState { fn eq(&self, _other: &Self) -> bool { true } } +// Used for state that impacts the entire layout. Changes here will +// cause a re-render +#[derive(PartialEq, Properties)] +pub struct GlobalLayoutState { + term_splits: TermSplit, +} +// Note: Despite interior mutability, you still should always call the +// setter to force a re-render. Interior mutability is used to allow this +// to be baked into the Lua closures and still allow access to the current +// value. +type GlobalLayoutCell = Rc; + #[function_component(App)] fn app() -> Html { - let global = use_memo((), |_| GlobalState { + let global_layout: UseStateHandle = use_state(|| { + Rc::new(GlobalLayoutState { + term_splits: TermSplit::Term { + frame: TermFrame(1), + }, + }) + }); + let global_memo = use_memo((), |_| GlobalMemoState { frame_registry: RegisteredTermFrames::new().into(), lua_engine: LuaState::setup().expect("Can create interpreter").into(), - term_splits: TermSplit::Term { - frame: TermFrame(1), - } - .into(), + layout: RefCell::new((*global_layout).clone()), + }); + use_memo((), |_| { + install_lua_globals(&global_memo, global_layout.setter()) + .expect("Couldn't install Lua globals") }); - install_lua_globals(&global).expect("Couldn't install Lua globals"); html! {
- +
} } diff --git a/src/term_split.rs b/src/term_split.rs index 43c4a4e..b03ff3b 100644 --- a/src/term_split.rs +++ b/src/term_split.rs @@ -1,5 +1,5 @@ use itertools::Itertools; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, rc::Rc}; use crate::TermFrame; @@ -9,12 +9,12 @@ pub enum TermSplit { frame: TermFrame, }, Horizontal { - left: Box, - right: Box, + left: Rc, + right: Rc, }, Vertical { - top: Box, - bottom: Box, + top: Rc, + bottom: Rc, }, } @@ -60,28 +60,40 @@ impl TermSplit { Ok(()) } - pub fn modify_at_pathstr(&mut self, pathstr: &str, mod_with: F) -> Result<(), String> + pub fn modify_at_pathstr(&self, pathstr: &str, mod_with: F) -> Result where - F: FnOnce(&mut TermSplit) -> Result<(), String>, + F: FnOnce(&TermSplit) -> Result, { self.modify_at_pathstr_vec(&pathstr.chars().collect::>(), mod_with) } - fn modify_at_pathstr_vec(&mut self, pathstr: &[char], mod_with: F) -> Result<(), String> + fn modify_at_pathstr_vec(&self, pathstr: &[char], mod_with: F) -> Result where - F: FnOnce(&mut TermSplit) -> Result<(), String>, + F: FnOnce(&TermSplit) -> Result, { match self { TermSplit::Horizontal { left, right } => match pathstr.split_first() { None => mod_with(self), - Some(('l', path_rest)) => left.modify_at_pathstr_vec(path_rest, mod_with), - Some(('r', path_rest)) => right.modify_at_pathstr_vec(path_rest, mod_with), + Some(('l', path_rest)) => Ok(TermSplit::Horizontal { + left: left.modify_at_pathstr_vec(path_rest, mod_with)?.into(), + right: right.clone() + }), + Some(('r', path_rest)) => Ok(TermSplit::Horizontal { + left: left.clone(), + right: right.modify_at_pathstr_vec(path_rest, mod_with)?.into() + }), Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a horizontal split", c, path_rest.iter().collect::())) }, TermSplit::Vertical { top, bottom } => match pathstr.split_first() { None => mod_with(self), - Some(('t', path_rest)) => top.modify_at_pathstr_vec(path_rest, mod_with), - Some(('b', path_rest)) => bottom.modify_at_pathstr_vec(path_rest, mod_with), + Some(('t', path_rest)) => Ok(TermSplit::Vertical { + top: top.modify_at_pathstr_vec(path_rest, mod_with)?.into(), + bottom: bottom.clone() + }), + Some(('b', path_rest)) => Ok(TermSplit::Vertical { + top: top.clone(), + bottom: bottom.modify_at_pathstr_vec(path_rest, mod_with)?.into() + }), Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a vertical split", c, path_rest.iter().collect::())) }, TermSplit::Term { .. } => match pathstr.split_first() { @@ -91,47 +103,35 @@ impl TermSplit { } } - pub fn hsplit(&mut self, pathstr: &str, new_frame: TermFrame) -> Result<(), String> { - let mut new = self.clone(); - new.modify_at_pathstr(pathstr, move |n| { - *n = TermSplit::Horizontal { + pub fn hsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result { + let new = self.modify_at_pathstr(pathstr, move |n| { + Ok(TermSplit::Horizontal { left: n.clone().into(), right: TermSplit::Term { frame: new_frame }.into(), - }; - Ok(()) + }) })?; new.validate()?; - *self = new; - Ok(()) + Ok(new) } - pub fn vsplit(&mut self, pathstr: &str, new_frame: TermFrame) -> Result<(), String> { - let mut new = self.clone(); - new.modify_at_pathstr(pathstr, move |n| { - *n = TermSplit::Vertical { + pub fn vsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result { + let new = self.modify_at_pathstr(pathstr, move |n| { + Ok(TermSplit::Vertical { top: n.clone().into(), bottom: TermSplit::Term { frame: new_frame }.into(), - }; - Ok(()) + }) })?; new.validate()?; - *self = new; - Ok(()) + Ok(new) } - pub fn join(&mut self, pathstr: &str) -> Result<(), String> { + pub fn join(&self, pathstr: &str) -> Result { self.modify_at_pathstr(pathstr, move |n| match n { TermSplit::Term { .. } => { Err("Can only join vertical or horizontal splits, not a terminal".to_owned()) } - TermSplit::Horizontal { left, .. } => { - *n = (**left).clone(); - Ok(()) - } - TermSplit::Vertical { top, .. } => { - *n = (**top).clone(); - Ok(()) - } + TermSplit::Horizontal { left, .. } => Ok((**left).clone()), + TermSplit::Vertical { top, .. } => Ok((**top).clone()), }) } } diff --git a/src/term_view.rs b/src/term_view.rs index e8c9f7b..73840dc 100644 --- a/src/term_view.rs +++ b/src/term_view.rs @@ -8,7 +8,7 @@ use crate::{ command_handler::command_handler, lineengine::line::{Readline, ReadlineEvent}, term_split::TermSplit, - GlobalCell, PanelDirection, SplitPanel, + GlobalLayoutCell, GlobalMemoCell, PanelDirection, SplitPanel, }; #[wasm_bindgen] @@ -95,7 +95,7 @@ fn get_or_make_term_frame<'a>( frame: &TermFrame, frames: &'a mut RegisteredTermFrames, // Only used for callbacks, expected frames already borrowed mut! - globals: &GlobalCell, + globals: &GlobalMemoCell, ) -> &'a TermFrameData { frames.entry(frame.clone()).or_insert_with(|| { let term = Terminal::new(); @@ -141,9 +141,8 @@ fn get_or_make_term_frame<'a>( }; let frame_for_resize: TermFrame = frame.clone(); - let globals_for_resize: GlobalCell = globals.clone(); + let globals_for_resize: GlobalMemoCell = globals.clone(); let resize_closure = Closure::new(move |dims: Dims| { - web_sys::console::log_1(&"Resize closure called".into()); globals_for_resize .frame_registry .try_borrow_mut() @@ -161,7 +160,7 @@ fn get_or_make_term_frame<'a>( term.onResize(&resize_closure); let frame_for_ondata: TermFrame = frame.clone(); - let globals_for_ondata: GlobalCell = globals.clone(); + let globals_for_ondata: GlobalMemoCell = globals.clone(); let data_closure = Closure::new(move |d: String| { let new_lines: Vec = match globals_for_ondata .frame_registry @@ -200,13 +199,13 @@ fn get_or_make_term_frame<'a>( #[derive(Properties, PartialEq)] pub struct TermViewProps { pub terminal: TermFrame, - pub global: GlobalCell, + pub global_memo: GlobalMemoCell, } #[function_component(TermView)] pub fn term_view(props: &TermViewProps) -> Html { - let mut frame_reg = props.global.frame_registry.borrow_mut(); - let term = get_or_make_term_frame(&props.terminal, &mut frame_reg, &props.global); + let mut frame_reg = props.global_memo.frame_registry.borrow_mut(); + let term = get_or_make_term_frame(&props.terminal, &mut frame_reg, &props.global_memo); term.fit.fit(); html! { {Html::VRef(term.node.clone())} @@ -215,16 +214,17 @@ pub fn term_view(props: &TermViewProps) -> Html { #[derive(Properties, PartialEq)] pub struct TermViewTreeProps { - pub global: GlobalCell, + pub global_memo: GlobalMemoCell, + pub global_layout: UseStateHandle, } #[function_component(TermViewTree)] pub fn term_view_tree(props: &TermViewTreeProps) -> Html { - fn mk_term_view_tree(global: &GlobalCell, split: &TermSplit) -> Html { + fn mk_term_view_tree(global: &GlobalMemoCell, split: &TermSplit) -> Html { use TermSplit::*; match split { Term { frame } => html! { - + }, Horizontal { left, right } => html! { Html { } } - mk_term_view_tree(&props.global, &props.global.term_splits.borrow()) + mk_term_view_tree(&props.global_memo, &props.global_layout.term_splits) } pub fn echo_to_term_frame( - global: &GlobalCell, + global: &GlobalMemoCell, frame_id: &TermFrame, message: &str, ) -> Result<(), &'static str> {