diff options
| author | Kunal Mohan <kunalmohan99@gmail.com> | 2021-04-01 22:40:40 +0530 |
|---|---|---|
| committer | Kunal Mohan <kunalmohan99@gmail.com> | 2021-05-04 20:48:15 +0530 |
| commit | 223ee743e1b503f7d81c06343c23b2c786b2dbee (patch) | |
| tree | 0b7a6a996c93c80328e327197816a28447f7b65a /src/server | |
| parent | wip: helper functions for ServerInstruction (diff) | |
| download | sl0ppij-223ee743e1b503f7d81c06343c23b2c786b2dbee.tar.gz sl0ppij-223ee743e1b503f7d81c06343c23b2c786b2dbee.tar.xz sl0ppij-223ee743e1b503f7d81c06343c23b2c786b2dbee.zip | |
Move screen and plugins to client side.
Remove AppInstruction enum
spawn pty thread, screen thread and plugin thread on demand
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/mod.rs | 710 |
1 files changed, 536 insertions, 174 deletions
diff --git a/src/server/mod.rs b/src/server/mod.rs index eedc4c3f..5f122799 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,17 +1,33 @@ -use crate::client::ClientInstruction; -use crate::common::{ChannelWithContext, SenderType, SenderWithContext}; -use crate::errors::{ContextType, ErrorContext, OsContext, PtyContext, ServerContext}; -use crate::os_input_output::{ServerOsApi, ServerOsApiInstruction}; -use crate::panes::PaneId; -use crate::pty_bus::{PtyBus, PtyInstruction}; -use crate::screen::ScreenInstruction; -use crate::{cli::CliArgs, common::pty_bus::VteEvent}; +use directories_next::ProjectDirs; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; use std::path::PathBuf; use std::sync::mpsc::channel; use std::thread; -use zellij_tile::prelude::InputMode; +use std::{collections::HashMap, fs}; +use std::{ + collections::HashSet, + str::FromStr, + sync::{Arc, Mutex}, +}; +use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; +use wasmer_wasi::{Pipe, WasiState}; +use zellij_tile::data::{Event, EventType, InputMode}; + +use crate::cli::CliArgs; +use crate::client::ClientInstruction; +use crate::common::pty_bus::VteEvent; +use crate::common::{ + errors::{ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext, ServerContext}, + os_input_output::ServerOsApi, + pty_bus::{PtyBus, PtyInstruction}, + screen::{Screen, ScreenInstruction}, + wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginEnv, PluginInstruction}, + ChannelWithContext, SenderType, SenderWithContext, OPENCALLS, +}; +use crate::layout::Layout; +use crate::panes::PaneId; +use crate::panes::PositionAndSize; /// Instructions related to server-side application including the /// ones sent by client to server @@ -21,13 +37,17 @@ pub enum ServerInstruction { SplitHorizontally, SplitVertically, MoveFocus, - NewClient(String), + NewClient(String, PositionAndSize), ToPty(PtyInstruction), ToScreen(ScreenInstruction), - OsApi(ServerOsApiInstruction), + Render(String), + PluginUpdate(Option<u32>, Event), DoneClosingPane, - ClosePluginPane(u32), + DoneOpeningNewPane, + DoneUpdatingTabs, ClientExit, + ClientShouldExit, + // notify router thread to exit Exit, } impl ServerInstruction { @@ -127,8 +147,8 @@ impl ServerInstruction { pub fn screen_close_pane(pane_id: PaneId) -> Self { Self::ToScreen(ScreenInstruction::ClosePane(pane_id)) } - pub fn apply_layout(layout: (PathBuf, Vec<RawFd>)) -> Self { - Self::ToScreen(ScreenInstruction::ApplyLayout(layout)) + pub fn apply_layout(layout: PathBuf, pids: Vec<RawFd>) -> Self { + Self::ToScreen(ScreenInstruction::ApplyLayout(layout, pids)) } pub fn screen_new_tab(fd: RawFd) -> Self { Self::ToScreen(ScreenInstruction::NewTab(fd)) @@ -154,169 +174,54 @@ impl ServerInstruction { pub fn pty(fd: RawFd, event: VteEvent) -> Self { Self::ToScreen(ScreenInstruction::Pty(fd, event)) } +} - // OsApi - pub fn set_terminal_size_using_fd(fd: RawFd, cols: u16, rows: u16) -> Self { - Self::OsApi(ServerOsApiInstruction::SetTerminalSizeUsingFd( - fd, cols, rows, - )) - } - pub fn write_to_tty_stdin(fd: RawFd, buf: Vec<u8>) -> Self { - Self::OsApi(ServerOsApiInstruction::WriteToTtyStdin(fd, buf)) - } - pub fn tc_drain(fd: RawFd) -> Self { - Self::OsApi(ServerOsApiInstruction::TcDrain(fd)) +struct ClientMetaData { + pub send_pty_instructions: SenderWithContext<PtyInstruction>, + pub send_screen_instructions: SenderWithContext<ScreenInstruction>, + pub send_plugin_instructions: SenderWithContext<PluginInstruction>, + screen_thread: Option<thread::JoinHandle<()>>, + pty_thread: Option<thread::JoinHandle<()>>, + wasm_thread: Option<thread::JoinHandle<()>>, +} + +impl ClientMetaData { + fn update(&mut self, err_ctx: ErrorContext) { + self.send_plugin_instructions.update(err_ctx); + self.send_screen_instructions.update(err_ctx); + self.send_pty_instructions.update(err_ctx); } - pub fn os_exit() -> Self { - Self::OsApi(ServerOsApiInstruction::Exit) +} + +impl Drop for ClientMetaData { + fn drop(&mut self) { + let _ = self.send_pty_instructions.send(PtyInstruction::Exit); + let _ = self.send_screen_instructions.send(ScreenInstruction::Exit); + let _ = self.send_plugin_instructions.send(PluginInstruction::Exit); + let _ = self.screen_thread.take().unwrap().join(); + let _ = self.pty_thread.take().unwrap().join(); + let _ = self.wasm_thread.take().unwrap().join(); } } pub fn start_server(mut os_input: Box<dyn ServerOsApi>, opts: CliArgs) -> thread::JoinHandle<()> { - let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> = - channel(); - let mut send_pty_instructions = SenderWithContext::new( - ErrorContext::new(), - SenderType::Sender(send_pty_instructions), - ); - - let (send_os_instructions, receive_os_instructions): ChannelWithContext< - ServerOsApiInstruction, - > = channel(); - let mut send_os_instructions = SenderWithContext::new( - ErrorContext::new(), - SenderType::Sender(send_os_instructions), - ); - let (send_server_instructions, receive_server_instructions): ChannelWithContext< ServerInstruction, > = channel(); - let mut send_server_instructions = SenderWithContext::new( + let send_server_instructions = SenderWithContext::new( ErrorContext::new(), SenderType::Sender(send_server_instructions), ); - - // Don't use default layouts in tests, but do everywhere else - #[cfg(not(test))] - let default_layout = Some(PathBuf::from("default")); - #[cfg(test)] - let default_layout = None; - let maybe_layout = opts.layout.or(default_layout); - - let mut pty_bus = PtyBus::new( - receive_pty_instructions, - os_input.clone(), - send_server_instructions.clone(), - opts.debug, - ); - - let pty_thread = thread::Builder::new() - .name("pty".to_string()) - .spawn(move || loop { - let (event, mut err_ctx) = pty_bus - .receive_pty_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); - match event { - PtyInstruction::SpawnTerminal(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_server_instructions - .send(ServerInstruction::new_pane(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalVertically(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_server_instructions - .send(ServerInstruction::vertical_split(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_server_instructions - .send(ServerInstruction::horizontal_split(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::NewTab => { - if let Some(layout) = maybe_layout.clone() { - pty_bus.spawn_terminals_for_layout(layout); - } else { - let pid = pty_bus.spawn_terminal(None); - pty_bus - .send_server_instructions - .send(ServerInstruction::screen_new_tab(pid)) - .unwrap(); - } - } - PtyInstruction::ClosePane(id) => { - pty_bus.close_pane(id); - pty_bus - .send_server_instructions - .send(ServerInstruction::DoneClosingPane) - .unwrap(); - } - PtyInstruction::CloseTab(ids) => { - pty_bus.close_tab(ids); - pty_bus - .send_server_instructions - .send(ServerInstruction::DoneClosingPane) - .unwrap(); - } - PtyInstruction::Exit => { - break; - } - } - }) - .unwrap(); - - let os_thread = thread::Builder::new() - .name("os".to_string()) - .spawn({ - let mut os_input = os_input.clone(); - move || loop { - let (event, mut err_ctx) = receive_os_instructions - .recv() - .expect("failed to receive an event on the channel"); - err_ctx.add_call(ContextType::Os(OsContext::from(&event))); - match event { - ServerOsApiInstruction::SetTerminalSizeUsingFd(fd, cols, rows) => { - os_input.set_terminal_size_using_fd(fd, cols, rows); - } - ServerOsApiInstruction::WriteToTtyStdin(fd, mut buf) => { - let slice = buf.as_mut_slice(); - os_input.write_to_tty_stdin(fd, slice).unwrap(); - } - ServerOsApiInstruction::TcDrain(fd) => { - os_input.tcdrain(fd).unwrap(); - } - ServerOsApiInstruction::Exit => break, - } - } - }) - .unwrap(); - let router_thread = thread::Builder::new() .name("server_router".to_string()) .spawn({ let os_input = os_input.clone(); - let mut send_os_instructions = send_os_instructions.clone(); - let mut send_pty_instructions = send_pty_instructions.clone(); + let mut send_server_instructions = send_server_instructions.clone(); move || loop { let (instruction, err_ctx) = os_input.server_recv(); send_server_instructions.update(err_ctx); - send_pty_instructions.update(err_ctx); - send_os_instructions.update(err_ctx); match instruction { ServerInstruction::Exit => break, - ServerInstruction::ToPty(instruction) => { - send_pty_instructions.send(instruction).unwrap(); - } - ServerInstruction::OsApi(instruction) => { - send_os_instructions.send(instruction).unwrap(); - } _ => { send_server_instructions.send(instruction).unwrap(); } @@ -328,60 +233,517 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, opts: CliArgs) -> thread thread::Builder::new() .name("ipc_server".to_string()) .spawn({ + let mut clients: HashMap<String, ClientMetaData> = HashMap::new(); + // We handle only single client for now + let mut client: Option<String> = None; move || loop { let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap(); err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); - send_pty_instructions.update(err_ctx); - send_os_instructions.update(err_ctx); os_input.update_senders(err_ctx); + if let Some(ref c) = client { + clients.get_mut(c).unwrap().update(err_ctx); + } match instruction { ServerInstruction::OpenFile(file_name) => { let path = PathBuf::from(file_name); - send_pty_instructions + clients[client.as_ref().unwrap()] + .send_pty_instructions .send(PtyInstruction::SpawnTerminal(Some(path))) .unwrap(); } ServerInstruction::SplitHorizontally => { - send_pty_instructions + clients[client.as_ref().unwrap()] + .send_pty_instructions .send(PtyInstruction::SpawnTerminalHorizontally(None)) .unwrap(); } ServerInstruction::SplitVertically => { - send_pty_instructions + clients[client.as_ref().unwrap()] + .send_pty_instructions .send(PtyInstruction::SpawnTerminalVertically(None)) .unwrap(); } ServerInstruction::MoveFocus => { - os_input.send_to_client(ClientInstruction::ToScreen( - ScreenInstruction::MoveFocus, - )); + clients[client.as_ref().unwrap()] + .send_screen_instructions + .send(ScreenInstruction::MoveFocus) + .unwrap(); } - ServerInstruction::NewClient(buffer_path) => { - send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); + ServerInstruction::NewClient(buffer_path, full_screen_ws) => { + client = Some(buffer_path.clone()); + let client_data = init_client( + os_input.clone(), + opts.clone(), + send_server_instructions.clone(), + full_screen_ws, + ); + clients.insert(buffer_path.clone(), client_data); + clients[client.as_ref().unwrap()] + .send_pty_instructions + .send(PtyInstruction::NewTab) + .unwrap(); os_input.add_client_sender(buffer_path); } - ServerInstruction::ToScreen(instr) => { - os_input.send_to_client(ClientInstruction::ToScreen(instr)); + ServerInstruction::ToScreen(instruction) => { + clients[client.as_ref().unwrap()] + .send_screen_instructions + .send(instruction) + .unwrap(); + } + ServerInstruction::ToPty(instruction) => { + clients[client.as_ref().unwrap()] + .send_pty_instructions + .send(instruction) + .unwrap(); } ServerInstruction::DoneClosingPane => { os_input.send_to_client(ClientInstruction::DoneClosingPane); } - ServerInstruction::ClosePluginPane(pid) => { - os_input.send_to_client(ClientInstruction::ClosePluginPane(pid)); + ServerInstruction::DoneOpeningNewPane => { + os_input.send_to_client(ClientInstruction::DoneOpeningNewPane); + } + ServerInstruction::DoneUpdatingTabs => { + os_input.send_to_client(ClientInstruction::DoneUpdatingTabs); + } + ServerInstruction::ClientShouldExit => { + os_input.send_to_client(ClientInstruction::Exit); + } + ServerInstruction::PluginUpdate(pid, event) => { + clients[client.as_ref().unwrap()] + .send_plugin_instructions + .send(PluginInstruction::Update(pid, event)) + .unwrap(); } ServerInstruction::ClientExit => { - let _ = send_pty_instructions.send(PtyInstruction::Exit); - let _ = send_os_instructions.send(ServerOsApiInstruction::Exit); + clients.remove(client.as_ref().unwrap()).unwrap(); os_input.server_exit(); - let _ = pty_thread.join(); - let _ = os_thread.join(); let _ = router_thread.join(); let _ = os_input.send_to_client(ClientInstruction::Exit); break; } + ServerInstruction::Render(output) => { + os_input.send_to_client(ClientInstruction::Render(output)) + } _ => {} } } }) .unwrap() } + +fn init_client( + os_input: Box<dyn ServerOsApi>, + opts: CliArgs, + send_server_instructions: SenderWithContext<ServerInstruction>, + full_screen_ws: PositionAndSize, +) -> ClientMetaData { + let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); + let (send_screen_instructions, receive_screen_instructions): ChannelWithContext< + ScreenInstruction, + > = channel(); + let send_screen_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions)); + + let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext< + PluginInstruction, + > = channel(); + let send_plugin_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions)); + let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> = + channel(); + let send_pty_instructions = + SenderWithContext::new(err_ctx, SenderType::Sender(send_pty_instructions)); + + // Don't use default layouts in tests, but do everywhere else + #[cfg(not(test))] + let default_layout = Some(PathBuf::from("default")); + #[cfg(test)] + let default_layout = None; + let maybe_layout = opts.layout.or(default_layout); + + let mut pty_bus = PtyBus::new( + receive_pty_instructions, + os_input.clone(), + send_screen_instructions.clone(), + send_plugin_instructions.clone(), + opts.debug, + ); + + let pty_thread = thread::Builder::new() + .name("pty".to_string()) + .spawn({ + let send_server_instructions = send_server_instructions.clone(); + move || loop { + let (event, mut err_ctx) = pty_bus + .receive_pty_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); + match event { + PtyInstruction::SpawnTerminal(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalVertically(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::NewTab => { + if let Some(layout) = maybe_layout.clone() { + pty_bus.spawn_terminals_for_layout(layout); + } else { + let pid = pty_bus.spawn_terminal(None); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::NewTab(pid)) + .unwrap(); + } + } + PtyInstruction::ClosePane(id) => { + pty_bus.close_pane(id); + send_server_instructions + .send(ServerInstruction::DoneClosingPane) + .unwrap(); + } + PtyInstruction::CloseTab(ids) => { + pty_bus.close_tab(ids); + send_server_instructions + .send(ServerInstruction::DoneClosingPane) + .unwrap(); + } + PtyInstruction::Exit => { + break; + } + } + } + }) + .unwrap(); + + let screen_thread = thread::Builder::new() + .name("screen".to_string()) + .spawn({ + let os_input = os_input.clone(); + let send_plugin_instructions = send_plugin_instructions.clone(); + let send_pty_instructions = send_pty_instructions.clone(); + let send_server_instructions = send_server_instructions.clone(); + let max_panes = opts.max_panes; + + move || { + let mut screen = Screen::new( + receive_screen_instructions, + send_plugin_instructions, + send_pty_instructions, + send_server_instructions, + &full_screen_ws, + os_input, + max_panes, + InputMode::Normal, + ); + loop { + let (event, mut err_ctx) = screen + .receiver + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + screen.send_server_instructions.update(err_ctx); + screen.send_pty_instructions.update(err_ctx); + screen.send_plugin_instructions.update(err_ctx); + match event { + ScreenInstruction::Pty(pid, vte_event) => { + screen + .get_active_tab_mut() + .unwrap() + .handle_pty_event(pid, vte_event); + } + ScreenInstruction::Render => { + screen.render(); + } + ScreenInstruction::NewPane(pid) => { + screen.get_active_tab_mut().unwrap().new_pane(pid); + screen + .send_server_instructions + .send(ServerInstruction::DoneOpeningNewPane) + .unwrap(); + } + ScreenInstruction::HorizontalSplit(pid) => { + screen.get_active_tab_mut().unwrap().horizontal_split(pid); + screen + .send_server_instructions + .send(ServerInstruction::DoneOpeningNewPane) + .unwrap(); + } + ScreenInstruction::VerticalSplit(pid) => { + screen.get_active_tab_mut().unwrap().vertical_split(pid); + screen + .send_server_instructions + .send(ServerInstruction::DoneOpeningNewPane) + .unwrap(); + } + ScreenInstruction::WriteCharacter(bytes) => { + screen + .get_active_tab_mut() + .unwrap() + .write_to_active_terminal(bytes); + } + ScreenInstruction::ResizeLeft => { + screen.get_active_tab_mut().unwrap().resize_left(); + } + ScreenInstruction::ResizeRight => { + screen.get_active_tab_mut().unwrap().resize_right(); + } + ScreenInstruction::ResizeDown => { + screen.get_active_tab_mut().unwrap().resize_down(); + } + ScreenInstruction::ResizeUp => { + screen.get_active_tab_mut().unwrap().resize_up(); + } + ScreenInstruction::MoveFocus => { + screen.get_active_tab_mut().unwrap().move_focus(); + } + ScreenInstruction::MoveFocusLeft => { + screen.get_active_tab_mut().unwrap().move_focus_left(); + } + ScreenInstruction::MoveFocusDown => { + screen.get_active_tab_mut().unwrap().move_focus_down(); + } + ScreenInstruction::MoveFocusRight => { + screen.get_active_tab_mut().unwrap().move_focus_right(); + } + ScreenInstruction::MoveFocusUp => { + screen.get_active_tab_mut().unwrap().move_focus_up(); + } + ScreenInstruction::ScrollUp => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_up(); + } + ScreenInstruction::ScrollDown => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_down(); + } + ScreenInstruction::ClearScroll => { + screen + .get_active_tab_mut() + .unwrap() + .clear_active_terminal_scroll(); + } + ScreenInstruction::CloseFocusedPane => { + screen.get_active_tab_mut().unwrap().close_focused_pane(); + screen.render(); + } + ScreenInstruction::SetSelectable(id, selectable) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_selectable(id, selectable); + } + ScreenInstruction::SetMaxHeight(id, max_height) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_max_height(id, max_height); + } + ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_invisible_borders(id, invisible_borders); + screen.render(); + } + ScreenInstruction::ClosePane(id) => { + screen.get_active_tab_mut().unwrap().close_pane(id); + screen.render(); + } + ScreenInstruction::ToggleActiveTerminalFullscreen => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_active_pane_fullscreen(); + } + ScreenInstruction::NewTab(pane_id) => { + screen.new_tab(pane_id); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::SwitchTabNext => { + screen.switch_tab_next(); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::SwitchTabPrev => { + screen.switch_tab_prev(); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::CloseTab => { + screen.close_tab(); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::ApplyLayout(layout, new_pane_pids) => { + screen.apply_layout(Layout::new(layout), new_pane_pids); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::GoToTab(tab_index) => { + screen.go_to_tab(tab_index as usize); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::UpdateTabName(c) => { + screen.update_active_tab_name(c); + screen + .send_server_instructions + .send(ServerInstruction::DoneUpdatingTabs) + .unwrap(); + } + ScreenInstruction::ChangeInputMode(input_mode) => { + screen.change_input_mode(input_mode); + } + ScreenInstruction::Exit => { + break; + } + } + } + } + }) + .unwrap(); + + let wasm_thread = thread::Builder::new() + .name("wasm".to_string()) + .spawn({ + let mut send_screen_instructions = send_screen_instructions.clone(); + let mut send_pty_instructions = send_pty_instructions.clone(); + + let store = Store::default(); + let mut plugin_id = 0; + let mut plugin_map = HashMap::new(); + move || loop { + let (event, mut err_ctx) = receive_plugin_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + send_screen_instructions.update(err_ctx); + send_pty_instructions.update(err_ctx); + match event { + PluginInstruction::Load(pid_tx, path) => { + let project_dirs = + ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); + let plugin_dir = project_dirs.data_dir().join("plugins/"); + let wasm_bytes = fs::read(&path) + .or_else(|_| fs::read(&path.with_extension("wasm"))) + .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) + .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); + + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that + let module = Module::new(&store, &wasm_bytes).unwrap(); + + let output = Pipe::new(); + let input = Pipe::new(); + let mut wasi_env = WasiState::new("Zellij") + .env("CLICOLOR_FORCE", "1") + .preopen(|p| { + p.directory(".") // FIXME: Change this to a more meaningful dir + .alias(".") + .read(true) + .write(true) + .create(true) + }) + .unwrap() + .stdin(Box::new(input)) + .stdout(Box::new(output)) + .finalize() + .unwrap(); + + let wasi = wasi_env.import_object(&module).unwrap(); + + let plugin_env = PluginEnv { + plugin_id, + send_screen_instructions: send_screen_instructions.clone(), + send_pty_instructions: send_pty_instructions.clone(), + wasi_env, + subscriptions: Arc::new(Mutex::new(HashSet::new())), + }; + + let zellij = zellij_imports(&store, &plugin_env); + let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap(); + + let start = instance.exports.get_function("_start").unwrap(); + + // This eventually calls the `.init()` method + start.call(&[]).unwrap(); + + plugin_map.insert(plugin_id, (instance, plugin_env)); + pid_tx.send(plugin_id).unwrap(); + plugin_id += 1; + } + PluginInstruction::Update(pid, event) => { + for (&i, (instance, plugin_env)) in &plugin_map { + let subs = plugin_env.subscriptions.lock().unwrap(); + // FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType? + let event_type = EventType::from_str(&event.to_string()).unwrap(); + if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) { + let update = instance.exports.get_function("update").unwrap(); + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&event).unwrap(), + ); + update.call(&[]).unwrap(); + } + } + drop(send_screen_instructions.send(ScreenInstruction::Render)); + } + PluginInstruction::Render(buf_tx, pid, rows, cols) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + + let render = instance.exports.get_function("render").unwrap(); + + render + .call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) + .unwrap(); + + buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); + } + PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), + PluginInstruction::Exit => break, + } + } + }) + .unwrap(); + ClientMetaData { + send_plugin_instructions, + send_screen_instructions, + send_pty_instructions, + screen_thread: Some(screen_thread), + pty_thread: Some(pty_thread), + wasm_thread: Some(wasm_thread), + } +} |
