Get editor showing (not yet usable) on #editor

This commit is contained in:
Condorra 2024-10-05 19:28:39 +10:00
parent 56edc3d484
commit 4585db0433
15 changed files with 568 additions and 186 deletions

View File

@ -1,11 +1,51 @@
<!doctype html>
<html lang="en">
<head>
<script type="importmap">
{
"imports": {
"codemirror": "./codemirror/index.js",
"@codemirror/view": "./@codemirror/view/index.js",
"@codemirror/state": "./@codemirror/state/index.js",
"@codemirror/language": "./@codemirror/language/index.js",
"@codemirror/legacy-modes/mode/lua": "./@codemirror/legacy-modes/mode/lua.js",
"@codemirror/commands": "./@codemirror/commands/index.js",
"@codemirror/search": "./@codemirror/search/index.js",
"@codemirror/autocomplete": "./@codemirror/autocomplete/index.js",
"@codemirror/lint": "./@codemirror/lint/index.js",
"@lezer/common": "./@lezer/common/index.js",
"@lezer/highlight": "./@lezer/highlight/index.js",
"@lezer/lr": "./@lezer/lr/index.js",
"style-mod": "./style-mod/style-mod.js",
"w3c-keyname": "./w3c-keyname/index.js",
"crelt": "./crelt/index.js"
}
}
</script>
<link data-trunk rel="css" href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
<link data-trunk rel="css" href="node_modules/bootstrap-icons/font/bootstrap-icons.min.css"/>
<link data-trunk rel="css" href="node_modules/@xterm/xterm/css/xterm.css"/>
<script data-trunk src="node_modules/@xterm/xterm/lib/xterm.js"></script>
<script data-trunk src="node_modules/@xterm/addon-fit/lib/addon-fit.js"></script>
<link data-trunk rel="css" href="styles.css"/>
<link data-trunk rel="copy-file" href="assets/fonts/JetBrainsMono-Regular.woff2"/>
<link data-trunk rel="copy-file" href="node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" data-target-path="fonts"/>
<link data-trunk rel="copy-file" href="node_modules/codemirror/dist/index.js" data-target-path="codemirror"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/view/dist/index.js" data-target-path="@codemirror/view"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/state/dist/index.js" data-target-path="@codemirror/state"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/language/dist/index.js" data-target-path="@codemirror/language"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/legacy-modes/mode/lua.js" data-target-path="@codemirror/legacy-modes/mode"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/commands/dist/index.js" data-target-path="@codemirror/commands"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/autocomplete/dist/index.js" data-target-path="@codemirror/autocomplete"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/search/dist/index.js" data-target-path="@codemirror/search"></link>
<link data-trunk rel="copy-file" href="node_modules/@codemirror/lint/dist/index.js" data-target-path="@codemirror/lint"></link>
<link data-trunk rel="copy-file" href="node_modules/@lezer/common/dist/index.js" data-target-path="@lezer/common"></link>
<link data-trunk rel="copy-file" href="node_modules/@lezer/highlight/dist/index.js" data-target-path="@lezer/highlight"></link>
<link data-trunk rel="copy-file" href="node_modules/@lezer/lr/dist/index.js" data-target-path="@lezer/lr"></link>
<link data-trunk rel="copy-file" href="node_modules/style-mod/src/style-mod.js" data-target-path="style-mod"></link>
<link data-trunk rel="copy-file" href="node_modules/crelt/index.js" data-target-path="crelt"></link>
<link data-trunk rel="copy-file" href="node_modules/w3c-keyname/index.js" data-target-path="w3c-keyname"></link>
<script type="module" src="codemirror/index.js"></script>
</head>
<body></body>
</html>

184
package-lock.json generated
View File

@ -9,8 +9,128 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@codemirror/legacy-modes": "^6.4.1",
"@codemirror/state": "^6.4.1",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0"
"@xterm/xterm": "^5.5.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"codemirror": "^6.0.1"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz",
"integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.2.tgz",
"integrity": "sha512-Fq7eWOl1Rcbrfn6jD8FPCj9Auaxdm5nIK5RYOeW7ughnd/rY5AmPg6b+CfsG39ZHdwiwe8lde3q8uR7CF5S0yQ==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz",
"integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/legacy-modes": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.4.1.tgz",
"integrity": "sha512-vdg3XY7OAs5uLDx2Iw+cGfnwtd7kM+Et/eMsqAGTfT/JKiVBQZXosTzjEbWAi/FrY6DcQIz8mQjBozFHZEUWQA==",
"dependencies": {
"@codemirror/language": "^6.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz",
"integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
},
"node_modules/@codemirror/view": {
"version": "6.34.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz",
"integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@lezer/common": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.2.tgz",
"integrity": "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="
},
"node_modules/@lezer/highlight": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@xterm/addon-fit": {
@ -25,6 +145,68 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
},
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
}
}
}

View File

@ -9,7 +9,12 @@
"author": "",
"license": "ISC",
"dependencies": {
"@codemirror/legacy-modes": "^6.4.1",
"@codemirror/state": "^6.4.1",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0"
"@xterm/xterm": "^5.5.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"codemirror": "^6.0.1"
}
}

View File

