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 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,

View File

@ -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,

View File

@ -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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<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::*;
#[derive(Properties)]
pub struct GlobalState {
pub struct GlobalMemoState {
// No strong references allowed between each of these groups of state.
frame_registry: RefCell<RegisteredTermFrames>,
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
// 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<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 {
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)]
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(),
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! {
<div class="toplevel">
<TermViewTree global={global.clone()}/>
<TermViewTree global_memo={global_memo.clone()}
global_layout={global_layout.clone()}/>
</div>
}
}

View File

@ -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<TermSplit>,
right: Box<TermSplit>,
left: Rc<TermSplit>,
right: Rc<TermSplit>,
},
Vertical {
top: Box<TermSplit>,
bottom: Box<TermSplit>,
top: Rc<TermSplit>,
bottom: Rc<TermSplit>,
},
}
@ -60,28 +60,40 @@ impl TermSplit {
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
F: FnOnce(&mut TermSplit) -> Result<(), String>,
F: FnOnce(&TermSplit) -> Result<Self, String>,
{
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
F: FnOnce(&mut TermSplit) -> Result<(), String>,
F: FnOnce(&TermSplit) -> Result<Self, String>,
{
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::<String>()))
},
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::<String>()))
},
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<TermSplit, String> {
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<TermSplit, String> {
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<TermSplit, String> {
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()),
})
}
}

View File

@ -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<String> = 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<GlobalLayoutCell>,
}
#[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! {
<TermView global={global.clone()} terminal={frame.clone()}/>
<TermView global_memo={global.clone()} terminal={frame.clone()}/>
},
Horizontal { left, right } => html! {
<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(
global: &GlobalCell,
global: &GlobalMemoCell,
frame_id: &TermFrame,
message: &str,
) -> Result<(), &'static str> {