#![allow(missing_docs)]
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use crate::Result;
use crate::capture::CapturedFrame;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use crate::clipboard::WebClipboardProvider;
use crate::core::component::Component;
use crate::core::event::{KeyCode, KeyEvent, KeyMods, MouseEvent};
use crate::style::Rect;
use crate::test_backend::TestBackend;
pub fn mount_web<C: Component>(
component: C,
props: C::Properties,
term: JsValue,
cols: u16,
rows: u16,
) -> Result<WebTerminal<C>> {
WebTerminal::new(component, props, term, cols, rows)
}
pub struct WebTerminal<C: Component> {
backend: TestBackend<C>,
term: JsValue,
prev_frame: Option<CapturedFrame>,
effect_phase: u64,
}
impl<C: Component> WebTerminal<C> {
pub fn new(
component: C,
props: C::Properties,
term: JsValue,
cols: u16,
rows: u16,
) -> Result<Self> {
console_error_panic_hook::set_once();
let viewport = Rect {
x: 0,
y: 0,
w: cols.max(1),
h: rows.max(1),
};
let mut backend = TestBackend::new_with_props(component, props);
#[cfg(all(target_arch = "wasm32", feature = "web"))]
{
backend
.core
.ctx
.env()
.clipboard
.replace_provider(Box::new(WebClipboardProvider::new()));
}
backend.set_viewport(viewport);
backend.render();
let _ = backend.pump()?;
let mut s = Self {
backend,
term,
prev_frame: None,
effect_phase: 0,
};
s.paint_now()?;
xterm_write(&s.term, "\x1b[?1000h\x1b[?1002h\x1b[?1006h");
Ok(s)
}
pub fn dispatch_key_event(&mut self, ev: &web_sys::KeyboardEvent) -> Result<bool> {
let Some(key) = keyboard_event_to_key_event(ev) else {
return Ok(false);
};
let handled = self.backend.send_key(key)?;
if handled {
self.backend.render();
}
self.paint_now()?;
Ok(handled)
}
pub fn prime_clipboard_text(&mut self, text: String) {
self.backend
.core
.ctx
.env()
.clipboard
.set_clipboard_text_cache(text);
}
pub fn set_viewport(&mut self, cols: u16, rows: u16) -> Result<()> {
let viewport = Rect {
x: 0,
y: 0,
w: cols.max(1),
h: rows.max(1),
};
if self.backend.viewport() == viewport {
return Ok(());
}
self.prev_frame = None;
self.backend.set_viewport(viewport);
self.backend.render();
let _ = self.backend.pump()?;
self.paint_now()
}
pub fn dispatch_mouse_event(&mut self, event: MouseEvent) -> Result<()> {
let handled = self.backend.send_mouse(event)?;
if handled {
self.backend.render();
}
self.paint_now()
}
pub fn dispatch_message(&mut self, msg: C::Message) -> Result<()> {
if self.backend.dispatch(msg)? {
self.paint_now()?;
}
Ok(())
}
pub fn set_effect_phase(&mut self, phase: u64) {
self.effect_phase = phase;
self.backend.core.set_effect_phase(phase);
}
fn paint_now(&mut self) -> Result<()> {
self.effect_phase = self.effect_phase.wrapping_add(1);
self.backend.core.set_effect_phase(self.effect_phase);
let frame = self
.backend
.capture_frame_with_effect_phase(self.effect_phase);
xterm_write(&self.term, &frame.to_ansi_diff(self.prev_frame.as_ref()));
self.prev_frame = Some(frame);
Ok(())
}
}
fn xterm_write(term: &JsValue, data: &str) {
if let Ok(v) = js_sys::Reflect::get(term, &JsValue::from_str("write")) {
if let Ok(f) = v.dyn_into::<js_sys::Function>() {
let _ = f.call1(term, &JsValue::from_str(data));
}
}
}
fn keyboard_event_to_key_event(ev: &web_sys::KeyboardEvent) -> Option<KeyEvent> {
let mods = KeyMods {
ctrl: ev.ctrl_key(),
alt: ev.alt_key(),
shift: ev.shift_key(),
super_key: ev.meta_key(),
};
let key = ev.key();
let code = match key.as_str() {
"Enter" => KeyCode::Enter,
"Escape" | "Esc" => KeyCode::Esc,
"Tab" => KeyCode::Tab,
"Backspace" => KeyCode::Backspace,
"Delete" => KeyCode::Delete,
"Insert" => KeyCode::Insert,
"Home" => KeyCode::Home,
"End" => KeyCode::End,
"PageUp" => KeyCode::PageUp,
"PageDown" => KeyCode::PageDown,
"ArrowUp" => KeyCode::Up,
"ArrowDown" => KeyCode::Down,
"ArrowLeft" => KeyCode::Left,
"ArrowRight" => KeyCode::Right,
" " => KeyCode::Char(' '),
k if k.chars().count() == 1 => {
let mut ch = k.chars().next()?;
if mods.ctrl && ch.is_ascii_alphabetic() {
ch = ch.to_ascii_lowercase();
}
KeyCode::Char(ch)
}
k if k.starts_with('F') && k.len() > 1 => {
let n: u8 = k[1..].parse().ok()?;
if (1..=12).contains(&n) {
KeyCode::F(n)
} else {
return None;
}
}
_ => return None,
};
Some(KeyEvent { code, mods })
}