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 | |
| 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')
| -rw-r--r-- | src/client/mod.rs | 440 | ||||
| -rw-r--r-- | src/client/panes/terminal_pane.rs | 2 | ||||
| -rw-r--r-- | src/client/tab.rs | 402 | ||||
| -rw-r--r-- | src/common/errors.rs | 83 | ||||
| -rw-r--r-- | src/common/input/handler.rs | 152 | ||||
| -rw-r--r-- | src/common/os_input_output.rs | 22 | ||||
| -rw-r--r-- | src/common/pty_bus.rs | 85 | ||||
| -rw-r--r-- | src/common/screen.rs | 36 | ||||
| -rw-r--r-- | src/common/wasm_vm.rs | 9 | ||||
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/server/mod.rs | 710 | ||||
| -rw-r--r-- | src/tests/fakes.rs | 7 |
12 files changed, 921 insertions, 1031 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index e91ad07e..ae298a5e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -4,66 +4,32 @@ pub mod pane_resizer; pub mod panes; pub mod tab; +use serde::{Deserialize, Serialize}; +use std::io::Write; use std::sync::mpsc; use std::thread; -use std::{collections::HashMap, fs}; -use std::{ - collections::HashSet, - io::Write, - str::FromStr, - sync::{Arc, Mutex}, -}; - -use directories_next::ProjectDirs; -use serde::{Deserialize, Serialize}; -use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; -use wasmer_wasi::{Pipe, WasiState}; -use zellij_tile::data::{EventType, InputMode}; -use crate::cli::CliArgs; use crate::common::{ command_is_executing::CommandIsExecuting, - errors::{AppContext, ContextType, PluginContext, ScreenContext}, + errors::{ClientContext, ContextType}, input::handler::input_loop, os_input_output::ClientOsApi, - pty_bus::PtyInstruction, - screen::{Screen, ScreenInstruction}, - wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginEnv, PluginInstruction}, - ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext, OPENCALLS, + SenderType, SenderWithContext, SyncChannelWithContext, OPENCALLS, }; -use crate::layout::Layout; use crate::server::ServerInstruction; -/// Instructions sent from server to client +/// Instructions related to the client-side application and sent from server to client #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ClientInstruction { - ToScreen(ScreenInstruction), - ClosePluginPane(u32), Error(String), + Render(String), DoneClosingPane, + DoneOpeningNewPane, + DoneUpdatingTabs, Exit, } -/// Instructions related to the client-side application. -#[derive(Clone)] -pub enum AppInstruction { - Exit, - Error(String), - ToPty(PtyInstruction), - DoneClosingPane, -} - -impl From<ClientInstruction> for AppInstruction { - fn from(item: ClientInstruction) -> Self { - match item { - ClientInstruction::Error(e) => AppInstruction::Error(e), - ClientInstruction::DoneClosingPane => AppInstruction::DoneClosingPane, - _ => panic!("Unsupported AppInstruction"), - } - } -} - -pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) { +pub fn start_client(mut os_input: Box<dyn ClientOsApi>) { let take_snapshot = "\u{1b}[?1049h"; os_input.unset_raw_mode(0); let _ = os_input @@ -75,378 +41,68 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) { let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.set_raw_mode(0); - let (send_screen_instructions, receive_screen_instructions): ChannelWithContext< - ScreenInstruction, - > = mpsc::channel(); let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); - let mut send_screen_instructions = - SenderWithContext::new(err_ctx, SenderType::Sender(send_screen_instructions)); - - let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext< - PluginInstruction, - > = mpsc::channel(); - let send_plugin_instructions = - SenderWithContext::new(err_ctx, SenderType::Sender(send_plugin_instructions)); - let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> = - mpsc::sync_channel(500); - let mut send_app_instructions = - SenderWithContext::new(err_ctx, SenderType::SyncSender(send_app_instructions)); + let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< + ClientInstruction, + > = mpsc::sync_channel(500); + let mut send_client_instructions = + SenderWithContext::new(err_ctx, SenderType::SyncSender(send_client_instructions)); - os_input.connect_to_server(); + os_input.connect_to_server(full_screen_ws); #[cfg(not(test))] std::panic::set_hook({ use crate::errors::handle_panic; - let send_app_instructions = send_app_instructions.clone(); + let send_client_instructions = send_client_instructions.clone(); Box::new(move |info| { - handle_panic(info, &send_app_instructions); + handle_panic(info, &send_client_instructions); }) }); - let screen_thread = thread::Builder::new() - .name("screen".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - let os_input = os_input.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - let send_app_instructions = send_app_instructions.clone(); - let max_panes = opts.max_panes; - - move || { - let mut screen = Screen::new( - receive_screen_instructions, - send_plugin_instructions, - send_app_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_app_instructions.update(err_ctx); - screen.os_api.update_senders(err_ctx); - if let Some(t) = screen.get_active_tab_mut() { - t.os_api.update_senders(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); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); - command_is_executing.done_opening_new_pane(); - } - 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); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::SwitchTabNext => { - screen.switch_tab_next(); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::SwitchTabPrev => { - screen.switch_tab_prev(); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::CloseTab => { - screen.close_tab(); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { - screen.apply_layout(Layout::new(layout), new_pane_pids); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::GoToTab(tab_index) => { - screen.go_to_tab(tab_index as usize); - command_is_executing.done_updating_tabs(); - } - ScreenInstruction::UpdateTabName(c) => { - screen.update_active_tab_name(c); - command_is_executing.done_updating_tabs(); - } - 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_app_instructions = send_app_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_app_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_app_instructions: send_app_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(); - let _stdin_thread = thread::Builder::new() .name("stdin_handler".to_string()) .spawn({ - let send_screen_instructions = send_screen_instructions.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - let send_app_instructions = send_app_instructions.clone(); + let send_client_instructions = send_client_instructions.clone(); let command_is_executing = command_is_executing.clone(); let os_input = os_input.clone(); - move || { - input_loop( - os_input, - command_is_executing, - send_screen_instructions, - send_plugin_instructions, - send_app_instructions, - ) - } + move || input_loop(os_input, command_is_executing, send_client_instructions) }); let router_thread = thread::Builder::new() .name("router".to_string()) .spawn({ - let mut send_screen_instructions = send_screen_instructions.clone(); - let mut send_plugin_instructions = send_plugin_instructions.clone(); let os_input = os_input.clone(); - move || loop { - let (instruction, err_ctx) = os_input.client_recv(); - send_app_instructions.update(err_ctx); - send_screen_instructions.update(err_ctx); - send_plugin_instructions.update(err_ctx); - match instruction { - ClientInstruction::Exit => break, - ClientInstruction::ClosePluginPane(p) => { - send_plugin_instructions - .send(PluginInstruction::Unload(p)) - .unwrap(); - } - ClientInstruction::ToScreen(s) => send_screen_instructions.send(s).unwrap(), - _ => { - send_app_instructions - .send(AppInstruction::from(instruction)) - .unwrap(); + move || { + loop { + let (instruction, err_ctx) = os_input.client_recv(); + send_client_instructions.update(err_ctx); + if let ClientInstruction::Exit = instruction { + break; } + send_client_instructions.send(instruction).unwrap(); } + send_client_instructions + .send(ClientInstruction::Exit) + .unwrap(); } }) .unwrap(); #[warn(clippy::never_loop)] loop { - let (app_instruction, mut err_ctx) = receive_app_instructions + let (client_instruction, mut err_ctx) = receive_client_instructions .recv() .expect("failed to receive app instruction on channel"); - err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction))); - send_screen_instructions.update(err_ctx); + err_ctx.add_call(ContextType::Client(ClientContext::from( + &client_instruction, + ))); os_input.update_senders(err_ctx); - match app_instruction { - AppInstruction::Exit => break, - AppInstruction::Error(backtrace) => { + match client_instruction { + ClientInstruction::Exit => break, + ClientInstruction::Error(backtrace) => { let _ = os_input.send_to_server(ServerInstruction::ClientExit); - let _ = send_screen_instructions.send(ScreenInstruction::Exit); - let _ = send_plugin_instructions.send(PluginInstruction::Exit); - let _ = screen_thread.join(); - let _ = wasm_thread.join(); os_input.unset_raw_mode(0); let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); let error = format!("{}\n{}", goto_start_of_last_line, backtrace); @@ -456,18 +112,20 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) { .unwrap(); std::process::exit(1); } - AppInstruction::ToPty(instruction) => { - os_input.send_to_server(ServerInstruction::ToPty(instruction)); + ClientInstruction::Render(output) => { + let mut stdout = os_input.get_stdout_writer(); + stdout + .write_all(&output.as_bytes()) + .expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); } - AppInstruction::DoneClosingPane => command_is_executing.done_closing_pane(), + ClientInstruction::DoneClosingPane => command_is_executing.done_closing_pane(), + ClientInstruction::DoneOpeningNewPane => command_is_executing.done_opening_new_pane(), + ClientInstruction::DoneUpdatingTabs => command_is_executing.done_updating_tabs(), } } let _ = os_input.send_to_server(ServerInstruction::ClientExit); - let _ = send_screen_instructions.send(ScreenInstruction::Exit); - let _ = send_plugin_instructions.send(PluginInstruction::Exit); - screen_thread.join().unwrap(); - wasm_thread.join().unwrap(); router_thread.join().unwrap(); // cleanup(); @@ -481,9 +139,7 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs) { ); os_input.unset_raw_mode(0); - let _ = os_input - .get_stdout_writer() - .write(goodbye_message.as_bytes()) - .unwrap(); - os_input.get_stdout_writer().flush().unwrap(); + let mut stdout = os_input.get_stdout_writer(); + let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); + stdout.flush().unwrap(); } diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index 1834e722..9fcddc82 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -20,7 +20,7 @@ pub enum PaneId { /// Contains the position and size of a [`Pane`], or more generally of any terminal, measured /// in character rows and columns. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub struct PositionAndSize { pub x: usize, pub y: usize, diff --git a/src/client/tab.rs b/src/client/tab.rs index c285cc0b..4eabab10 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -2,10 +2,9 @@ //! as well as how they should be resized use crate::boundaries::colors; -use crate::client::AppInstruction; use crate::common::{input::handler::parse_keys, SenderWithContext}; use crate::layout::Layout; -use crate::os_input_output::{ClientOsApi, ServerOsApiInstruction}; +use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize, TerminalPane}; use crate::pty_bus::{PtyInstruction, VteEvent}; use crate::server::ServerInstruction; @@ -13,13 +12,12 @@ use crate::utils::shared::pad_to_size; use crate::wasm_vm::PluginInstruction; use crate::{boundaries::Boundaries, panes::PluginPane}; use std::os::unix::io::RawFd; -use std::time::Instant; +use std::sync::mpsc::channel; use std::{ cmp::Reverse, collections::{BTreeMap, HashSet}, }; -use std::{io::Write, sync::mpsc::channel}; -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette}; +use zellij_tile::data::{Event, InputMode}; const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this @@ -68,9 +66,11 @@ pub struct Tab { max_panes: Option<usize>, full_screen_ws: PositionAndSize, fullscreen_is_active: bool, - pub os_api: Box<dyn ClientOsApi>, - pub send_plugin_instructions: SenderWithContext<PluginInstruction>, - pub send_app_instructions: SenderWithContext<AppInstruction>, + os_api: Box<dyn ServerOsApi>, + send_plugin_instructions: SenderWithContext<PluginInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, + expansion_boundary: Option<PositionAndSize>, should_clear_display_before_rendering: bool, pub mode_info: ModeInfo, pub input_mode: InputMode, @@ -224,9 +224,10 @@ impl Tab { position: usize, name: String, full_screen_ws: &PositionAndSize, - os_api: Box<dyn ClientOsApi>, + mut os_api: Box<dyn ServerOsApi>, send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, max_panes: Option<usize>, pane_id: Option<PaneId>, mode_info: ModeInfo, @@ -235,13 +236,11 @@ impl Tab { ) -> Self { let panes = if let Some(PaneId::Terminal(pid)) = pane_id { let new_terminal = TerminalPane::new(pid, *full_screen_ws); - os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ), - )); + os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ); let mut panes: BTreeMap<PaneId, Box<dyn Pane>> = BTreeMap::new(); panes.insert(PaneId::Terminal(pid), Box::new(new_terminal)); panes @@ -260,8 +259,10 @@ impl Tab { fullscreen_is_active: false, synchronize_is_active: false, os_api, - send_app_instructions, send_plugin_instructions, + send_pty_instructions, + send_server_instructions, + expansion_boundary: None, should_clear_display_before_rendering: false, mode_info, input_mode, @@ -294,13 +295,11 @@ impl Tab { terminal_pane.set_max_width(max_columns); } terminal_pane.change_pos_and_size(&position_and_size); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - *pid, - position_and_size.columns as u16, - position_and_size.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + *pid, + position_and_size.columns as u16, + position_and_size.rows as u16, + ); } None => { // we filled the entire layout, no room for this pane @@ -342,13 +341,11 @@ impl Tab { // there are still panes left to fill, use the pids we received in this method let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout let new_terminal = TerminalPane::new(*pid, *position_and_size); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ); self.panes .insert(PaneId::Terminal(*pid), Box::new(new_terminal)); } @@ -357,10 +354,9 @@ impl Tab { // this is a bit of a hack and happens because we don't have any central location that // can query the screen as to how many panes it needs to create a layout // fixing this will require a bit of an architecture change - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(PaneId::Terminal( - *unused_pid, - ))); + self.send_pty_instructions + .send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) + .unwrap(); } self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); self.render(); @@ -373,13 +369,11 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -405,8 +399,9 @@ impl Tab { }, ); if terminal_id_to_split.is_none() { - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty + self.send_pty_instructions + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; // likely no terminal large enough to split } let terminal_id_to_split = terminal_id_to_split.unwrap(); @@ -424,23 +419,19 @@ impl Tab { if let PaneId::Terminal(term_pid) = pid { let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + bottom_winsize.columns as u16, + bottom_winsize.rows as u16, + ); terminal_to_split.change_pos_and_size(&top_winsize); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - terminal_id_to_split, - top_winsize.columns as u16, - top_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + terminal_id_to_split, + top_winsize.columns as u16, + top_winsize.rows as u16, + ); } self.active_terminal = Some(pid); } @@ -448,23 +439,19 @@ impl Tab { if let PaneId::Terminal(term_pid) = pid { let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); let new_terminal = TerminalPane::new(term_pid, right_winsize); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - right_winsize.columns as u16, - right_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + right_winsize.columns as u16, + right_winsize.rows as u16, + ); terminal_to_split.change_pos_and_size(&left_winsize); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - terminal_id_to_split, - left_winsize.columns as u16, - left_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + terminal_id_to_split, + left_winsize.columns as u16, + left_winsize.rows as u16, + ); } } } @@ -480,13 +467,11 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -495,8 +480,9 @@ impl Tab { let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty + self.send_pty_instructions + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; } let terminal_ws = PositionAndSize { @@ -510,23 +496,19 @@ impl Tab { active_pane.change_pos_and_size(&top_winsize); let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + bottom_winsize.columns as u16, + bottom_winsize.rows as u16, + ); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - *active_terminal_pid, - top_winsize.columns as u16, - top_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + *active_terminal_pid, + top_winsize.columns as u16, + top_winsize.rows as u16, + ); } self.active_terminal = Some(pid); @@ -541,13 +523,11 @@ impl Tab { if !self.has_panes() { if let PaneId::Terminal(term_pid) = pid { let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.columns() as u16, + new_terminal.rows() as u16, + ); self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } @@ -556,8 +536,9 @@ impl Tab { let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(pid)); // we can't open this pane, close the pty + self.send_pty_instructions + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); // we can't open this pane, close the pty return; } let terminal_ws = PositionAndSize { @@ -571,23 +552,19 @@ impl Tab { active_pane.change_pos_and_size(&left_winsize); let new_terminal = TerminalPane::new(term_pid, right_winsize); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - new_terminal.pid, - right_winsize.columns as u16, - right_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + right_winsize.columns as u16, + right_winsize.rows as u16, + ); self.panes.insert(pid, Box::new(new_terminal)); if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - *active_terminal_pid, - left_winsize.columns as u16, - left_winsize.rows as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + *active_terminal_pid, + left_winsize.columns as u16, + left_winsize.rows as u16, + ); } self.active_terminal = Some(pid); @@ -644,13 +621,13 @@ impl Tab { match self.get_active_pane_id() { Some(PaneId::Terminal(active_terminal_id)) => { let active_terminal = self.get_active_pane().unwrap(); - let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::WriteToTtyStdin(active_terminal_id, adjusted_input), - )); - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::TcDrain(active_terminal_id), - )); + let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); + self.os_api + .write_to_tty_stdin(active_terminal_id, &mut adjusted_input) + .expect("failed to write to terminal"); + self.os_api + .tcdrain(active_terminal_id) + .expect("failed to drain terminal"); } Some(PaneId::Plugin(pid)) => { for key in parse_keys(&input_bytes) { @@ -712,13 +689,11 @@ impl Tab { } let active_terminal = self.panes.get(&active_pane_id).unwrap(); if let PaneId::Terminal(active_pid) = active_pane_id { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - active_pid, - active_terminal.columns() as u16, - active_terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + active_pid, + active_terminal.columns() as u16, + active_terminal.rows() as u16, + ); } self.render(); self.toggle_fullscreen_is_active(); @@ -747,30 +722,16 @@ impl Tab { // in that case, we should not render as the app is exiting return; } - // if any pane contain widechar, all pane in the same row will messup. We should render them every time - // FIXME: remove this when we can handle widechars correctly - if self.panes_contain_widechar() { - self.set_force_render() - } - let mut stdout = self.os_api.get_stdout_writer(); + let mut output = String::new(); let mut boundaries = Boundaries::new( self.full_screen_ws.columns as u16, self.full_screen_ws.rows as u16, ); let hide_cursor = "\u{1b}[?25l"; - stdout - .write_all(&hide_cursor.as_bytes()) - .expect("cannot write to stdout"); - if self.should_clear_display_before_rendering { - let clear_display = "\u{1b}[2J"; - stdout - .write_all(&clear_display.as_bytes()) - .expect("cannot write to stdout"); - self.should_clear_display_before_rendering = false; - } - for (kind, pane) in self.panes.iter_mut() { - if !self.panes_to_hide.contains(&pane.pid()) { - match self.active_terminal.unwrap() == pane.pid() { + output.push_str(hide_cursor); + for (kind, terminal) in self.panes.iter_mut() { + if !self.panes_to_hide.contains(&terminal.pid()) { + match self.active_terminal.unwrap() == terminal.pid() { true => { pane.set_active_at(Instant::now()); boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(self.colors)) @@ -784,48 +745,39 @@ impl Tab { adjust_to_size(&vte_output, pane.rows(), pane.columns()) }; // FIXME: Use Termion for cursor and style clearing? - write!( - stdout, + output.push_str(&format!( "\u{1b}[{};{}H\u{1b}[m{}", pane.y() + 1, pane.x() + 1, vte_output - ) - .expect("cannot write to stdout"); + )); } } } // TODO: only render (and calculate) boundaries if there was a resize - let vte_output = boundaries.vte_output(); - stdout - .write_all(&vte_output.as_bytes()) - .expect("cannot write to stdout"); + output.push_str(&boundaries.vte_output()); match self.get_active_terminal_cursor_position() { Some((cursor_position_x, cursor_position_y)) => { let show_cursor = "\u{1b}[?25h"; - let goto_cursor_position = format!( + let goto_cursor_position = &format!( "\u{1b}[{};{}H\u{1b}[m", cursor_position_y + 1, cursor_position_x + 1 ); // goto row/col - stdout - .write_all(&show_cursor.as_bytes()) - .expect("cannot write to stdout"); - stdout - .write_all(&goto_cursor_position.as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); + output.push_str(show_cursor); + output.push_str(goto_cursor_position); } None => { let hide_cursor = "\u{1b}[?25l"; - stdout - .write_all(&hide_cursor.as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); + output.push_str(hide_cursor); } } + + self.send_server_instructions + .send(ServerInstruction::Render(output)) + .unwrap(); } fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { self.panes.iter() @@ -1278,104 +1230,88 @@ impl Tab { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_down(count); if let PaneId::Terminal(pid) = id { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + *pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_height_up(count); if let PaneId::Terminal(pid) = id { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + *pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_down(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_height_up(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_right(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.increase_width_left(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_right(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { let terminal = self.panes.get_mut(id).unwrap(); terminal.reduce_width_left(count); if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.send_to_server(ServerInstruction::OsApi( - ServerOsApiInstruction::SetTerminalSizeUsingFd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ), - )); + self.os_api.set_terminal_size_using_fd( + pid, + terminal.columns() as u16, + terminal.rows() as u16, + ); } } fn pane_is_between_vertical_borders( @@ -2144,8 +2080,9 @@ impl Tab { if let Some(max_panes) = self.max_panes { let terminals = self.get_pane_ids(); for &pid in terminals.iter().skip(max_panes - 1) { - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(pid)); + self.send_pty_instructions + .send(PtyInstruction::ClosePane(pid)) + .unwrap(); self.close_pane_without_rerender(pid); } } @@ -2255,8 +2192,9 @@ impl Tab { pub fn close_focused_pane(&mut self) { if let Some(active_pane_id) = self.get_active_pane_id() { self.close_pane(active_pane_id); - self.os_api - .send_to_server(ServerInstruction::pty_close_pane(active_pane_id)); + self.send_pty_instructions + .send(PtyInstruction::ClosePane(active_pane_id)) + .unwrap(); } } pub fn scroll_active_terminal_up(&mut self) { diff --git a/src/common/errors.rs b/src/common/errors.rs index 3ee36752..720341a4 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,8 +1,8 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{os_input_output::ServerOsApiInstruction, ServerInstruction, OPENCALLS}; -use crate::client::AppInstruction; +use super::{ServerInstruction, OPENCALLS}; +use crate::client::ClientInstruction; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; @@ -21,7 +21,7 @@ use std::panic::PanicInfo; #[cfg(not(test))] pub fn handle_panic( info: &PanicInfo<'_>, - send_app_instructions: &SenderWithContext<AppInstruction>, + send_app_instructions: &SenderWithContext<ClientInstruction>, ) { use backtrace::Backtrace; use std::{process, thread}; @@ -68,9 +68,7 @@ pub fn handle_panic( println!("{}", backtrace); process::exit(1); } else { - send_app_instructions - .send(AppInstruction::Error(backtrace)) - .unwrap(); + let _ = send_app_instructions.send(ClientInstruction::Error(backtrace)); } } @@ -132,19 +130,17 @@ impl Display for ErrorContext { /// /// Complex variants store a variant of a related enum, whose variants can be built from /// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`], -/// [`PtyInstruction`], [`AppInstruction`], etc). +/// [`PtyInstruction`], [`ClientInstruction`], etc). #[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum ContextType { /// A screen-related call. Screen(ScreenContext), /// A PTY-related call. Pty(PtyContext), - /// An OS-related call. - Os(OsContext), /// A plugin-related call. Plugin(PluginContext), /// An app-related call. - App(AppContext), + Client(ClientContext), /// A server-related call. IPCServer(ServerContext), StdinHandler, @@ -162,9 +158,8 @@ impl Display for ContextType { match *self { ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c), ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c), - ContextType::Os(c) => write!(f, "{}os_thread: {}{:?}", purple, green, c), ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c), - ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c), + ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c), ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c), ContextType::StdinHandler => { write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green) @@ -257,7 +252,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, - ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout, + ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, ScreenInstruction::NewTab(_) => ScreenContext::NewTab, ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, @@ -297,28 +292,6 @@ impl From<&PtyInstruction> for PtyContext { } } -/// Stack call representations corresponding to the different types of [`ServerOsApiInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum OsContext { - SetTerminalSizeUsingFd, - WriteToTtyStdin, - TcDrain, - Exit, -} - -impl From<&ServerOsApiInstruction> for OsContext { - fn from(os_instruction: &ServerOsApiInstruction) -> Self { - match *os_instruction { - ServerOsApiInstruction::SetTerminalSizeUsingFd(_, _, _) => { - OsContext::SetTerminalSizeUsingFd - } - ServerOsApiInstruction::WriteToTtyStdin(_, _) => OsContext::WriteToTtyStdin, - ServerOsApiInstruction::TcDrain(_) => OsContext::TcDrain, - ServerOsApiInstruction::Exit => OsContext::Exit, - } - } -} - // FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write use crate::wasm_vm::PluginInstruction; @@ -345,22 +318,26 @@ impl From<&PluginInstruction> for PluginContext { } } -/// Stack call representations corresponding to the different types of [`AppInstruction`]s. +/// Stack call representations corresponding to the different types of [`ClientInstruction`]s. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum AppContext { +pub enum ClientContext { Exit, Error, DoneClosingPane, - ToPty, + DoneOpeningNewPane, + DoneUpdatingTabs, + Render, } -impl From<&AppInstruction> for AppContext { - fn from(app_instruction: &AppInstruction) -> Self { - match *app_instruction { - AppInstruction::Exit => AppContext::Exit, - AppInstruction::Error(_) => AppContext::Error, - AppInstruction::DoneClosingPane => AppContext::DoneClosingPane, - AppInstruction::ToPty(_) => AppContext::ToPty, +impl From<&ClientInstruction> for ClientContext { + fn from(client_instruction: &ClientInstruction) -> Self { + match *client_instruction { + ClientInstruction::Exit => ClientContext::Exit, + ClientInstruction::Error(_) => ClientContext::Error, + ClientInstruction::Render(_) => ClientContext::Render, + ClientInstruction::DoneClosingPane => ClientContext::DoneClosingPane, + ClientInstruction::DoneOpeningNewPane => ClientContext::DoneOpeningNewPane, + ClientInstruction::DoneUpdatingTabs => ClientContext::DoneUpdatingTabs, } } } @@ -375,10 +352,13 @@ pub enum ServerContext { NewClient, ToPty, ToScreen, - OsApi, + Render, + PluginUpdate, DoneClosingPane, - ClosePluginPane, + DoneOpeningNewPane, + DoneUpdatingTabs, ClientExit, + ClientShouldExit, Exit, } @@ -389,13 +369,16 @@ impl From<&ServerInstruction> for ServerContext { ServerInstruction::SplitHorizontally => ServerContext::SplitHorizontally, ServerInstruction::SplitVertically => ServerContext::SplitVertically, ServerInstruction::MoveFocus => ServerContext::MoveFocus, - ServerInstruction::NewClient(_) => ServerContext::NewClient, + ServerInstruction::NewClient(..) => ServerContext::NewClient, ServerInstruction::ToPty(_) => ServerContext::ToPty, ServerInstruction::ToScreen(_) => ServerContext::ToScreen, - ServerInstruction::OsApi(_) => ServerContext::OsApi, + ServerInstruction::PluginUpdate(..) => ServerContext::PluginUpdate, + ServerInstruction::Render(_) => ServerContext::Render, ServerInstruction::DoneClosingPane => ServerContext::DoneClosingPane, - ServerInstruction::ClosePluginPane(_) => ServerContext::ClosePluginPane, + ServerInstruction::DoneOpeningNewPane => ServerContext::DoneOpeningNewPane, + ServerInstruction::DoneUpdatingTabs => ServerContext::DoneUpdatingTabs, ServerInstruction::ClientExit => ServerContext::ClientExit, + ServerInstruction::ClientShouldExit => ServerContext::ClientShouldExit, ServerInstruction::Exit => ServerContext::Exit, } } diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index d4ae3080..4015938a 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -2,14 +2,12 @@ use super::actions::Action; use super::keybinds::get_default_keybinds; -use crate::client::AppInstruction; +use crate::client::ClientInstruction; use crate::common::{SenderWithContext, OPENCALLS}; use crate::errors::ContextType; use crate::os_input_output::ClientOsApi; use crate::pty_bus::PtyInstruction; -use crate::screen::ScreenInstruction; use crate::server::ServerInstruction; -use crate::wasm_vm::PluginInstruction; use crate::CommandIsExecuting; use termion::input::{TermRead, TermReadEventsAndRaw}; @@ -22,10 +20,7 @@ struct InputHandler { mode: InputMode, os_input: Box<dyn ClientOsApi>, command_is_executing: CommandIsExecuting, - send_screen_instructions: SenderWithContext<ScreenInstruction>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, - should_exit: bool, + send_client_instructions: SenderWithContext<ClientInstruction>, } impl InputHandler { @@ -33,20 +28,14 @@ impl InputHandler { fn new( os_input: Box<dyn ClientOsApi>, command_is_executing: CommandIsExecuting, - config: Config, - send_screen_instructions: SenderWithContext<ScreenInstruction>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_client_instructions: SenderWithContext<ClientInstruction>, ) -> Self { InputHandler { mode: InputMode::Normal, os_input, config, command_is_executing, - send_screen_instructions, - send_plugin_instructions, - send_app_instructions, - should_exit: false, + send_client_instructions, } } @@ -55,8 +44,7 @@ impl InputHandler { fn handle_input(&mut self) { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); - self.send_app_instructions.update(err_ctx); - self.send_screen_instructions.update(err_ctx); + self.send_client_instructions.update(err_ctx); self.os_input.update_senders(err_ctx); if let Ok(keybinds) = get_default_keybinds() { 'input_loop: loop { @@ -122,12 +110,10 @@ impl InputHandler { match action { Action::Write(val) => { - self.send_screen_instructions - .send(ScreenInstruction::ClearScroll) - .unwrap(); - self.send_screen_instructions - .send(ScreenInstruction::WriteCharacter(val)) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::clear_scroll()); + self.os_input + .send_to_server(ServerInstruction::write_character(val)); } Action::Quit => { self.exit(); @@ -135,64 +121,42 @@ impl InputHandler { } Action::SwitchToMode(mode) => { self.mode = mode; - self.send_plugin_instructions - .send(PluginInstruction::Update( + self.os_input + .send_to_server(ServerInstruction::PluginUpdate( None, - Event::ModeUpdate(get_mode_info(mode, self.os_input.load_palette())), - )) - .unwrap(); - self.send_screen_instructions - .send(ScreenInstruction::ChangeMode(get_mode_info( - mode, - self.os_input.load_palette(), - ))) - .unwrap(); - self.send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + Event::ModeUpdate(get_mode_info(mode)), + )); + self.os_input + .send_to_server(ServerInstruction::change_input_mode(mode)); + self.os_input.send_to_server(ServerInstruction::render()); } Action::Resize(direction) => { let screen_instr = match direction { - super::actions::Direction::Left => ScreenInstruction::ResizeLeft, - super::actions::Direction::Right => ScreenInstruction::ResizeRight, - super::actions::Direction::Up => ScreenInstruction::ResizeUp, - super::actions::Direction::Down => ScreenInstruction::ResizeDown, + super::actions::Direction::Left => ServerInstruction::resize_left(), + super::actions::Direction::Right => ServerInstruction::resize_right(), + super::actions::Direction::Up => ServerInstruction::resize_up(), + super::actions::Direction::Down => ServerInstruction::resize_down(), }; - self.send_screen_instructions.send(screen_instr).unwrap(); - } - Action::SwitchFocus => { - self.send_screen_instructions - .send(ScreenInstruction::SwitchFocus) - .unwrap(); - } - Action::FocusNextPane => { - self.send_screen_instructions - .send(ScreenInstruction::FocusNextPane) - .unwrap(); + self.os_input.send_to_server(screen_instr); } - Action::FocusPreviousPane => { - self.send_screen_instructions - .send(ScreenInstruction::FocusPreviousPane) - .unwrap(); + Action::SwitchFocus(_) => { + self.os_input.send_to_server(ServerInstruction::MoveFocus); } Action::MoveFocus(direction) => { let screen_instr = match direction { - super::actions::Direction::Left => ScreenInstruction::MoveFocusLeft, - super::actions::Direction::Right => ScreenInstruction::MoveFocusRight, - super::actions::Direction::Up => ScreenInstruction::MoveFocusUp, - super::actions::Direction::Down => ScreenInstruction::MoveFocusDown, + super::actions::Direction::Left => ServerInstruction::move_focus_left(), + super::actions::Direction::Right => ServerInstruction::move_focus_right(), + super::actions::Direction::Up => ServerInstruction::move_focus_up(), + super::actions::Direction::Down => ServerInstruction::move_focus_down(), }; - self.send_screen_instructions.send(screen_instr).unwrap(); + self.os_input.send_to_server(screen_instr); } Action::ScrollUp => { - self.send_screen_instructions - .send(ScreenInstruction::ScrollUp) - .unwrap(); + self.os_input.send_to_server(ServerInstruction::scroll_up()); } Action::ScrollDown => { - self.send_screen_instructions - .send(ScreenInstruction::ScrollDown) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::scroll_down()); } Action::PageScrollUp => { self.send_screen_instructions @@ -205,9 +169,8 @@ impl InputHandler { .unwrap(); } Action::ToggleFocusFullscreen => { - self.send_screen_instructions - .send(ScreenInstruction::ToggleActiveTerminalFullscreen) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::toggle_active_terminal_fullscreen()); } Action::NewPane(direction) => { let pty_instr = match direction { @@ -233,9 +196,8 @@ impl InputHandler { } Action::CloseFocus => { self.command_is_executing.closing_pane(); - self.send_screen_instructions - .send(ScreenInstruction::CloseFocusedPane) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::close_focused_pane()); self.command_is_executing.wait_until_pane_is_closed(); } Action::NewTab => { @@ -246,16 +208,14 @@ impl InputHandler { } Action::GoToNextTab => { self.command_is_executing.updating_tabs(); - self.send_screen_instructions - .send(ScreenInstruction::SwitchTabNext) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::switch_tab_next()); self.command_is_executing.wait_until_tabs_are_updated(); } Action::GoToPreviousTab => { self.command_is_executing.updating_tabs(); - self.send_screen_instructions - .send(ScreenInstruction::SwitchTabPrev) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::switch_tab_prev()); self.command_is_executing.wait_until_tabs_are_updated(); } Action::ToggleActiveSyncPanes => { @@ -265,22 +225,19 @@ impl InputHandler { } Action::CloseTab => { self.command_is_executing.updating_tabs(); - self.send_screen_instructions - .send(ScreenInstruction::CloseTab) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::screen_close_tab()); self.command_is_executing.wait_until_tabs_are_updated(); } Action::GoToTab(i) => { self.command_is_executing.updating_tabs(); - self.send_screen_instructions - .send(ScreenInstruction::GoToTab(i)) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::go_to_tab(i)); self.command_is_executing.wait_until_tabs_are_updated(); } Action::TabNameInput(c) => { - self.send_screen_instructions - .send(ScreenInstruction::UpdateTabName(c)) - .unwrap(); + self.os_input + .send_to_server(ServerInstruction::update_tab_name(c)); } Action::NoOp => {} } @@ -291,8 +248,8 @@ impl InputHandler { /// Routine to be called when the input handler exits (at the moment this is the /// same as quitting Zellij). fn exit(&mut self) { - self.send_app_instructions - .send(AppInstruction::Exit) + self.send_client_instructions + .send(ClientInstruction::Exit) .unwrap(); } } @@ -343,19 +300,10 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { pub fn input_loop( os_input: Box<dyn ClientOsApi>, command_is_executing: CommandIsExecuting, - send_screen_instructions: SenderWithContext<ScreenInstruction>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_client_instructions: SenderWithContext<ClientInstruction>, ) { - let _handler = InputHandler::new( - os_input, - command_is_executing, - config, - send_screen_instructions, - send_plugin_instructions, - send_app_instructions, - ) - .handle_input(); + let _handler = + InputHandler::new(os_input, command_is_executing, send_client_instructions).handle_input(); } pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> { diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs index 7cc43e27..53300424 100644 --- a/src/common/os_input_output.rs +++ b/src/common/os_input_output.rs @@ -6,7 +6,7 @@ use nix::sys::termios; use nix::sys::wait::waitpid; use nix::unistd; use nix::unistd::{ForkResult, Pid}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use std::env; use std::io; use std::io::prelude::*; @@ -22,7 +22,7 @@ use crate::panes::PositionAndSize; use crate::server::ServerInstruction; use crate::utils::consts::ZELLIJ_IPC_PIPE; -const IPC_BUFFER_SIZE: u32 = 8192; +const IPC_BUFFER_SIZE: u32 = 8388608; fn into_raw_mode(pid: RawFd) { let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute"); @@ -300,15 +300,6 @@ pub fn get_server_os_input() -> ServerOsInputOutput { } } -/// OS Instructions sent to the Server by clients -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ServerOsApiInstruction { - SetTerminalSizeUsingFd(RawFd, u16, u16), - WriteToTtyStdin(RawFd, Vec<u8>), - TcDrain(RawFd), - Exit, -} - #[derive(Clone)] pub struct ClientOsInputOutput { orig_termios: Arc<Mutex<termios::Termios>>, @@ -342,7 +333,7 @@ pub trait ClientOsApi: Send + Sync { // This should be called from the client-side router thread only. fn client_recv(&self) -> (ClientInstruction, ErrorContext); /// Setup the client IpcChannel and notify server of new client - fn connect_to_server(&mut self); + fn connect_to_server(&mut self, full_screen_ws: PositionAndSize); } impl ClientOsApi for ClientOsInputOutput { @@ -378,13 +369,16 @@ impl ClientOsApi for ClientOsInputOutput { fn update_senders(&mut self, new_ctx: ErrorContext) { self.server_sender.update(new_ctx); } - fn connect_to_server(&mut self) { + fn connect_to_server(&mut self, full_screen_ws: PositionAndSize) { let (client_buffer_path, client_buffer) = SharedRingBuffer::create_temp(IPC_BUFFER_SIZE).unwrap(); self.client_receiver = Some(Arc::new(Mutex::new(IpcReceiver::new( client_buffer.clone(), )))); - self.send_to_server(ServerInstruction::NewClient(client_buffer_path)); + self.send_to_server(ServerInstruction::NewClient( + client_buffer_path, + full_screen_ws, + )); } fn client_recv(&self) -> (ClientInstruction, ErrorContext) { self.client_receiver diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs index bc49e9bd..b9bb422b 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty_bus.rs @@ -18,7 +18,7 @@ use crate::{ errors::{ContextType, ErrorContext}, panes::PaneId, screen::ScreenInstruction, - server::ServerInstruction, + wasm_vm::PluginInstruction, }; pub struct ReadFromPid { @@ -82,35 +82,35 @@ pub enum VteEvent { struct VteEventSender { id: RawFd, - send_server_instructions: SenderWithContext<ServerInstruction>, + send_screen_instructions: SenderWithContext<ScreenInstruction>, } impl VteEventSender { - pub fn new(id: RawFd, send_server_instructions: SenderWithContext<ServerInstruction>) -> Self { + pub fn new(id: RawFd, send_screen_instructions: SenderWithContext<ScreenInstruction>) -> Self { VteEventSender { id, - send_server_instructions, + send_screen_instructions, } } } impl vte::Perform for VteEventSender { fn print(&mut self, c: char) { - self.send_server_instructions - .send(ServerInstruction::pty(self.id, VteEvent::Print(c))) + self.send_screen_instructions + .send(ScreenInstruction::Pty(self.id, VteEvent::Print(c))) .unwrap(); } fn execute(&mut self, byte: u8) { - self.send_server_instructions - .send(ServerInstruction::pty(self.id, VteEvent::Execute(byte))) + self.send_screen_instructions + .send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte))) .unwrap(); } fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { let params = params.iter().copied().collect(); let intermediates = intermediates.iter().copied().collect(); - self.send_server_instructions - .send(ServerInstruction::pty( + self.send_screen_instructions + .send(ScreenInstruction::Pty( self.id, VteEvent::Hook(params, intermediates, ignore, c), )) @@ -118,21 +118,21 @@ impl vte::Perform for VteEventSender { } fn put(&mut self, byte: u8) { - self.send_server_instructions - .send(ServerInstruction::pty(self.id, VteEvent::Put(byte))) + self.send_screen_instructions + .send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte))) .unwrap(); } fn unhook(&mut self) { - self.send_server_instructions - .send(ServerInstruction::pty(self.id, VteEvent::Unhook)) + self.send_screen_instructions + .send(ScreenInstruction::Pty(self.id, VteEvent::Unhook)) .unwrap(); } fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { let params = params.iter().map(|p| p.to_vec()).collect(); - self.send_server_instructions - .send(ServerInstruction::pty( + self.send_screen_instructions + .send(ScreenInstruction::Pty( self.id, VteEvent::OscDispatch(params, bell_terminated), )) @@ -142,8 +142,8 @@ impl vte::Perform for VteEventSender { fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { let params = params.iter().copied().collect(); let intermediates = intermediates.iter().copied().collect(); - self.send_server_instructions - .send(ServerInstruction::pty( + self.send_screen_instructions + .send(ScreenInstruction::Pty( self.id, VteEvent::CsiDispatch(params, intermediates, ignore, c), )) @@ -152,8 +152,8 @@ impl vte::Perform for VteEventSender { fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { let intermediates = intermediates.iter().copied().collect(); - self.send_server_instructions - .send(ServerInstruction::pty( + self.send_screen_instructions + .send(ScreenInstruction::Pty( self.id, VteEvent::EscDispatch(intermediates, ignore, byte), )) @@ -176,7 +176,8 @@ pub enum PtyInstruction { pub struct PtyBus { pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub id_to_child_pid: HashMap<RawFd, RawFd>, - pub send_server_instructions: SenderWithContext<ServerInstruction>, + pub send_screen_instructions: SenderWithContext<ScreenInstruction>, + send_plugin_instructions: SenderWithContext<PluginInstruction>, os_input: Box<dyn ServerOsApi>, debug_to_file: bool, task_handles: HashMap<RawFd, JoinHandle<()>>, @@ -185,16 +186,16 @@ pub struct PtyBus { fn stream_terminal_bytes( pid: RawFd, os_input: Box<dyn ServerOsApi>, - mut send_server_instructions: SenderWithContext<ServerInstruction>, + mut send_screen_instructions: SenderWithContext<ScreenInstruction>, debug: bool, ) -> JoinHandle<()> { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); task::spawn({ async move { err_ctx.add_call(ContextType::AsyncTask); - send_server_instructions.update(err_ctx); + send_screen_instructions.update(err_ctx); let mut vte_parser = vte::Parser::new(); - let mut vte_event_sender = VteEventSender::new(pid, send_server_instructions.clone()); + let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone()); let mut terminal_bytes = ReadFromPid::new(&pid, os_input.clone()); let mut last_byte_receive_time: Option<Instant> = None; @@ -220,8 +221,8 @@ fn stream_terminal_bytes( Some(receive_time) => { if receive_time.elapsed() > max_render_pause { pending_render = false; - send_server_instructions - .send(ServerInstruction::render()) + send_screen_instructions + .send(ScreenInstruction::Render) .unwrap(); last_byte_receive_time = Some(Instant::now()); } else { @@ -236,23 +237,23 @@ fn stream_terminal_bytes( } else { if pending_render { pending_render = false; - send_server_instructions - .send(ServerInstruction::render()) + send_screen_instructions + .send(ScreenInstruction::Render) .unwrap(); } last_byte_receive_time = None; task::sleep(::std::time::Duration::from_millis(10)).await; } } - send_server_instructions - .send(ServerInstruction::render()) + send_screen_instructions + .send(ScreenInstruction::Render) .unwrap(); #[cfg(not(test))] // this is a little hacky, and is because the tests end the file as soon as // we read everything, rather than hanging until there is new data // a better solution would be to fix the test fakes, but this will do for now - send_server_instructions - .send(ServerInstruction::screen_close_pane(PaneId::Terminal(pid))) + send_screen_instructions + .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) .unwrap(); } }) @@ -262,14 +263,16 @@ impl PtyBus { pub fn new( receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, os_input: Box<dyn ServerOsApi>, - send_server_instructions: SenderWithContext<ServerInstruction>, + send_screen_instructions: SenderWithContext<ScreenInstruction>, + send_plugin_instructions: SenderWithContext<PluginInstruction>, debug_to_file: bool, ) -> Self { PtyBus { receive_pty_instructions, os_input, id_to_child_pid: HashMap::new(), - send_server_instructions, + send_screen_instructions, + send_plugin_instructions, debug_to_file, task_handles: HashMap::new(), } @@ -280,7 +283,7 @@ impl PtyBus { let task_handle = stream_terminal_bytes( pid_primary, self.os_input.clone(), - self.send_server_instructions.clone(), + self.send_screen_instructions.clone(), self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); @@ -296,17 +299,17 @@ impl PtyBus { self.id_to_child_pid.insert(pid_primary, pid_secondary); new_pane_pids.push(pid_primary); } - self.send_server_instructions - .send(ServerInstruction::apply_layout(( + self.send_screen_instructions + .send(ScreenInstruction::ApplyLayout( layout_path, new_pane_pids.clone(), - ))) + )) .unwrap(); for id in new_pane_pids { let task_handle = stream_terminal_bytes( id, self.os_input.clone(), - self.send_server_instructions.clone(), + self.send_screen_instructions.clone(), self.debug_to_file, ); self.task_handles.insert(id, task_handle); @@ -323,8 +326,8 @@ impl PtyBus { }); } PaneId::Plugin(pid) => self - .send_server_instructions - .send(ServerInstruction::ClosePluginPane(pid)) + .send_plugin_instructions + .send(PluginInstruction::Unload(pid)) .unwrap(), } } diff --git a/src/common/screen.rs b/src/common/screen.rs index 549eccd1..8868febb 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -7,9 +7,8 @@ use std::path::PathBuf; use std::str; use std::sync::mpsc::Receiver; -use crate::client::AppInstruction; use crate::common::SenderWithContext; -use crate::os_input_output::ClientOsApi; +use crate::os_input_output::ServerOsApi; use crate::panes::PositionAndSize; use crate::pty_bus::{PtyInstruction, VteEvent}; use crate::server::ServerInstruction; @@ -51,7 +50,7 @@ pub enum ScreenInstruction { SetMaxHeight(PaneId, usize), SetInvisibleBorders(PaneId, bool), ClosePane(PaneId), - ApplyLayout((PathBuf, Vec<RawFd>)), + ApplyLayout(PathBuf, Vec<RawFd>), NewTab(RawFd), SwitchTabNext, SwitchTabPrev, @@ -74,14 +73,16 @@ pub struct Screen { tabs: BTreeMap<usize, Tab>, /// A [`PluginInstruction`] and [`ErrorContext`] sender. pub send_plugin_instructions: SenderWithContext<PluginInstruction>, - /// An [`AppInstruction`] and [`ErrorContext`] sender. - pub send_app_instructions: SenderWithContext<AppInstruction>, + /// An [`PtyInstruction`] and [`ErrorContext`] sender. + pub send_pty_instructions: SenderWithContext<PtyInstruction>, + /// An [`ServerInstruction`] and [`ErrorContext`] sender. + pub send_server_instructions: SenderWithContext<ServerInstruction>, /// The full size of this [`Screen`]. full_screen_ws: PositionAndSize, /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option<usize>, /// The [`ClientOsApi`] this [`Screen`] uses. - pub os_api: Box<dyn ClientOsApi>, + os_api: Box<dyn ServerOsApi>, input_mode: InputMode, } @@ -92,9 +93,10 @@ impl Screen { pub fn new( receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, full_screen_ws: &PositionAndSize, - os_api: Box<dyn ClientOsApi>, + os_api: Box<dyn ServerOsApi>, max_panes: Option<usize>, mode_info: ModeInfo, input_mode: InputMode, @@ -104,7 +106,8 @@ impl Screen { receiver: receive_screen_instructions, max_panes, send_plugin_instructions, - send_app_instructions, + send_pty_instructions, + send_server_instructions, full_screen_ws: *full_screen_ws, active_tab_index: None, tabs: BTreeMap::new(), @@ -127,7 +130,8 @@ impl Screen { &self.full_screen_ws, self.os_api.clone(), self.send_plugin_instructions.clone(), - self.send_app_instructions.clone(), + self.send_pty_instructions.clone(), + self.send_server_instructions.clone(), self.max_panes, Some(PaneId::Terminal(pane_id)), self.mode_info.clone(), @@ -211,12 +215,13 @@ impl Screen { // below we don't check the result of sending the CloseTab instruction to the pty thread // because this might be happening when the app is closing, at which point the pty thread // has already closed and this would result in an error - self.os_api - .send_to_server(ServerInstruction::pty_close_tab(pane_ids)); + self.send_pty_instructions + .send(PtyInstruction::CloseTab(pane_ids)) + .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.send_app_instructions - .send(AppInstruction::Exit) + self.send_server_instructions + .send(ServerInstruction::ClientShouldExit) .unwrap(); } else { for t in self.tabs.values_mut() { @@ -282,7 +287,8 @@ impl Screen { &self.full_screen_ws, self.os_api.clone(), self.send_plugin_instructions.clone(), - self.send_app_instructions.clone(), + self.send_pty_instructions.clone(), + self.send_server_instructions.clone(), self.max_panes, None, self.mode_info.clone(), diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index a7cd7543..5c234403 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -12,7 +12,6 @@ use wasmer_wasi::WasiEnv; use zellij_tile::data::{Event, EventType, PluginIds}; use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext}; -use crate::client::AppInstruction; #[derive(Clone, Debug)] pub enum PluginInstruction { @@ -28,7 +27,7 @@ pub struct PluginEnv { pub plugin_id: u32, // FIXME: This should be a big bundle of all of the channels pub send_screen_instructions: SenderWithContext<ScreenInstruction>, - pub send_app_instructions: SenderWithContext<AppInstruction>, // FIXME: This should be a big bundle of all of the channels + pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, pub subscriptions: Arc<Mutex<HashSet<EventType>>>, } @@ -74,10 +73,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) { fn host_open_file(plugin_env: &PluginEnv) { let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap()); plugin_env - .send_app_instructions - .send(AppInstruction::ToPty(PtyInstruction::SpawnTerminal(Some( - path, - )))) + .send_pty_instructions + .send(PtyInstruction::SpawnTerminal(Some(path))) .unwrap(); } diff --git a/src/main.rs b/src/main.rs index 77dd6f9a..d89ff577 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,7 +89,7 @@ pub fn start( opts: CliArgs, server_os_input: Box<dyn ServerOsApi>, ) { - let ipc_thread = start_server(server_os_input, opts.clone()); - start_client(client_os_input, opts); + let ipc_thread = start_server(server_os_input, opts); + start_client(client_os_input); drop(ipc_thread.join()); } 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), + } +} diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 7c4175bb..f5b03fde 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -190,8 +190,11 @@ impl ClientOsApi for FakeInputOutput { self.server_sender.update(new_ctx); self.client_sender.update(new_ctx); } - fn connect_to_server(&mut self) { - ClientOsApi::send_to_server(self, ServerInstruction::NewClient("zellij".into())); + fn connect_to_server(&mut self, full_screen_ws: PositionAndSize) { + ClientOsApi::send_to_server( + self, + ServerInstruction::NewClient("zellij".into(), full_screen_ws), + ); } fn client_recv(&self) -> (ClientInstruction, ErrorContext) { self.client_receiver.lock().unwrap().recv().unwrap() |