@ -2,7 +2,7 @@ use crate::{
echo_to_term_frame,
lua_engine::LuaState,
parsing::{parse_commands, ParsedCommand},
GlobalMemoCell, TermFrame,
FrameId, GlobalMemoCell,
};
use itertools::Itertools;
use wasm_bindgen::JsValue;
@ -20,7 +20,7 @@ pub fn debrace(inp: &str) -> &str {
fn reentrant_command_handler(
lua_state: &mut LuaState,
globals: &GlobalMemoCell,
term_frame: &TermFrame,
term_frame: &FrameId,
commands_in: &[ParsedCommand],
) {
lua_state.set_current_frame(term_frame);
@ -68,7 +68,7 @@ fn reentrant_command_handler(
}
}
pub fn command_handler(globals: &GlobalMemoCell, term_frame: &TermFrame, command_in: &str) {
pub fn command_handler(globals: &GlobalMemoCell, term_frame: &FrameId, command_in: &str) {
echo_to_term_frame(globals, term_frame, "\r").unwrap_or(());
{
let mut cq = globals.command_queue.borrow_mut();

164
src/editor_view.rs Normal file
View File

@ -0,0 +1,164 @@
use std::{ops::Deref, rc::Rc};
use anyhow::Error;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use web_sys::{window, Node};
use yew::{
function_component, html, use_effect_with, use_node_ref, use_state_eq, AttrValue, Callback,
Html, Properties, UseStateHandle,
};
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
#[derive(Properties, PartialEq, Clone)]
pub struct EditorViewProps {
pub frame: FrameId,
pub global_memo: GlobalMemoCell,
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
}
fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) {
let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone();
gl_new.frame_views.remove(frame);
global_layout.set(gl_new.into());
}
#[derive(PartialEq, Clone)]
pub struct EditorState {
error_msg: Option<AttrValue>,
available_files: Vec<AttrValue>,
}
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorState> {
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
win.local_storage()
.map_err(|_| Error::msg("Error retrieving localStorage"))?
.ok_or_else(|| Error::msg("Local storage not available"))?;
Ok(EditorState {
error_msg: None,
available_files: vec![],
})
}
fn fetch_initial_editor_state() -> EditorState {
match fetch_initial_editor_state_or_fail() {
Ok(s) => s,
Err(e) => EditorState {
error_msg: Some(format!("Can't load editor data: {}", e.to_string()).into()),
available_files: vec![],
},
}
}
#[function_component(EditorNav)]
fn editor_nav(props: &EditorViewProps) -> Html {
let global_layout = props.global_layout.clone();
let frame = props.frame.clone();
html! {
<div class="editornav">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button>
<div class="flex-fill"/>
<button class="btn" onclick={move |_ev| close_editor(&frame, &global_layout)} aria-label="Close Editor" title="Close editor"><i class="bi bi-arrow-return-left"></i></button>
</nav>
</div>
}
}
#[wasm_bindgen(getter_with_clone)]
struct EditorViewConfig {
pub doc: String,
pub extensions: Vec<CMExtension>,
pub parent: Node,
}
#[wasm_bindgen(module = codemirror)]
extern "C" {
type EditorView;
#[derive(Clone)]
type CMExtension;
#[wasm_bindgen(constructor)]
fn new(settings: EditorViewConfig) -> EditorView;
#[wasm_bindgen(js_name = basicSetup, thread_local)]
static BASIC_SETUP: CMExtension;
}
#[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")]
extern "C" {
#[wasm_bindgen(js_name = lua, thread_local)]
static LUA: StreamLanguage;
}
#[wasm_bindgen(module = "@codemirror/language")]
extern "C" {
#[derive(Clone)]
type StreamLanguage;
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
fn streamlanguage_define(input: StreamLanguage) -> CMExtension;
}
#[function_component(EditorArea)]
fn editor_area(props: &EditorViewProps) -> Html {
let node_ref = use_node_ref();
use_effect_with(node_ref.clone(), |node_ref| match node_ref.get() {
None => {}
Some(node) => {
EditorView::new(EditorViewConfig {
doc: "Hello World".to_owned(),
extensions: vec![
BASIC_SETUP.with(CMExtension::clone),
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
],
parent: node,
});
}
});
html! {
<div class="editorarea" ref={node_ref.clone()}>
</div>
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct ErrorBarProps {
pub msg: AttrValue,
pub dismiss: Callback<()>,
}
#[function_component(ErrorBar)]
fn error_bar(props: &ErrorBarProps) -> Html {
let props = props.clone();
html! {
<div class="errorbar alert alert-danger alert-dismissible" role="alert">
<span class="errorbar-msg">{props.msg.clone()}</span>
<button class="btn-close" aria-label="Close" onclick={move |_ev| props.dismiss.emit(())}></button>
</div>
}
}
#[function_component(CodeEditorView)]
pub fn editor_view(props: &EditorViewProps) -> Html {
let editor_state: UseStateHandle<EditorState> = use_state_eq(fetch_initial_editor_state);
html! {
<>
{match editor_state.error_msg.as_ref() {
None => { html! { <></> }}
Some(msg) => html! {
<ErrorBar msg={msg.clone()}
dismiss={move |()| editor_state.set(EditorState {
error_msg: None,
..(editor_state.deref().clone())
})
}/> }
}}
<SplitPanel direction={PanelDirection::Horizontal}
first={html!{<EditorNav ..props.clone()/>}}
second={html!{<EditorArea ..props.clone()/>}}
/>
</>
}
}

View File

@ -7,6 +7,7 @@ use yew::prelude::*;
use crate::{
command_handler::command_handler,
editor_view::CodeEditorView,
lineengine::line::{Readline, ReadlineEvent},
term_split::TermSplit,
timer_host::TimerHost,
@ -69,11 +70,17 @@ extern "C" {
#[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Debug, Collect)]
#[collect(require_static)]
pub struct TermFrame(pub u64);
pub struct FrameId(pub u64);
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Debug)]
pub enum FrameViewType {
Terminal,
Editor,
}
#[derive(Properties)]
pub struct TermFrameData {
pub id: TermFrame,
pub struct FrameData {
pub id: FrameId,
pub term: Terminal,
pub fit: FitAddon,
pub node: Node,
@ -86,21 +93,21 @@ pub struct TermFrameData {
)>,
}
impl PartialEq for TermFrameData {
impl PartialEq for FrameData {
fn eq(&self, other: &Self) -> bool {
// Only the ID matters, the rest are just reference data.
self.id == other.id
}
}
pub type RegisteredTermFrames = HashMap<TermFrame, TermFrameData>;
pub type RegisteredTermFrames = HashMap<FrameId, FrameData>;
fn get_or_make_term_frame<'a>(
frame: &TermFrame,
frame: &FrameId,
frames: &'a mut RegisteredTermFrames,
// Only used for callbacks, expected frames already borrowed mut!
globals: &GlobalMemoCell,
) -> &'a TermFrameData {
) -> &'a mut FrameData {
frames.entry(frame.clone()).or_insert_with(|| {
let term = Terminal::new();
let fit = FitAddon::new();
@ -124,7 +131,7 @@ fn get_or_make_term_frame<'a>(
fit.fit();
let term_for_readline: Terminal = Terminal { obj: term.clone() };
let initial_size = (term.cols(), term.rows());
let mut new_data: TermFrameData = TermFrameData {
let mut new_data: FrameData = FrameData {
id: frame.clone(),
term: Terminal { obj: term.clone() },
fit,
@ -142,7 +149,7 @@ fn get_or_make_term_frame<'a>(
retained_closures: None,
};
let frame_for_resize: TermFrame = frame.clone();
let frame_for_resize: FrameId = frame.clone();
let globals_for_resize: GlobalMemoCell = globals.clone();
let resize_closure = Closure::new(move |dims: Dims| {
globals_for_resize
@ -161,7 +168,7 @@ fn get_or_make_term_frame<'a>(
});
term.onResize(&resize_closure);
let frame_for_ondata: TermFrame = frame.clone();
let frame_for_ondata: FrameId = frame.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
@ -200,8 +207,9 @@ fn get_or_make_term_frame<'a>(
#[derive(Properties, PartialEq)]
pub struct TermViewProps {
pub terminal: TermFrame,
pub terminal: FrameId,
pub global_memo: GlobalMemoCell,
pub global_layout: UseStateHandle<GlobalLayoutCell>,
}
#[function_component(TermView)]
@ -209,8 +217,20 @@ pub fn term_view(props: &TermViewProps) -> Html {
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())}
match props
.global_layout
.frame_views
.get(&props.terminal)
.cloned()
.unwrap_or(FrameViewType::Terminal)
{
FrameViewType::Terminal => Html::VRef(term.node.clone()),
FrameViewType::Editor => html! {
<CodeEditorView
frame={props.terminal.clone()}
global_memo={props.global_memo.clone()}
global_layout={props.global_layout.clone()}/>
},
}
}
@ -222,35 +242,43 @@ pub struct TermViewTreeProps {
#[function_component(TermViewTree)]
pub fn term_view_tree(props: &TermViewTreeProps) -> Html {
fn mk_term_view_tree(global: &GlobalMemoCell, split: &TermSplit) -> Html {
fn mk_term_view_tree(
global: &GlobalMemoCell,
layout: UseStateHandle<GlobalLayoutCell>,
split: &TermSplit,
) -> Html {
use TermSplit::*;
match split {
Term { frame } => html! {
<TermView global_memo={global.clone()} terminal={frame.clone()}/>
<TermView global_memo={global.clone()} global_layout={layout} terminal={frame.clone()}/>
},
Horizontal { left, right } => html! {
<SplitPanel
direction={PanelDirection::Horizontal}
first={mk_term_view_tree(global, left)}
second={mk_term_view_tree(global, right)}
first={mk_term_view_tree(global, layout.clone(), left)}
second={mk_term_view_tree(global, layout, right)}
/>
},
Vertical { top, bottom } => html! {
<SplitPanel
direction={PanelDirection::Vertical}
first={mk_term_view_tree(global, top)}
second={mk_term_view_tree(global, bottom)}
first={mk_term_view_tree(global, layout.clone(), top)}
second={mk_term_view_tree(global, layout, bottom)}
/>
},
}
}
mk_term_view_tree(&props.global_memo, &props.global_layout.term_splits)
mk_term_view_tree(
&props.global_memo,
props.global_layout.clone(),
&props.global_layout.term_splits,
)
}
pub fn echo_to_term_frame(
global: &GlobalMemoCell,
frame_id: &TermFrame,
frame_id: &FrameId,
message: &str,
) -> Result<(), &'static str> {
global

View File

@ -22,19 +22,19 @@ use web_sys::{
IdbTransactionMode, Url,
};
use crate::{echo_to_term_frame, GlobalMemoCell, TermFrame};
use crate::{echo_to_term_frame, FrameId, GlobalMemoCell};
#[derive(Clone, Debug)]
pub enum QueuedAction {
LogEvent(LogEvent),
ReportOnLogStreams(TermFrame),
ReportOnLogStreams(FrameId),
DeleteLogs(DeleteLogs),
DownloadLogs(DownloadLogs),
}
#[derive(Clone, Debug)]
pub struct DeleteLogs {
pub reply_to: TermFrame,
pub reply_to: FrameId,
pub stream: String,
pub min_date: String,
pub max_date: String,
@ -42,7 +42,7 @@ pub struct DeleteLogs {
#[derive(Clone, Debug)]
pub struct DownloadLogs {
pub reply_to: TermFrame,
pub reply_to: FrameId,
pub stream: String,
pub min_date: String,
pub max_date: String,
@ -114,7 +114,7 @@ async fn async_init_logging() -> Result<LoggingEngine, DomException> {
Ok(LoggingEngine::Ready { db: db.into() })
}
fn logging_broken_message(global: &GlobalMemoCell, frame: &TermFrame) {
fn logging_broken_message(global: &GlobalMemoCell, frame: &FrameId) {
let _ = echo_to_term_frame(global, frame, "Couldn't enable logging. This sometimes happens if your browser doesn't enable indexed storage, or is in a mode (e.g. private browsing) where such storage is not permitted.\r\n");
}
@ -143,7 +143,7 @@ fn init_logging(global: &GlobalMemoCell) {
}
Err(_) => {
if let LoggingEngine::Initialising { .. } = global.log_engine.borrow().deref() {
logging_broken_message(&global, &TermFrame(1));
logging_broken_message(&global, &FrameId(1));
}
}
}
@ -181,7 +181,7 @@ fn queue_immediate_log(global: &GlobalMemoCell, event: LogEvent) {
async fn immediate_report_log_frame_refutable(
global: GlobalMemoCell,
db: &IdbDatabase,
frame: TermFrame,
frame: FrameId,
) -> Result<(), DomException> {
let trans = db.transaction_on_one("logs")?;
let store = trans.object_store("logs")?;
@ -216,7 +216,7 @@ async fn immediate_report_log_frame_refutable(
async fn immediate_report_log_frame_async(
global: GlobalMemoCell,
db: Arc<IdbDatabase>,
frame: TermFrame,
frame: FrameId,
) {
match immediate_report_log_frame_refutable(global, &db, frame).await {
Ok(()) => {}
@ -229,7 +229,7 @@ async fn immediate_report_log_frame_async(
}
}
fn immediate_report_log_frame(global: &GlobalMemoCell, frame: TermFrame) {
fn immediate_report_log_frame(global: &GlobalMemoCell, frame: FrameId) {
let log_engine = global.log_engine.borrow();
if let LoggingEngine::Ready { db } = log_engine.deref() {
spawn_local(immediate_report_log_frame_async(
@ -450,7 +450,7 @@ pub fn log(global: &GlobalMemoCell, event: &LogEvent) {
}
}
pub fn start_listing_logs(global: &GlobalMemoCell, send_to: &TermFrame) {
pub fn start_listing_logs(global: &GlobalMemoCell, send_to: &FrameId) {
let mut engine_borrow = global.log_engine.borrow_mut();
match engine_borrow.deref_mut() {
LoggingEngine::Uninitialised => {
@ -475,7 +475,7 @@ pub fn start_listing_logs(global: &GlobalMemoCell, send_to: &TermFrame) {
pub fn start_deleting_logs(
global: &GlobalMemoCell,
reply_to: &TermFrame,
reply_to: &FrameId,
stream: &str,
min_date: &str,
max_date: &str,
@ -510,7 +510,7 @@ pub fn start_deleting_logs(
pub fn start_downloading_logs(
global: &GlobalMemoCell,
reply_to: &TermFrame,
reply_to: &FrameId,
stream: &str,
min_date: &str,
max_date: &str,

View File

@ -17,7 +17,7 @@ use crate::{
match_table_try_run_sub,
},
parsing::ParsedCommand,
GlobalLayoutCell, GlobalMemoCell, TermFrame,
FrameId, GlobalLayoutCell, GlobalMemoCell,
};
pub struct LuaState {
@ -38,19 +38,19 @@ impl LuaState {
Ok(LuaState { interp, exec })
}
fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), ExternError> {
fn try_set_current_frame(&mut self, frame: &FrameId) -> Result<(), ExternError> {
self.interp.try_enter(|ctx| {
let info_table = Table::from_value(ctx, ctx.get_global("info")?)?;
info_table.set(
ctx,
ctx.intern_static(b"current_frame"),
intern_id::<TermFrame>(ctx, frame.clone()),
intern_id::<FrameId>(ctx, frame.clone()),
)?;
Ok(())
})
}
pub fn set_current_frame(&mut self, frame: &TermFrame) {
pub fn set_current_frame(&mut self, frame: &FrameId) {
// We silently ignore errors here. Failure can happen if the Lua code does weird things
// like messes with the info global, so better to just ignore.
self.try_set_current_frame(frame).unwrap_or(())
@ -104,7 +104,7 @@ impl LuaState {
pub(crate) fn dispatch_normal_command(
&mut self,
frame: &TermFrame,
frame: &FrameId,
command: &str,
) -> anyhow::Result<()> {
self.interp.try_enter(|ctx| {
@ -187,6 +187,7 @@ pub fn install_lua_globals(
register_command!(echo);
register_command!(echo_frame);
register_command!(echo_frame_raw);
register_command!(editor);
register_command!(hsplit);
register_command!(cmd_list_logs, "listlogs");
register_command!(mud_log, "log");
@ -341,7 +342,7 @@ fn lua_global_initialisation(global_memo: &GlobalMemoCell) -> Result<(), String>
lua_engine_ref.interp.enter(|ctx| {
ctx.fetch(&lua_engine_ref.exec).restart(
ctx,
Function::Callback(ensure_frame_instance(ctx, &TermFrame(1))),
Function::Callback(ensure_frame_instance(ctx, &FrameId(1))),
(),
);
});

View File

@ -1,6 +1,6 @@
use piccolo::{Callback, CallbackReturn, Context, FromValue, Table};
use crate::{echo_to_term_frame, id_intern::intern_id, GlobalMemoCell, TermFrame};
use crate::{echo_to_term_frame, id_intern::intern_id, FrameId, GlobalMemoCell};
use super::try_unwrap_frame;
@ -15,7 +15,7 @@ pub(super) fn new_frameroute<'gc>(
.pop_front()
.ok_or_else(|| anyhow::Error::msg("classes.frameroute:new missing object!"))?,
)?;
let frame: TermFrame = try_unwrap_frame(
let frame: FrameId = try_unwrap_frame(
ctx,
&stack
.pop_front()
@ -48,7 +48,7 @@ pub(super) fn frameroute_route<'gc>(
.pop_front()
.ok_or_else(|| anyhow::Error::msg("frameroute:route called without line!"))?,
)?;
let frame: TermFrame =
let frame: FrameId =
try_unwrap_frame(ctx, &frameroute.get(ctx, ctx.intern_static(b"frame"))?)?;
// We ignore errors with the term frame to avoid breaking the entire client for one closed frame.

View File

@ -3,7 +3,7 @@ use crate::{
id_intern::intern_id,
match_table::{create_match_table, match_table_add, match_table_remove},
timer_host::TimerHostAccessContext,
GlobalLayoutCell, GlobalLayoutState, GlobalMemoCell, TermFrame,
FrameId, FrameViewType, GlobalLayoutCell, GlobalLayoutState, GlobalMemoCell,
};
use gc_arena::{Gc, Rootable};
use itertools::Itertools;
@ -19,7 +19,7 @@ use super::call_checking_metatable;
pub fn alias(ctx: Context<'_>) -> Callback<'_> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
let frames: Table = ctx.get_global("frames")?;
let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?;
@ -65,7 +65,7 @@ pub fn alias(ctx: Context<'_>) -> Callback<'_> {
pub fn unalias(ctx: Context<'_>) -> Callback<'_> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
let frames: Table = ctx.get_global("frames")?;
let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?;
@ -98,7 +98,7 @@ pub fn unalias(ctx: Context<'_>) -> Callback<'_> {
}
pub fn list_match_tab<'gc>(
frame: TermFrame,
frame: FrameId,
aliases: UserData<'gc>,
ctx: Context<'gc>,
) -> Result<CallbackReturn<'gc>, Error<'gc>> {
@ -147,7 +147,7 @@ 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(
let frame: FrameId = try_unwrap_frame(
ctx,
&stack
.pop_front()
@ -214,7 +214,7 @@ pub fn vsplit<'gc>(
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let path: String = stack.from_front(ctx)?;
let frame: TermFrame = TermFrame(stack.from_front(ctx)?);
let frame: FrameId = FrameId(stack.from_front(ctx)?);
let new_splits = global_memo
.layout
.borrow()
@ -223,6 +223,7 @@ pub fn vsplit<'gc>(
.map_err(|e| e.into_value(ctx))?;
let new_layout = Rc::new(GlobalLayoutState {
term_splits: new_splits.clone(),
..(*(global_memo.layout.borrow().as_ref())).clone()
});
global_layout.set(new_layout.clone());
*(global_memo.layout.borrow_mut()) = new_layout;
@ -242,7 +243,7 @@ pub fn hsplit<'gc>(
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let path: String = stack.from_front(ctx)?;
let frame: TermFrame = TermFrame(stack.from_front(ctx)?);
let frame: FrameId = FrameId(stack.from_front(ctx)?);
let new_splits = global_memo
.layout
.borrow()
@ -251,6 +252,7 @@ pub fn hsplit<'gc>(
.map_err(|e| e.into_value(ctx))?;
let new_layout = Rc::new(GlobalLayoutState {
term_splits: new_splits.clone(),
..(*(global_memo.layout.borrow().as_ref())).clone()
});
global_layout.set(new_layout.clone());
*(global_memo.layout.borrow_mut()) = new_layout;
@ -278,6 +280,7 @@ pub fn panel_merge<'gc>(
.map_err(|e| e.into_value(ctx))?;
let new_layout = Rc::new(GlobalLayoutState {
term_splits: new_splits.clone(),
..(*(global_memo.layout.borrow().as_ref())).clone()
});
global_layout.set(new_layout.clone());
*(global_memo.layout.borrow_mut()) = new_layout;
@ -288,17 +291,17 @@ pub fn panel_merge<'gc>(
pub fn try_unwrap_frame<'gc>(
ctx: Context<'gc>,
value: &Value<'gc>,
) -> Result<TermFrame, piccolo::Error<'gc>> {
) -> Result<FrameId, piccolo::Error<'gc>> {
match u64::from_value(ctx, *value) {
Ok(v) => Ok(TermFrame(v)),
Ok(v) => Ok(FrameId(v)),
Err(_) => Ok(UserData::from_value(ctx, *value)?
.downcast::<Rootable!['gcb => Gc<'gcb, TermFrame>]>()?
.downcast::<Rootable!['gcb => Gc<'gcb, FrameId>]>()?
.as_ref()
.clone()),
}
}
pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &TermFrame) -> Callback<'gc> {
pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &FrameId) -> Callback<'gc> {
let frame = frame.clone();
Callback::from_fn(&ctx, move |ctx, _ex, _stack| {
let frames: Table = ctx.get_global("frames")?;
@ -313,7 +316,7 @@ pub fn ensure_frame_instance<'gc>(ctx: Context<'gc>, frame: &TermFrame) -> Callb
frames.set(ctx, frame.0 as i64, frame_tab)?;
// Call frame_tab:new(frame) to setup.
let frame = intern_id::<TermFrame>(ctx, frame.clone());
let frame = intern_id::<FrameId>(ctx, frame.clone());
let seq = async_sequence(&ctx, move |locals, mut seq| {
let frame_tab = locals.stash(&ctx, frame_tab);
let frame = locals.stash(&ctx, frame);
@ -444,7 +447,7 @@ pub(super) fn frame_input<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell)
fn list_timers<'gc>(
ctx: Context<'gc>,
global_memo: &GlobalMemoCell,
cur_frame_id: TermFrame,
cur_frame_id: FrameId,
) -> Result<(), Error<'gc>> {
let timer_val = match global_memo.frame_registry.borrow().get(&cur_frame_id) {
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
@ -486,7 +489,7 @@ fn list_timers<'gc>(
struct GlobalCellTermFrameTimerHostAccess {
global_memo: GlobalMemoCell,
frame: TermFrame,
frame: FrameId,
}
impl TimerHostAccessContext for GlobalCellTermFrameTimerHostAccess {
fn with_ref<F>(&self, f: F)
@ -512,7 +515,7 @@ pub(super) fn tick<'gc>(
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
if stack.is_empty() {
@ -586,7 +589,7 @@ pub(super) fn delay<'gc>(
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
if stack.is_empty() {
@ -660,7 +663,7 @@ pub(super) fn untick<'gc>(
let global_memo = global_memo.clone();
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
let name = stack.consume::<piccolo::String>(ctx)?.to_str()?.to_owned();
@ -675,3 +678,32 @@ pub(super) fn untick<'gc>(
Ok(piccolo::CallbackReturn::Return)
})
}
pub(super) fn editor<'gc>(
ctx: Context<'gc>,
global_memo: &GlobalMemoCell,
global_layout: &UseStateSetter<GlobalLayoutCell>,
) -> Callback<'gc> {
let global_memo = global_memo.clone();
let global_layout = global_layout.clone();
Callback::from_fn(&ctx, move |ctx, _ex, _stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
match global_memo
.frame_registry
.borrow_mut()
.get_mut(&cur_frame_id)
{
None => Err(anyhow::Error::msg("Frame no longer exists"))?,
Some(_frame_dat) => {
let mut new_gl: GlobalLayoutState = (*global_memo.layout.borrow().as_ref()).clone();
new_gl
.frame_views
.insert(cur_frame_id, FrameViewType::Editor);
global_layout.set(new_gl.into());
}
};
Ok(piccolo::CallbackReturn::Return)
})
}

View File

@ -15,7 +15,7 @@ use crate::{
match_table::{create_match_table, match_table_add, match_table_remove},
telnet::{parse_telnet_buf, TelnetOutput},
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
GlobalLayoutCell, GlobalMemoCell, TermFrame,
FrameId, GlobalLayoutCell, GlobalMemoCell,
};
use super::{call_checking_metatable, list_match_tab, try_unwrap_frame, LuaState};
@ -537,7 +537,7 @@ pub(super) fn new_mud<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
pub(super) fn mud_trigger<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
let frames: Table = ctx.get_global("frames")?;
let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?;
@ -592,7 +592,7 @@ pub(super) fn mud_trigger<'gc>(ctx: Context<'gc>) -> Callback<'gc> {
pub(super) fn mud_untrigger(ctx: Context<'_>) -> Callback<'_> {
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
let info: Table = ctx.get_global("info")?;
let cur_frame_id: TermFrame =
let cur_frame_id: FrameId =
try_unwrap_frame(ctx, &info.get(ctx, ctx.intern_static(b"current_frame"))?)?;
let frames: Table = ctx.get_global("frames")?;
let cur_frame: Table = frames.get(ctx, cur_frame_id.0 as i64)?;

View File

@ -8,6 +8,8 @@ use term_split::TermSplit;
use yew::prelude::*;
pub mod command_handler;
pub mod editor_view;
pub mod frame_view;
pub mod id_intern;
pub mod lineengine;
pub mod logging;
@ -17,12 +19,11 @@ pub mod parsing;
pub mod split_panel;
pub mod telnet;
pub mod term_split;
pub mod term_view;
pub mod timer_host;
pub mod websocket;
use crate::frame_view::*;
use crate::lua_engine::{install_lua_globals, LuaState};
use crate::split_panel::*;
use crate::term_view::*;
use crate::websocket::RegisteredWebSockets;
#[derive(Properties)]
@ -31,7 +32,7 @@ pub struct GlobalMemoState {
frame_registry: RefCell<RegisteredTermFrames>,
lua_engine: RefCell<LuaState>,
ws_registry: RefCell<RegisteredWebSockets>,
command_queue: RefCell<VecDeque<(TermFrame, ParsedCommand)>>,
command_queue: RefCell<VecDeque<(FrameId, ParsedCommand)>>,
log_engine: RefCell<LoggingEngine>,
// A cache of the latest layout info (separate from the state).
@ -51,9 +52,10 @@ impl PartialEq for GlobalMemoState {
// Used for state that impacts the entire layout. Changes here will
// cause a re-render
#[derive(PartialEq, Properties)]
#[derive(PartialEq, Properties, Clone)]
pub struct GlobalLayoutState {
term_splits: TermSplit,
frame_views: im::OrdMap<FrameId, FrameViewType>,
}
// Note: Despite interior mutability, you still should always call the
// setter to force a re-render. Interior mutability is used to allow this
@ -65,9 +67,8 @@ type GlobalLayoutCell = Rc<GlobalLayoutState>;
fn app() -> Html {
let global_layout: UseStateHandle<GlobalLayoutCell> = use_state(|| {
Rc::new(GlobalLayoutState {
term_splits: TermSplit::Term {
frame: TermFrame(1),
},
term_splits: TermSplit::Term { frame: FrameId(1) },
frame_views: im::OrdMap::new(),
})
});
let global_memo = use_memo((), |_| GlobalMemoState {
@ -84,7 +85,7 @@ fn app() -> Html {
});
html! {
<div class="toplevel">
<div class="toplevel" data-bs-theme="dark">
<TermViewTree global_memo={global_memo.clone()}
global_layout={global_layout.clone()}/>
</div>

View File

@ -1,12 +1,12 @@
use itertools::Itertools;
use std::{collections::BTreeMap, rc::Rc};
use crate::TermFrame;
use crate::FrameId;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum TermSplit {
Term {
frame: TermFrame,
frame: FrameId,
},
Horizontal {
left: Rc<TermSplit>,
@ -19,7 +19,7 @@ pub enum TermSplit {
}
impl TermSplit {
fn collect_term_frames(&self, into: &mut BTreeMap<TermFrame, usize>) {
fn collect_term_frames(&self, into: &mut BTreeMap<FrameId, usize>) {
match self {
TermSplit::Term { frame } => {
into.entry(frame.clone())
@ -38,7 +38,7 @@ impl TermSplit {
}
pub fn validate(&self) -> Result<(), String> {
let mut frame_count: BTreeMap<TermFrame, usize> = BTreeMap::new();
let mut frame_count: BTreeMap<FrameId, usize> = BTreeMap::new();
self.collect_term_frames(&mut frame_count);
let duplicate_terminal = frame_count
.iter()
@ -103,7 +103,7 @@ impl TermSplit {
}
}
pub fn hsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result<TermSplit, String> {
pub fn hsplit(&self, pathstr: &str, new_frame: FrameId) -> Result<TermSplit, String> {
let new = self.modify_at_pathstr(pathstr, move |n| {
Ok(TermSplit::Horizontal {
left: n.clone().into(),
@ -114,7 +114,7 @@ impl TermSplit {
Ok(new)
}
pub fn vsplit(&self, pathstr: &str, new_frame: TermFrame) -> Result<TermSplit, String> {
pub fn vsplit(&self, pathstr: &str, new_frame: FrameId) -> Result<TermSplit, String> {
let new = self.modify_at_pathstr(pathstr, move |n| {
Ok(TermSplit::Vertical {
top: n.clone().into(),
@ -145,19 +145,10 @@ mod tests {
use TermSplit::*;
assert_eq!(
(Vertical {
top: Term {
frame: TermFrame(1)
}
.into(),
top: Term { frame: FrameId(1) }.into(),
bottom: Horizontal {
left: Term {
frame: TermFrame(2)
}
.into(),
right: Term {
frame: TermFrame(3)
}
.into(),
left: Term { frame: FrameId(2) }.into(),
right: Term { frame: FrameId(3) }.into(),
}
.into()
})
@ -171,19 +162,10 @@ mod tests {
use TermSplit::*;
assert_eq!(
(Vertical {
top: Term {
frame: TermFrame(1)
}
.into(),
top: Term { frame: FrameId(1) }.into(),
bottom: Horizontal {
left: Term {
frame: TermFrame(1)
}
.into(),
right: Term {
frame: TermFrame(3)
}
.into(),
left: Term { frame: FrameId(1) }.into(),
right: Term { frame: FrameId(3) }.into(),
}
.into()
})
@ -192,19 +174,10 @@ mod tests {
);
assert_eq!(
(Vertical {
top: Term {
frame: TermFrame(42)
}
.into(),
top: Term { frame: FrameId(42) }.into(),
bottom: Horizontal {
left: Term {
frame: TermFrame(1)
}
.into(),
right: Term {
frame: TermFrame(42)
}
.into(),
left: Term { frame: FrameId(1) }.into(),
right: Term { frame: FrameId(42) }.into(),
}
.into()
})
@ -216,99 +189,49 @@ mod tests {
#[test]
fn modify_at_pathstr_works() {
use TermSplit::*;
let t = Term {
frame: TermFrame(1),
};
let t = Term { frame: FrameId(1) };
assert_eq!(
t.modify_at_pathstr("", |_v| {
Ok(Term {
frame: TermFrame(2),
})
}),
Ok(Term {
frame: TermFrame(2),
})
t.modify_at_pathstr("", |_v| { Ok(Term { frame: FrameId(2) }) }),
Ok(Term { frame: FrameId(2) })
);
assert_eq!(
t.modify_at_pathstr("tlr", |_v| {
Ok(Term {
frame: TermFrame(2),
})
}),
t.modify_at_pathstr("tlr", |_v| { Ok(Term { frame: FrameId(2) }) }),
Err("In split path, found trailing junk tlr after addressing terminal".to_owned())
);
let t = Vertical {
top: Horizontal {
left: Horizontal {
left: Term {
frame: TermFrame(42),
}
.into(),
right: Term {
frame: TermFrame(64),
}
.into(),
}
.into(),
right: Term {
frame: TermFrame(42),
left: Term { frame: FrameId(42) }.into(),
right: Term { frame: FrameId(64) }.into(),
}
.into(),
right: Term { frame: FrameId(42) }.into(),
}
.into(),
bottom: Vertical {
top: Term {
frame: TermFrame(43),
}
.into(),
bottom: Term {
frame: TermFrame(44),
}
.into(),
top: Term { frame: FrameId(43) }.into(),
bottom: Term { frame: FrameId(44) }.into(),
}
.into(),
};
assert_eq!(
t.modify_at_pathstr("tlr", |_v| {
Ok(Term {
frame: TermFrame(2),
})
})
.and_then(|t| t.modify_at_pathstr("bb", |_v| {
Ok(Term {
frame: TermFrame(3),
})
})),
t.modify_at_pathstr("tlr", |_v| { Ok(Term { frame: FrameId(2) }) })
.and_then(|t| t.modify_at_pathstr("bb", |_v| { Ok(Term { frame: FrameId(3) }) })),
Ok(Vertical {
top: Horizontal {
left: Horizontal {
left: Term {
frame: TermFrame(42),
}
.into(),
right: Term {
frame: TermFrame(2),
}
.into(),
}
.into(),
right: Term {
frame: TermFrame(42),
left: Term { frame: FrameId(42) }.into(),
right: Term { frame: FrameId(2) }.into(),
}
.into(),
right: Term { frame: FrameId(42) }.into(),
}
.into(),
bottom: Vertical {
top: Term {
frame: TermFrame(43),
}
.into(),
bottom: Term {
frame: TermFrame(3),
}
.into(),
top: Term { frame: FrameId(43) }.into(),
bottom: Term { frame: FrameId(3) }.into(),
}
.into(),
})

View File

@ -5,12 +5,12 @@ use wasm_bindgen::{closure::Closure, JsCast};
use crate::{
command_handler::execute_queue,
parsing::{parse_commands, ParsedCommand},
GlobalMemoCell, TermFrame,
FrameId, GlobalMemoCell,
};
pub struct TimerHost {
timers: Vec<TimerRecord>,
frame_id: TermFrame,
frame_id: FrameId,
next_id: u64,
}
@ -29,7 +29,7 @@ impl Drop for TimerHost {
}
impl TimerHost {
pub fn new(frame_id: TermFrame) -> Self {
pub fn new(frame_id: FrameId) -> Self {
Self {
timers: vec![],
frame_id,

View File

@ -67,3 +67,9 @@ body {
font-family: "JetBrainsMono-Regular", monospace, serif;
font-variant-ligatures: none;
}
.editornav {
color: white;
}
.editorarea {
color: white;
}