Support vsplit from in Lua.
This commit is contained in:
parent
525f69296e
commit
c32d7ac2bd
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
53
src/main.rs
53
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<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>
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
Loading…
Reference in New Issue
Block a user