Support vsplit from in Lua.

This commit is contained in:
Condorra 2024-08-23 23:37:58 +10:00
parent 525f69296e
commit c32d7ac2bd
6 changed files with 151 additions and 86 deletions

View File

@ -1,10 +1,10 @@
use itertools::join; use itertools::join;
use crate::{ 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(); let v = inp.trim();
if v.starts_with("{") && v.ends_with("}") { if v.starts_with("{") && v.ends_with("}") {
&v[1..(v.len() - 1)] &v[1..(v.len() - 1)]
@ -15,7 +15,7 @@ fn debrace(inp: &str) -> &str {
fn reentrant_command_handler( fn reentrant_command_handler(
lua_state: &mut LuaState, lua_state: &mut LuaState,
globals: &GlobalCell, globals: &GlobalMemoCell,
term_frame: &TermFrame, term_frame: &TermFrame,
command_in: &str, 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() { match globals.lua_engine.try_borrow_mut() {
Err(_) => echo_to_term_frame( Err(_) => echo_to_term_frame(
globals, globals,

View File

@ -498,11 +498,6 @@ impl Readline {
} }
pub fn handle_resize(&mut self, term_size: (u16, u16)) -> Result<(), ReadlineError> { 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( self.line_state.handle_event(
Event::Resize(term_size.0, term_size.1), Event::Resize(term_size.0, term_size.1),
&mut self.write_term, &mut self.write_term,

View File

@ -3,9 +3,13 @@ use piccolo::{
Callback, Closure, Context, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor, Callback, Closure, Context, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor,
StaticError, Table, Value, Variadic, StaticError, Table, Value, Variadic,
}; };
use yew::UseStateSetter;
use crate::{echo_to_term_frame, GlobalCell, TermFrame}; use crate::{
use std::{collections::VecDeque, str}; command_handler::debrace, echo_to_term_frame, GlobalLayoutCell, GlobalLayoutState,
GlobalMemoCell, TermFrame,
};
use std::{collections::VecDeque, rc::Rc, str};
pub struct LuaState { pub struct LuaState {
pub interp: Lua, pub interp: Lua,
@ -82,8 +86,11 @@ impl LuaState {
} }
} }
pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> { pub fn install_lua_globals(
global global_memo: &GlobalMemoCell,
global_layout: UseStateSetter<GlobalLayoutCell>,
) -> Result<(), String> {
global_memo
.lua_engine .lua_engine
.borrow_mut() .borrow_mut()
.interp .interp
@ -95,15 +102,16 @@ pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> {
.set( .set(
ctx, ctx,
ctx.intern_static(stringify!($sym).as_bytes()), 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"))?; .map_err(|_| Error::msg("Can't add command"))?;
}; };
} }
register_command!(echo_frame_raw);
register_command!(echo_frame);
register_command!(echo); 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) ctx.set_global(ctx.intern_static(b"commands").into_value(ctx), cmd_table)
.map(|_| ()) .map(|_| ())
.map_err(|_| Error::msg("Can't set commands key"))?; .map_err(|_| Error::msg("Can't set commands key"))?;
@ -120,19 +128,28 @@ pub fn install_lua_globals(global: &GlobalCell) -> Result<(), String> {
Ok(()) 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<GlobalLayoutCell>,
) -> Callback<'gc> {
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let frame_no: u64 = stack.from_front(ctx)?; let frame_no: u64 = stack.from_front(ctx)?;
let message: piccolo::String = stack.from_front(ctx)?; let message: piccolo::String = stack.from_front(ctx)?;
let message_str = str::from_utf8(message.as_bytes()) let message_str = str::from_utf8(message.as_bytes())
.map_err(|_| "Expected message to echo to be UTF-8.".into_value(ctx))?; .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))?; .map_err(|m| m.into_value(ctx))?;
Ok(piccolo::CallbackReturn::Return) 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<GlobalLayoutCell>,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let commands: Table<'gc> = let commands: Table<'gc> =
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; 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<GlobalLayoutCell>,
) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let commands: Table<'gc> = let commands: Table<'gc> =
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?; 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<GlobalLayoutCell>,
) -> 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)
})
}

View File

@ -16,38 +16,61 @@ use crate::split_panel::*;
use crate::term_view::*; use crate::term_view::*;
#[derive(Properties)] #[derive(Properties)]
pub struct GlobalState { pub struct GlobalMemoState {
// No strong references allowed between each of these groups of state. // No strong references allowed between each of these groups of state.
frame_registry: RefCell<RegisteredTermFrames>, frame_registry: RefCell<RegisteredTermFrames>,
lua_engine: RefCell<LuaState>, lua_engine: RefCell<LuaState>,
term_splits: RefCell<TermSplit>,
}
type GlobalCell = Rc<GlobalState>;
// Used only for yew. Always equal since we don't want it to // A cache of the latest layout info (separate from the state).
// actually look into GlobalState, changes there should never // Updating this doesn't force a relayout, so only update the cache when
// cause a re-render. // you also emit a change to the real layout.
impl PartialEq for GlobalState { layout: RefCell<Rc<GlobalLayoutState>>,
}
type GlobalMemoCell = Rc<GlobalMemoState>;
// 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 { fn eq(&self, _other: &Self) -> bool {
true 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<GlobalLayoutState>;
#[function_component(App)] #[function_component(App)]
fn app() -> Html { fn app() -> Html {
let global = use_memo((), |_| GlobalState { let global_layout: UseStateHandle<GlobalLayoutCell> = use_state(|| {
Rc::new(GlobalLayoutState {
term_splits: TermSplit::Term {
frame: TermFrame(1),
},
})
});
let global_memo = use_memo((), |_| GlobalMemoState {
frame_registry: RegisteredTermFrames::new().into(), frame_registry: RegisteredTermFrames::new().into(),
lua_engine: LuaState::setup().expect("Can create interpreter").into(), lua_engine: LuaState::setup().expect("Can create interpreter").into(),
term_splits: TermSplit::Term { layout: RefCell::new((*global_layout).clone()),
frame: TermFrame(1), });
} use_memo((), |_| {
.into(), 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! { html! {
<div class="toplevel"> <div class="toplevel">
<TermViewTree global={global.clone()}/> <TermViewTree global_memo={global_memo.clone()}
global_layout={global_layout.clone()}/>
</div> </div>
} }
} }

View File

@ -1,5 +1,5 @@
use itertools::Itertools; use itertools::Itertools;
use std::collections::BTreeMap; use std::{collections::BTreeMap, rc::Rc};
use crate::TermFrame; use crate::TermFrame;
@ -9,12 +9,12 @@ pub enum TermSplit {
frame: TermFrame, frame: TermFrame,
}, },
Horizontal { Horizontal {
left: Box<TermSplit>, left: Rc<TermSplit>,
right: Box<TermSplit>, right: Rc<TermSplit>,
}, },
Vertical { Vertical {
top: Box<TermSplit>, top: Rc<TermSplit>,
bottom: Box<TermSplit>, bottom: Rc<TermSplit>,
}, },
} }
@ -60,28 +60,40 @@ impl TermSplit {
Ok(()) Ok(())
} }
pub fn modify_at_pathstr<F>(&mut self, pathstr: &str, mod_with: F) -> Result<(), String> pub fn modify_at_pathstr<F>(&self, pathstr: &str, mod_with: F) -> Result<Self, String>
where where
F: FnOnce(&mut TermSplit) -> Result<(), String>, F: FnOnce(&TermSplit) -> Result<Self, String>,
{ {
self.modify_at_pathstr_vec(&pathstr.chars().collect::<Vec<char>>(), mod_with) self.modify_at_pathstr_vec(&pathstr.chars().collect::<Vec<char>>(), mod_with)
} }
fn modify_at_pathstr_vec<F>(&mut self, pathstr: &[char], mod_with: F) -> Result<(), String> fn modify_at_pathstr_vec<F>(&self, pathstr: &[char], mod_with: F) -> Result<Self, String>
where where
F: FnOnce(&mut TermSplit) -> Result<(), String>, F: FnOnce(&TermSplit) -> Result<Self, String>,
{ {
match self { match self {
TermSplit::Horizontal { left, right } => match pathstr.split_first() { TermSplit::Horizontal { left, right } => match pathstr.split_first() {
None => mod_with(self), None => mod_with(self),
Some(('l', path_rest)) => left.modify_at_pathstr_vec(path_rest, mod_with), Some(('l', path_rest)) => Ok(TermSplit::Horizontal {
Some(('r', path_rest)) => right.modify_at_pathstr_vec(path_rest, mod_with), 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::<String>())) Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a horizontal split", c, path_rest.iter().collect::<String>()))
}, },
TermSplit::Vertical { top, bottom } => match pathstr.split_first() { TermSplit::Vertical { top, bottom } => match pathstr.split_first() {
None => mod_with(self), None => mod_with(self),
Some(('t', path_rest)) => top.modify_at_pathstr_vec(path_rest, mod_with), Some(('t', path_rest)) => Ok(TermSplit::Vertical {
Some(('b', path_rest)) => bottom.modify_at_pathstr_vec(path_rest, mod_with), 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::<String>())) Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a vertical split", c, path_rest.iter().collect::<String>()))
}, },
TermSplit::Term { .. } => match pathstr.split_first() { TermSplit::Term { .. } => match pathstr.split_first() {
@ -91,47 +103,35 @@ impl TermSplit {
} }
} }
pub fn hsplit(&mut self, pathstr: &str, new_frame: TermFrame) -> Result<(), String> { pub fn hsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result<TermSplit, String> {
let mut new = self.clone(); let new = self.modify_at_pathstr(pathstr, move |n| {
new.modify_at_pathstr(pathstr, move |n| { Ok(TermSplit::Horizontal {
*n = TermSplit::Horizontal {
left: n.clone().into(), left: n.clone().into(),
right: TermSplit::Term { frame: new_frame }.into(), right: TermSplit::Term { frame: new_frame }.into(),
}; })
Ok(())
})?; })?;
new.validate()?; new.validate()?;
*self = new; Ok(new)
Ok(())
} }
pub fn vsplit(&mut self, pathstr: &str, new_frame: TermFrame) -> Result<(), String> { pub fn vsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result<TermSplit, String> {
let mut new = self.clone(); let new = self.modify_at_pathstr(pathstr, move |n| {
new.modify_at_pathstr(pathstr, move |n| { Ok(TermSplit::Vertical {
*n = TermSplit::Vertical {
top: n.clone().into(), top: n.clone().into(),
bottom: TermSplit::Term { frame: new_frame }.into(), bottom: TermSplit::Term { frame: new_frame }.into(),
}; })
Ok(())
})?; })?;
new.validate()?; new.validate()?;
*self = new; Ok(new)
Ok(())
} }
pub fn join(&mut self, pathstr: &str) -> Result<(), String> { pub fn join(&self, pathstr: &str) -> Result<TermSplit, String> {
self.modify_at_pathstr(pathstr, move |n| match n { self.modify_at_pathstr(pathstr, move |n| match n {
TermSplit::Term { .. } => { TermSplit::Term { .. } => {
Err("Can only join vertical or horizontal splits, not a terminal".to_owned()) Err("Can only join vertical or horizontal splits, not a terminal".to_owned())
} }
TermSplit::Horizontal { left, .. } => { TermSplit::Horizontal { left, .. } => Ok((**left).clone()),
*n = (**left).clone(); TermSplit::Vertical { top, .. } => Ok((**top).clone()),
Ok(())
}
TermSplit::Vertical { top, .. } => {
*n = (**top).clone();
Ok(())
}
}) })
} }
} }

View File

@ -8,7 +8,7 @@ use crate::{
command_handler::command_handler, command_handler::command_handler,
lineengine::line::{Readline, ReadlineEvent}, lineengine::line::{Readline, ReadlineEvent},
term_split::TermSplit, term_split::TermSplit,
GlobalCell, PanelDirection, SplitPanel, GlobalLayoutCell, GlobalMemoCell, PanelDirection, SplitPanel,
}; };
#[wasm_bindgen] #[wasm_bindgen]
@ -95,7 +95,7 @@ fn get_or_make_term_frame<'a>(
frame: &TermFrame, frame: &TermFrame,
frames: &'a mut RegisteredTermFrames, frames: &'a mut RegisteredTermFrames,
// Only used for callbacks, expected frames already borrowed mut! // Only used for callbacks, expected frames already borrowed mut!
globals: &GlobalCell, globals: &GlobalMemoCell,
) -> &'a TermFrameData { ) -> &'a TermFrameData {
frames.entry(frame.clone()).or_insert_with(|| { frames.entry(frame.clone()).or_insert_with(|| {
let term = Terminal::new(); let term = Terminal::new();
@ -141,9 +141,8 @@ fn get_or_make_term_frame<'a>(
}; };
let frame_for_resize: TermFrame = frame.clone(); 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| { let resize_closure = Closure::new(move |dims: Dims| {
web_sys::console::log_1(&"Resize closure called".into());
globals_for_resize globals_for_resize
.frame_registry .frame_registry
.try_borrow_mut() .try_borrow_mut()
@ -161,7 +160,7 @@ fn get_or_make_term_frame<'a>(
term.onResize(&resize_closure); term.onResize(&resize_closure);
let frame_for_ondata: TermFrame = frame.clone(); 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 data_closure = Closure::new(move |d: String| {
let new_lines: Vec<String> = match globals_for_ondata let new_lines: Vec<String> = match globals_for_ondata
.frame_registry .frame_registry
@ -200,13 +199,13 @@ fn get_or_make_term_frame<'a>(
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct TermViewProps { pub struct TermViewProps {
pub terminal: TermFrame, pub terminal: TermFrame,
pub global: GlobalCell, pub global_memo: GlobalMemoCell,
} }
#[function_component(TermView)] #[function_component(TermView)]
pub fn term_view(props: &TermViewProps) -> Html { pub fn term_view(props: &TermViewProps) -> Html {
let mut frame_reg = props.global.frame_registry.borrow_mut(); 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); let term = get_or_make_term_frame(&props.terminal, &mut frame_reg, &props.global_memo);
term.fit.fit(); term.fit.fit();
html! { html! {
{Html::VRef(term.node.clone())} {Html::VRef(term.node.clone())}
@ -215,16 +214,17 @@ pub fn term_view(props: &TermViewProps) -> Html {
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct TermViewTreeProps { pub struct TermViewTreeProps {
pub global: GlobalCell, pub global_memo: GlobalMemoCell,
pub global_layout: UseStateHandle<GlobalLayoutCell>,
} }
#[function_component(TermViewTree)] #[function_component(TermViewTree)]
pub fn term_view_tree(props: &TermViewTreeProps) -> Html { 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::*; use TermSplit::*;
match split { match split {
Term { frame } => html! { Term { frame } => html! {
<TermView global={global.clone()} terminal={frame.clone()}/> <TermView global_memo={global.clone()} terminal={frame.clone()}/>
}, },
Horizontal { left, right } => html! { Horizontal { left, right } => html! {
<SplitPanel <SplitPanel
@ -243,11 +243,11 @@ pub fn term_view_tree(props: &TermViewTreeProps) -> 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( pub fn echo_to_term_frame(
global: &GlobalCell, global: &GlobalMemoCell,
frame_id: &TermFrame, frame_id: &TermFrame,
message: &str, message: &str,
) -> Result<(), &'static str> { ) -> Result<(), &'static str> {