use std::collections::HashMap; use wasm_bindgen::prelude::*; use web_sys::{Element, Node}; use yew::prelude::*; use crate::{ command_handler::command_handler, lineengine::line::{Readline, ReadlineEvent}, GlobalCell, }; #[wasm_bindgen] extern "C" { #[derive(PartialEq)] pub type Terminal; // Sadly, can't do type parameters with wasm_bindgen. pub type IEventString; pub type IEventDims; pub type IDisposable; #[wasm_bindgen(constructor)] fn new() -> Terminal; #[wasm_bindgen(method)] fn open(this: &Terminal, element: &Element); #[wasm_bindgen(method)] pub fn write(this: &Terminal, data: &str); // Todo: Can we do this with interfaces somehow? #[wasm_bindgen(method)] fn loadAddon(this: &Terminal, addon: &FitAddon); #[wasm_bindgen(method)] fn onData(this: &Terminal, listener: &Closure) -> IDisposable; #[wasm_bindgen(method)] fn onResize(this: &Terminal, listener: &Closure) -> IDisposable; #[wasm_bindgen(method, getter)] fn rows(this: &Terminal) -> u16; #[wasm_bindgen(method, getter)] fn cols(this: &Terminal) -> u16; pub type Dims; #[wasm_bindgen(method, getter)] fn rows(this: &Dims) -> u16; #[wasm_bindgen(method, getter)] fn cols(this: &Dims) -> u16; #[wasm_bindgen(method, setter)] fn set_listener(this: &IEventString, handler: &Closure) -> IDisposable; #[wasm_bindgen(method, setter)] fn set_listener(this: &IEventDims, handler: &Closure) -> IDisposable; #[wasm_bindgen(method)] fn dispose(this: &IDisposable); #[derive(PartialEq)] pub type FitAddon; } #[wasm_bindgen(js_namespace=FitAddon)] extern "C" { #[wasm_bindgen(constructor)] fn new() -> FitAddon; #[wasm_bindgen(method)] fn fit(this: &FitAddon); } #[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone)] pub struct TermFrame(pub u64); #[derive(Properties)] pub struct TermFrameData { pub id: TermFrame, pub term: Terminal, pub fit: FitAddon, pub node: Node, pub readline: Readline, pub retained_closures: Option<(Closure, Closure)>, } impl PartialEq for TermFrameData { fn eq(&self, other: &Self) -> bool { // Only the ID matters, the rest are just reference data. self.id == other.id } } pub type RegisteredTermFrames = HashMap; fn get_or_make_term_frame<'a>( frame: &TermFrame, frames: &'a mut RegisteredTermFrames, // Only used for callbacks, expected frames already borrowed mut! globals: &GlobalCell, ) -> &'a TermFrameData { frames.entry(frame.clone()).or_insert_with(|| { let term = Terminal::new(); let fit = FitAddon::new(); let element = web_sys::window() .and_then(|w| w.document()) .and_then(|d| d.create_element("div").ok()) .expect("Can create element for term"); element.set_class_name("hterminal"); term.open(&element); term.loadAddon(&fit); fit.fit(); for i in 0..100 { term.write(&format!("{} Hello world\r\n", i)); } let term_for_readline: Terminal = Terminal { obj: term.clone() }; let initial_size = (term.cols(), term.rows()); let mut new_data: TermFrameData = TermFrameData { id: frame.clone(), term: Terminal { obj: term.clone() }, fit, node: element.into(), readline: Readline::new( "".to_owned(), Box::new(move |dat| { term_for_readline.write( std::str::from_utf8(dat).expect("readline tried to emit invalid UTF-8"), ) }), initial_size, ) .into(), retained_closures: None, }; let frame_for_resize: TermFrame = frame.clone(); let globals_for_resize: GlobalCell = globals.clone(); let resize_closure = Closure::new(move |dims: Dims| { globals_for_resize .frame_registry .try_borrow_mut() .ok() .and_then(|mut frame_reg| { frame_reg.get_mut(&frame_for_resize).and_then(|frame| { frame .readline .handle_resize((dims.cols(), dims.rows())) .ok() }) }) .unwrap_or(()) }); term.onResize(&resize_closure); let frame_for_ondata: TermFrame = frame.clone(); let globals_for_ondata: GlobalCell = globals.clone(); let data_closure = Closure::new(move |d: String| { let new_lines: Vec = match globals_for_ondata .frame_registry .try_borrow_mut() .expect("Unexpected failure to borrow frame registry in onData") .get_mut(&frame_for_ondata) { None => vec![], Some(frame) => frame .readline .readline(d.as_bytes()) .expect("Readline failed") .into_iter() .filter_map(|ev| match ev { ReadlineEvent::Line(l) => Some(l), _ => None, }) .collect(), }; // We deliberately stop borrowing anything from RefCells here since command_handler has // lots of its own borrows, and we don't want them to conflict. for cmd in &new_lines { command_handler(&globals_for_ondata, &frame_for_ondata, cmd); } }); term.onData(&data_closure); new_data .retained_closures .replace((data_closure, resize_closure)); new_data }) } #[derive(Properties, PartialEq)] pub struct TermViewProps { pub terminal: TermFrame, pub global: GlobalCell, } #[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); term.fit.fit(); html! { {Html::VRef(term.node.clone())} } } pub fn echo_to_term_frame( global: &GlobalCell, frame_id: &TermFrame, message: &str, ) -> Result<(), &'static str> { global .frame_registry .borrow() .get(frame_id) .ok_or_else(|| "Attempt to echo to frame that doesn't exist.")? .term .write(message); Ok(()) }