From 588cdaa00894a9ae46e6a293f73ba62d5b1df18f Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Thu, 11 Feb 2021 21:15:38 +0530 Subject: [PATCH] isolate pty thread --- src/cli.rs | 2 +- src/client/panes/terminal_pane.rs | 4 +- src/client/tab.rs | 99 ++++++------ src/common/errors.rs | 21 ++- src/common/input/handler.rs | 60 +++---- src/common/mod.rs | 253 ++++++++---------------------- src/common/pty_bus.rs | 217 +++++++++++++++++++------ src/common/screen.rs | 13 +- src/common/wasm_vm.rs | 14 +- src/main.rs | 22 +-- src/server/mod.rs | 201 +++++++++++++++++++++++- 11 files changed, 557 insertions(+), 349 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index bc4e4a9e..d4c1c704 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV use std::path::PathBuf; use structopt::StructOpt; -#[derive(StructOpt, Default, Debug)] +#[derive(StructOpt, Debug, Default, Clone)] #[structopt(name = "zellij")] pub struct CliArgs { /// Send "split (direction h == horizontal / v == vertical)" to active zellij session diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index a5a957e6..1834e722 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -1,6 +1,8 @@ use crate::tab::Pane; use ::nix::pty::Winsize; use ::std::os::unix::io::RawFd; +use ::vte::Perform; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::time::Instant; @@ -10,7 +12,7 @@ use crate::panes::terminal_character::{ }; use crate::pty_bus::VteBytes; -#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] pub enum PaneId { Terminal(RawFd), Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? diff --git a/src/client/tab.rs b/src/client/tab.rs index b01e04e5..b87926b7 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -69,7 +69,6 @@ pub struct Tab { fullscreen_is_active: bool, synchronize_is_active: bool, os_api: Box, - pub send_pty_instructions: SenderWithContext, pub send_plugin_instructions: SenderWithContext, pub send_app_instructions: SenderWithContext, should_clear_display_before_rendering: bool, @@ -226,7 +225,6 @@ impl Tab { name: String, full_screen_ws: &PositionAndSize, mut os_api: Box, - send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, max_panes: Option, @@ -261,7 +259,6 @@ impl Tab { synchronize_is_active: false, os_api, send_app_instructions, - send_pty_instructions, send_plugin_instructions, should_clear_display_before_rendering: false, mode_info, @@ -354,8 +351,10 @@ 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.send_pty_instructions - .send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) + self.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane( + PaneId::Terminal(*unused_pid), + ))) .unwrap(); } self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); @@ -399,8 +398,8 @@ impl Tab { }, ); if terminal_id_to_split.is_none() { - self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty + self.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty .unwrap(); return; // likely no terminal large enough to split } @@ -475,24 +474,25 @@ impl Tab { self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } - } else if let PaneId::Terminal(term_pid) = pid { - // TODO: check minimum size of active terminal - 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.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); - return; - } - let terminal_ws = PositionAndSize { - x: active_pane.x(), - y: active_pane.y(), - rows: active_pane.rows(), - columns: active_pane.columns(), - ..Default::default() - }; - let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); + } else { + // FIXME: This could use a second look + if let PaneId::Terminal(term_pid) = pid { + // TODO: check minimum size of active terminal + 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.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty + .unwrap(); + return; + } + let terminal_ws = PositionAndSize { + x: active_pane.x(), + y: active_pane.y(), + rows: active_pane.rows(), + columns: active_pane.columns(), + }; + let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); active_pane.change_pos_and_size(&top_winsize); @@ -532,26 +532,25 @@ impl Tab { self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); } - } else if let PaneId::Terminal(term_pid) = pid { - // TODO: check minimum size of active terminal - 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.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); - return; - } - let terminal_ws = PositionAndSize { - x: active_pane.x(), - y: active_pane.y(), - rows: active_pane.rows(), - columns: active_pane.columns(), - ..Default::default() - }; - let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); - - active_pane.change_pos_and_size(&left_winsize); + } else { + // FIXME: This could use a second look + if let PaneId::Terminal(term_pid) = pid { + // TODO: check minimum size of active terminal + 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.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty + .unwrap(); + return; + } + let terminal_ws = PositionAndSize { + x: active_pane.x(), + y: active_pane.y(), + rows: active_pane.rows(), + columns: active_pane.columns(), + }; + let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); let new_terminal = TerminalPane::new(term_pid, right_winsize); self.os_api.set_terminal_size_using_fd( @@ -2105,8 +2104,8 @@ 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.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) + self.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) .unwrap(); self.close_pane_without_rerender(pid); } @@ -2217,8 +2216,10 @@ 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.send_pty_instructions - .send(PtyInstruction::ClosePane(active_pane_id)) + self.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::ClosePane( + active_pane_id, + ))) .unwrap(); } } diff --git a/src/common/errors.rs b/src/common/errors.rs index c1c6753f..a9156ae6 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -4,6 +4,7 @@ use super::{AppInstruction, ASYNCOPENCALLS, OPENCALLS}; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; +use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; @@ -79,7 +80,7 @@ pub fn get_current_ctx() -> ErrorContext { } /// A representation of the call stack. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Serialize, Deserialize)] pub struct ErrorContext { calls: [ContextType; MAX_THREAD_CALL_STACK], } @@ -131,7 +132,7 @@ 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). -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum ContextType { /// A screen-related call. Screen(ScreenContext), @@ -141,7 +142,7 @@ pub enum ContextType { Plugin(PluginContext), /// An app-related call. App(AppContext), - IpcServer, + IPCServer, // Fix: Create a separate ServerContext when sessions are introduced StdinHandler, AsyncTask, /// An empty, placeholder call. This should be thought of as representing no call at all. @@ -174,7 +175,7 @@ impl Display for ContextType { // FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! /// Stack call representations corresponding to the different types of [`ScreenInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ScreenContext { HandlePtyBytes, Render, @@ -267,7 +268,7 @@ impl From<&ScreenInstruction> for ScreenContext { } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum PtyContext { SpawnTerminal, SpawnTerminalVertically, @@ -297,7 +298,7 @@ impl From<&PtyInstruction> for PtyContext { use crate::wasm_vm::PluginInstruction; /// Stack call representations corresponding to the different types of [`PluginInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum PluginContext { Load, Update, @@ -319,10 +320,13 @@ impl From<&PluginInstruction> for PluginContext { } /// Stack call representations corresponding to the different types of [`AppInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum AppContext { Exit, Error, + ToPty, + ToPlugin, + ToScreen, } impl From<&AppInstruction> for AppContext { @@ -330,6 +334,9 @@ impl From<&AppInstruction> for AppContext { match *app_instruction { AppInstruction::Exit => AppContext::Exit, AppInstruction::Error(_) => AppContext::Error, + AppInstruction::ToPty(_) => AppContext::ToPty, + AppInstruction::ToPlugin(_) => AppContext::ToPlugin, + AppInstruction::ToScreen(_) => AppContext::ToScreen, } } } diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 7304225c..f8f07f53 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -23,7 +23,6 @@ struct InputHandler { config: Config, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, should_exit: bool, @@ -36,7 +35,6 @@ impl InputHandler { command_is_executing: CommandIsExecuting, config: Config, send_screen_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) -> Self { @@ -46,7 +44,6 @@ impl InputHandler { config, command_is_executing, send_screen_instructions, - send_pty_instructions, send_plugin_instructions, send_app_instructions, should_exit: false, @@ -58,25 +55,34 @@ impl InputHandler { fn handle_input(&mut self) { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); - let alt_left_bracket = vec![27, 91]; - loop { - if self.should_exit { - break; - } - let stdin_buffer = self.os_input.read_from_stdin(); - for key_result in stdin_buffer.events_and_raw() { - match key_result { - Ok((event, raw_bytes)) => match event { - termion::event::Event::Key(key) => { - let key = cast_termion_key(key); - self.handle_key(&key, raw_bytes); - } - termion::event::Event::Unsupported(unsupported_key) => { - // we have to do this because of a bug in termion - // this should be a key event and not an unsupported event - if unsupported_key == alt_left_bracket { - let key = Key::Alt('['); - self.handle_key(&key, raw_bytes); + self.send_app_instructions.update(err_ctx); + self.send_screen_instructions.update(err_ctx); + if let Ok(keybinds) = get_default_keybinds() { + 'input_loop: loop { + //@@@ I think this should actually just iterate over stdin directly + let stdin_buffer = self.os_input.read_from_stdin(); + for key_result in stdin_buffer.events_and_raw() { + match key_result { + Ok((event, raw_bytes)) => match event { + termion::event::Event::Key(key) => { + let key = cast_termion_key(key); + // FIXME this explicit break is needed because the current test + // framework relies on it to not create dead threads that loop + // and eat up CPUs. Do not remove until the test framework has + // been revised. Sorry about this (@categorille) + let mut should_break = false; + for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds) + { + should_break |= self.dispatch_action(action); + } + if should_break { + break 'input_loop; + } + } + termion::event::Event::Mouse(_) + | termion::event::Event::Unsupported(_) => { + // Mouse and unsupported events aren't implemented yet, + // use a NoOp untill then. } } termion::event::Event::Mouse(_) => { @@ -220,7 +226,9 @@ impl InputHandler { None => PtyInstruction::SpawnTerminal(None), }; self.command_is_executing.opening_new_pane(); - self.send_pty_instructions.send(pty_instr).unwrap(); + self.send_app_instructions + .send(AppInstruction::ToPty(pty_instr)) + .unwrap(); self.command_is_executing.wait_until_new_pane_is_opened(); } Action::CloseFocus => { @@ -232,8 +240,8 @@ impl InputHandler { } Action::NewTab => { self.command_is_executing.opening_new_pane(); - self.send_pty_instructions - .send(PtyInstruction::NewTab) + self.send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::NewTab)) .unwrap(); self.command_is_executing.wait_until_new_pane_is_opened(); } @@ -332,7 +340,6 @@ pub fn input_loop( config: Config, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) { @@ -341,7 +348,6 @@ pub fn input_loop( command_is_executing, config, send_screen_instructions, - send_pty_instructions, send_plugin_instructions, send_app_instructions, ) diff --git a/src/common/mod.rs b/src/common/mod.rs index 17c1f4cb..61897df0 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -9,8 +9,9 @@ pub mod setup; pub mod utils; pub mod wasm_vm; -use std::cell::RefCell; -use std::path::PathBuf; +use std::io::Write; +use std::os::unix::net::UnixStream; +use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::thread; use std::{collections::HashMap, fs}; @@ -23,19 +24,12 @@ use std::{ }; use crate::cli::CliArgs; -use crate::common::input::config::Config; -use crate::layout::Layout; -use crate::panes::PaneId; -use async_std::task_local; +use crate::server::start_server; use command_is_executing::CommandIsExecuting; -use directories_next::ProjectDirs; -use errors::{ - get_current_ctx, AppContext, ContextType, ErrorContext, PluginContext, PtyContext, - ScreenContext, -}; +use errors::{AppContext, ContextType, ErrorContext, PluginContext, ScreenContext}; use input::handler::input_loop; use os_input_output::OsApi; -use pty_bus::{PtyBus, PtyInstruction}; +use pty_bus::PtyInstruction; use screen::{Screen, ScreenInstruction}; use serde::{Deserialize, Serialize}; use setup::install; @@ -45,12 +39,36 @@ use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; use wasmer_wasi::{Pipe, WasiState}; use zellij_tile::data::{EventType, InputMode, ModeInfo}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum ApiCommand { OpenFile(PathBuf), SplitHorizontally, SplitVertically, MoveFocus, + ToPty(PtyInstruction), + ToScreen(ScreenInstruction), + ClosePluginPane(u32), + Quit, +} + +// FIXME: It would be good to add some more things to this over time +#[derive(Debug, Clone, Default)] +pub struct AppState { + pub input_mode: InputMode, +} + +// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted +// from self instead of being explicitly passed here +pub fn update_state( + app_tx: &SenderWithContext, + update_fn: impl FnOnce(AppState) -> AppState, +) { + let (state_tx, state_rx) = mpsc::channel(); + + drop(app_tx.send(AppInstruction::GetState(state_tx))); + let state = state_rx.recv().unwrap(); + + drop(app_tx.send(AppInstruction::SetState(update_fn(state)))) } /// An [MPSC](mpsc) asynchronous channel with added error context. @@ -66,7 +84,7 @@ pub type SyncChannelWithContext = ( /// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`]. #[derive(Clone)] -enum SenderType { +pub enum SenderType { /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`]. Sender(mpsc::Sender<(T, ErrorContext)>), /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`]. @@ -81,8 +99,8 @@ pub struct SenderWithContext { } impl SenderWithContext { - fn new(sender: SenderType) -> Self { - Self { sender } + pub fn new(err_ctx: ErrorContext, sender: SenderType) -> Self { + Self { err_ctx, sender } } /// Sends an event, along with the current [`ErrorContext`], on this @@ -116,6 +134,9 @@ task_local! { pub enum AppInstruction { Exit, Error(String), + ToPty(PtyInstruction), + ToScreen(ScreenInstruction), + ToPlugin(PluginInstruction), } /// Start Zellij with the specified [`OsApi`] and command-line arguments. @@ -140,10 +161,6 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { let send_screen_instructions = SenderWithContext::new(SenderType::Sender(send_screen_instructions)); - let (send_pty_instructions, receive_pty_instructions): ChannelWithContext = - mpsc::channel(); - let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions)); - let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext< PluginInstruction, > = mpsc::channel(); @@ -155,31 +172,13 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { let send_app_instructions = SenderWithContext::new(SenderType::SyncSender(send_app_instructions)); - let mut pty_bus = PtyBus::new( - receive_pty_instructions, - send_screen_instructions.clone(), - send_plugin_instructions.clone(), + let ipc_thread = start_server( os_input.clone(), - opts.debug, + opts.clone(), + command_is_executing.clone(), + send_app_instructions.clone(), ); - // Determine and initialize the data directory - let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let data_dir = opts - .data_dir - .unwrap_or_else(|| project_dirs.data_dir().to_path_buf()); - install::populate_data_dir(&data_dir); - - // 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 - .map(|p| Layout::new(&p, &data_dir)) - .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir))); - #[cfg(not(test))] std::panic::set_hook({ use crate::errors::handle_panic; @@ -189,72 +188,11 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { }) }); - let pty_thread = thread::Builder::new() - .name("pty".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); - 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); - command_is_executing.done_closing_pane(); - } - PtyInstruction::CloseTab(ids) => { - pty_bus.close_tab(ids); - command_is_executing.done_closing_pane(); - } - PtyInstruction::Quit => { - break; - } - } - } - }) - .unwrap(); - 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_pty_instructions = send_pty_instructions.clone(); let send_plugin_instructions = send_plugin_instructions.clone(); let send_app_instructions = send_app_instructions.clone(); let max_panes = opts.max_panes; @@ -262,7 +200,6 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { move || { let mut screen = Screen::new( receive_screen_instructions, - send_pty_instructions, send_plugin_instructions, send_app_instructions, &full_screen_ws, @@ -281,6 +218,7 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { .recv() .expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + screen.send_app_instructions.update(err_ctx); match event { ScreenInstruction::PtyBytes(pid, vte_bytes) => { let active_tab = screen.get_active_tab_mut().unwrap(); @@ -460,10 +398,8 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { let wasm_thread = thread::Builder::new() .name("wasm".to_string()) .spawn({ - let send_pty_instructions = send_pty_instructions.clone(); - let send_screen_instructions = send_screen_instructions.clone(); - let send_app_instructions = send_app_instructions.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); + 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; @@ -473,6 +409,8 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { .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 plugin_dir = data_dir.join("plugins/"); @@ -505,7 +443,6 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { let plugin_env = PluginEnv { plugin_id, - send_pty_instructions: send_pty_instructions.clone(), send_screen_instructions: send_screen_instructions.clone(), send_app_instructions: send_app_instructions.clone(), send_plugin_instructions: send_plugin_instructions.clone(), @@ -556,83 +493,10 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { }) .unwrap(); - let _signal_thread = thread::Builder::new() - .name("signal_listener".to_string()) - .spawn({ - let os_input = os_input.clone(); - let send_screen_instructions = send_screen_instructions.clone(); - move || { - os_input.receive_sigwinch(Box::new(move || { - let _ = send_screen_instructions.send(ScreenInstruction::TerminalResize); - })); - } - }) - .unwrap(); - - // TODO: currently we don't wait for this to quit - // because otherwise the app will hang. Need to fix this so it both - // listens to the ipc-bus and is able to quit cleanly - #[cfg(not(test))] - let _ipc_thread = thread::Builder::new() - .name("ipc_server".to_string()) - .spawn({ - use std::io::Read; - let send_pty_instructions = send_pty_instructions.clone(); - let send_screen_instructions = send_screen_instructions.clone(); - move || { - std::fs::remove_file(ZELLIJ_IPC_PIPE).ok(); - let listener = std::os::unix::net::UnixListener::bind(ZELLIJ_IPC_PIPE) - .expect("could not listen on ipc socket"); - let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); - err_ctx.add_call(ContextType::IpcServer); - - for stream in listener.incoming() { - match stream { - Ok(mut stream) => { - let mut buffer = [0; 65535]; // TODO: more accurate - let _ = stream - .read(&mut buffer) - .expect("failed to parse ipc message"); - let decoded: ApiCommand = bincode::deserialize(&buffer) - .expect("failed to deserialize ipc message"); - match &decoded { - ApiCommand::OpenFile(file_name) => { - let path = PathBuf::from(file_name); - send_pty_instructions - .send(PtyInstruction::SpawnTerminal(Some(path))) - .unwrap(); - } - ApiCommand::SplitHorizontally => { - send_pty_instructions - .send(PtyInstruction::SpawnTerminalHorizontally(None)) - .unwrap(); - } - ApiCommand::SplitVertically => { - send_pty_instructions - .send(PtyInstruction::SpawnTerminalVertically(None)) - .unwrap(); - } - ApiCommand::MoveFocus => { - send_screen_instructions - .send(ScreenInstruction::FocusNextPane) - .unwrap(); - } - } - } - Err(err) => { - panic!("err {:?}", err); - } - } - } - } - }) - .unwrap(); - let _stdin_thread = thread::Builder::new() .name("stdin_handler".to_string()) .spawn({ let send_screen_instructions = send_screen_instructions.clone(); - let send_pty_instructions = send_pty_instructions.clone(); let send_plugin_instructions = send_plugin_instructions.clone(); let os_input = os_input.clone(); let config = config; @@ -642,13 +506,14 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { config, command_is_executing, send_screen_instructions, - send_pty_instructions, send_plugin_instructions, send_app_instructions, ) } }); + let mut server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); + #[warn(clippy::never_loop)] loop { let (app_instruction, mut err_ctx) = receive_app_instructions @@ -656,15 +521,17 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { .expect("failed to receive app instruction on channel"); err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction))); + send_screen_instructions.update(err_ctx); match app_instruction { AppInstruction::Exit => { break; } AppInstruction::Error(backtrace) => { + let api_command = bincode::serialize(&(err_ctx, ApiCommand::Quit)).unwrap(); + server_stream.write_all(&api_command).unwrap(); + let _ = ipc_thread.join(); let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = screen_thread.join(); - let _ = send_pty_instructions.send(PtyInstruction::Quit); - let _ = pty_thread.join(); let _ = send_plugin_instructions.send(PluginInstruction::Quit); let _ = wasm_thread.join(); os_input.unset_raw_mode(0); @@ -680,11 +547,23 @@ pub fn start(mut os_input: Box, opts: CliArgs, config: Config) { .unwrap(); std::process::exit(1); } + AppInstruction::ToScreen(instruction) => { + send_screen_instructions.send(instruction).unwrap(); + } + AppInstruction::ToPlugin(instruction) => { + send_plugin_instructions.send(instruction).unwrap(); + } + AppInstruction::ToPty(instruction) => { + let api_command = + bincode::serialize(&(err_ctx, ApiCommand::ToPty(instruction))).unwrap(); + server_stream.write_all(&api_command).unwrap(); + } } } - let _ = send_pty_instructions.send(PtyInstruction::Quit); - pty_thread.join().unwrap(); + let api_command = bincode::serialize(&(err_ctx, ApiCommand::Quit)).unwrap(); + server_stream.write_all(&api_command).unwrap(); + let _ = ipc_thread.join().unwrap(); let _ = send_screen_instructions.send(ScreenInstruction::Quit); screen_thread.join().unwrap(); let _ = send_plugin_instructions.send(PluginInstruction::Quit); diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs index 2c132228..108c04fd 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty_bus.rs @@ -6,16 +6,21 @@ use ::std::os::unix::io::RawFd; use ::std::pin::*; use ::std::sync::mpsc::Receiver; use ::std::time::{Duration, Instant}; +use ::vte; +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::os::unix::net::UnixStream; use std::path::PathBuf; -use super::{ScreenInstruction, SenderWithContext}; +use super::{ScreenInstruction, OPENCALLS}; +use crate::layout::Layout; use crate::os_input_output::OsApi; -use crate::utils::logging::debug_to_file; +use crate::utils::{consts::ZELLIJ_IPC_PIPE, logging::debug_to_file}; use crate::{ - errors::{get_current_ctx, ContextType, ErrorContext}, + common::ApiCommand, + errors::{ContextType, ErrorContext}, panes::PaneId, }; -use crate::{layout::Layout, wasm_vm::PluginInstruction}; pub struct ReadFromPid { pid: RawFd, @@ -63,10 +68,114 @@ impl Stream for ReadFromPid { } } -pub type VteBytes = Vec; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum VteEvent { + // TODO: try not to allocate Vecs + Print(char), + Execute(u8), // byte + Hook(Vec, Vec, bool, char), // params, intermediates, ignore, char + Put(u8), // byte + Unhook, + OscDispatch(Vec>, bool), // params, bell_terminated + CsiDispatch(Vec, Vec, bool, char), // params, intermediates, ignore, char + EscDispatch(Vec, bool, u8), // intermediates, ignore, byte +} + +struct VteEventSender { + id: RawFd, + err_ctx: ErrorContext, + server_stream: UnixStream, +} + +impl VteEventSender { + pub fn new(id: RawFd, err_ctx: ErrorContext) -> Self { + VteEventSender { + id, + err_ctx, + server_stream: UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(), + } + } +} + +impl vte::Perform for VteEventSender { + fn print(&mut self, c: char) { + let api_command = bincode::serialize(&( + self.err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Print(c))), + )) + .unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + fn execute(&mut self, byte: u8) { + let api_command = bincode::serialize(&( + self.err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte))), + )) + .unwrap(); + self.server_stream.write_all(&api_command).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(); + let instruction = + ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c)); + let api_command = + bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + + fn put(&mut self, byte: u8) { + let api_command = bincode::serialize(&( + self.err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Put(byte))), + )) + .unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + + fn unhook(&mut self) { + let api_command = bincode::serialize(&( + self.err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Unhook)), + )) + .unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + let params = params.iter().map(|p| p.to_vec()).collect(); + let instruction = + ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated)); + let api_command = + bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + + 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(); + let instruction = ScreenInstruction::Pty( + self.id, + VteEvent::CsiDispatch(params, intermediates, ignore, c), + ); + let api_command = + bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + let intermediates = intermediates.iter().copied().collect(); + let instruction = + ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte)); + let api_command = + bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } +} /// Instructions related to PTYs (pseudoterminals). -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum PtyInstruction { SpawnTerminal(Option), SpawnTerminalVertically(Option), @@ -78,25 +187,22 @@ pub enum PtyInstruction { } pub struct PtyBus { - pub send_screen_instructions: SenderWithContext, - pub send_plugin_instructions: SenderWithContext, pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub id_to_child_pid: HashMap, os_input: Box, debug_to_file: bool, task_handles: HashMap>, + pub server_stream: UnixStream, } -fn stream_terminal_bytes( - pid: RawFd, - send_screen_instructions: SenderWithContext, - os_input: Box, - debug: bool, -) -> JoinHandle<()> { - let mut err_ctx = get_current_ctx(); +fn stream_terminal_bytes(pid: RawFd, os_input: Box, debug: bool) -> JoinHandle<()> { + let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); task::spawn({ async move { err_ctx.add_call(ContextType::AsyncTask); + let mut server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); + let mut vte_parser = vte::Parser::new(); + let mut vte_event_sender = VteEventSender::new(pid, err_ctx); let mut terminal_bytes = ReadFromPid::new(&pid, os_input); let mut last_byte_receive_time: Option = None; @@ -122,7 +228,12 @@ fn stream_terminal_bytes( Some(receive_time) => { if receive_time.elapsed() > max_render_pause { pending_render = false; - let _ = send_screen_instructions.send(ScreenInstruction::Render); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Render), + )) + .unwrap(); + server_stream.write_all(&api_command).unwrap(); last_byte_receive_time = Some(Instant::now()); } else { pending_render = true; @@ -136,22 +247,31 @@ fn stream_terminal_bytes( } else { if pending_render { pending_render = false; - let _ = send_screen_instructions.send(ScreenInstruction::Render); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::Render), + )) + .unwrap(); + server_stream.write_all(&api_command).unwrap(); } last_byte_receive_time = None; task::sleep(::std::time::Duration::from_millis(10)).await; } } - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + let api_command = + bincode::serialize(&(err_ctx, ApiCommand::ToScreen(ScreenInstruction::Render))) + .unwrap(); + server_stream.write_all(&api_command).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_screen_instructions - .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) - .unwrap(); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))), + )) + .unwrap(); + server_stream.write_all(&api_command).unwrap(); } }) } @@ -159,35 +279,29 @@ fn stream_terminal_bytes( impl PtyBus { pub fn new( receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, - send_screen_instructions: SenderWithContext, - send_plugin_instructions: SenderWithContext, os_input: Box, + server_stream: UnixStream, debug_to_file: bool, ) -> Self { PtyBus { - send_screen_instructions, - send_plugin_instructions, receive_pty_instructions, os_input, id_to_child_pid: HashMap::new(), debug_to_file, task_handles: HashMap::new(), + server_stream, } } pub fn spawn_terminal(&mut self, file_to_open: Option) -> RawFd { let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(file_to_open); - let task_handle = stream_terminal_bytes( - pid_primary, - self.send_screen_instructions.clone(), - self.os_input.clone(), - self.debug_to_file, - ); + let task_handle = + stream_terminal_bytes(pid_primary, self.os_input.clone(), self.debug_to_file); self.task_handles.insert(pid_primary, task_handle); self.id_to_child_pid.insert(pid_primary, pid_secondary); pid_primary } - pub fn spawn_terminals_for_layout(&mut self, layout: Layout) { + pub fn spawn_terminals_for_layout(&mut self, layout: Layout, err_ctx: ErrorContext) { let total_panes = layout.total_terminal_panes(); let mut new_pane_pids = vec![]; for _ in 0..total_panes { @@ -195,23 +309,21 @@ impl PtyBus { self.id_to_child_pid.insert(pid_primary, pid_secondary); new_pane_pids.push(pid_primary); } - self.send_screen_instructions - .send(ScreenInstruction::ApplyLayout(( + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::ApplyLayout(( layout, new_pane_pids.clone(), - ))) - .unwrap(); + ))), + )) + .unwrap(); + self.server_stream.write_all(&api_command).unwrap(); for id in new_pane_pids { - let task_handle = stream_terminal_bytes( - id, - self.send_screen_instructions.clone(), - self.os_input.clone(), - self.debug_to_file, - ); + let task_handle = stream_terminal_bytes(id, self.os_input.clone(), self.debug_to_file); self.task_handles.insert(id, task_handle); } } - pub fn close_pane(&mut self, id: PaneId) { + pub fn close_pane(&mut self, id: PaneId, err_ctx: ErrorContext) { match id { PaneId::Terminal(id) => { let child_pid = self.id_to_child_pid.remove(&id).unwrap(); @@ -221,15 +333,16 @@ impl PtyBus { handle.cancel().await; }); } - PaneId::Plugin(pid) => drop( - self.send_plugin_instructions - .send(PluginInstruction::Unload(pid)), - ), + PaneId::Plugin(pid) => { + let api_command = + bincode::serialize(&(err_ctx, ApiCommand::ClosePluginPane(pid))).unwrap(); + self.server_stream.write_all(&api_command).unwrap(); + } } } - pub fn close_tab(&mut self, ids: Vec) { + pub fn close_tab(&mut self, ids: Vec, err_ctx: ErrorContext) { ids.iter().for_each(|&id| { - self.close_pane(id); + self.close_pane(id, err_ctx); }); } } @@ -238,7 +351,7 @@ impl Drop for PtyBus { fn drop(&mut self) { let child_ids: Vec = self.id_to_child_pid.keys().copied().collect(); for id in child_ids { - self.close_pane(PaneId::Terminal(id)); + self.close_pane(PaneId::Terminal(id), ErrorContext::new()); } } } diff --git a/src/common/screen.rs b/src/common/screen.rs index e6cf73f5..b6b8e7f2 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -1,5 +1,6 @@ //! Things related to [`Screen`]s. +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; @@ -16,7 +17,7 @@ use crate::{layout::Layout, panes::PaneId}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; /// Instructions that can be sent to the [`Screen`]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum ScreenInstruction { PtyBytes(RawFd, VteBytes), Render, @@ -68,8 +69,6 @@ pub struct Screen { max_panes: Option, /// A map between this [`Screen`]'s tabs and their ID/key. tabs: BTreeMap, - /// A [`PtyInstruction`] and [`ErrorContext`] sender. - pub send_pty_instructions: SenderWithContext, /// A [`PluginInstruction`] and [`ErrorContext`] sender. pub send_plugin_instructions: SenderWithContext, /// An [`AppInstruction`] and [`ErrorContext`] sender. @@ -91,7 +90,6 @@ impl Screen { #[allow(clippy::too_many_arguments)] pub fn new( receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, - send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, full_screen_ws: &PositionAndSize, @@ -104,7 +102,6 @@ impl Screen { Screen { receiver: receive_screen_instructions, max_panes, - send_pty_instructions, send_plugin_instructions, send_app_instructions, full_screen_ws: *full_screen_ws, @@ -128,7 +125,6 @@ impl Screen { String::new(), &self.full_screen_ws, self.os_api.clone(), - self.send_pty_instructions.clone(), self.send_plugin_instructions.clone(), self.send_app_instructions.clone(), self.max_panes, @@ -215,8 +211,8 @@ impl Screen { // 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 let _ = self - .send_pty_instructions - .send(PtyInstruction::CloseTab(pane_ids)); + .send_app_instructions + .send(AppInstruction::ToPty(PtyInstruction::CloseTab(pane_ids))); if self.tabs.is_empty() { self.active_tab_index = None; self.send_app_instructions @@ -285,7 +281,6 @@ impl Screen { String::new(), &self.full_screen_ws, self.os_api.clone(), - self.send_pty_instructions.clone(), self.send_plugin_instructions.clone(), self.send_app_instructions.clone(), self.max_panes, diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index bd98a85b..ddb7deaf 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -29,9 +29,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, - pub send_app_instructions: SenderWithContext, - pub send_pty_instructions: SenderWithContext, - pub send_plugin_instructions: SenderWithContext, + pub send_app_instructions: SenderWithContext, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, pub subscriptions: Arc>>, } @@ -74,6 +72,16 @@ fn host_unsubscribe(plugin_env: &PluginEnv) { subscriptions.retain(|k| !old.contains(k)); } +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, + )))) + .unwrap(); +} + fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { let selectable = selectable != 0; plugin_env diff --git a/src/main.rs b/src/main.rs index ece55fa2..5fa9317e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ mod cli; -mod common; -#[cfg(test)] -mod tests; -// TODO mod server; mod client; +mod common; +mod server; use crate::cli::CliArgs; use crate::command_is_executing::CommandIsExecuting; -use crate::common::input::config::Config; +use crate::errors::ErrorContext; use crate::os_input_output::get_os_input; use crate::utils::{ consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, @@ -36,23 +34,29 @@ pub fn main() { match split_dir { 'h' => { let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); - let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap(); + let api_command = + bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitHorizontally)) + .unwrap(); stream.write_all(&api_command).unwrap(); } 'v' => { let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); - let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap(); + let api_command = + bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitVertically)) + .unwrap(); stream.write_all(&api_command).unwrap(); } _ => {} }; } else if opts.move_focus { let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); - let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap(); + let api_command = + bincode::serialize(&(ErrorContext::new(), ApiCommand::MoveFocus)).unwrap(); stream.write_all(&api_command).unwrap(); } else if let Some(file_to_open) = opts.open_file { let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); - let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap(); + let api_command = + bincode::serialize(&(ErrorContext::new(), ApiCommand::OpenFile(file_to_open))).unwrap(); stream.write_all(&api_command).unwrap(); } else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option { let shell = match shell.as_ref() { diff --git a/src/server/mod.rs b/src/server/mod.rs index 626b4e6b..3db2ab52 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,198 @@ -use super::super::common::{screen}; +use crate::cli::CliArgs; +use crate::command_is_executing::CommandIsExecuting; +use crate::common::{ + ApiCommand, AppInstruction, ChannelWithContext, SenderType, SenderWithContext, +}; +use crate::errors::{ContextType, ErrorContext, PtyContext}; +use crate::layout::Layout; +use crate::os_input_output::OsApi; +use crate::panes::PaneId; +use crate::pty_bus::{PtyBus, PtyInstruction}; +use crate::screen::ScreenInstruction; +use crate::utils::consts::ZELLIJ_IPC_PIPE; +use crate::wasm_vm::PluginInstruction; +use std::io::{Read, Write}; +use std::os::unix::net::UnixStream; +use std::path::PathBuf; +use std::sync::mpsc::channel; +use std::thread; -pub fn start_server() { - // TODO -} \ No newline at end of file +pub fn start_server( + os_input: Box, + opts: CliArgs, + command_is_executing: CommandIsExecuting, + mut send_app_instructions: SenderWithContext, +) -> thread::JoinHandle<()> { + let (send_pty_instructions, receive_pty_instructions): ChannelWithContext = + channel(); + let mut send_pty_instructions = SenderWithContext::new( + ErrorContext::new(), + SenderType::Sender(send_pty_instructions), + ); + + std::fs::remove_file(ZELLIJ_IPC_PIPE).ok(); + let listener = std::os::unix::net::UnixListener::bind(ZELLIJ_IPC_PIPE) + .expect("could not listen on ipc socket"); + + // 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).map(Layout::new); + + let server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); + let mut pty_bus = PtyBus::new( + receive_pty_instructions, + os_input.clone(), + server_stream, + opts.debug, + ); + + let pty_thread = thread::Builder::new() + .name("pty".to_string()) + .spawn({ + let mut command_is_executing = command_is_executing.clone(); + send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); + 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); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::NewPane(PaneId::Terminal(pid))), + )) + .unwrap(); + pty_bus.server_stream.write_all(&api_command).unwrap(); + } + PtyInstruction::SpawnTerminalVertically(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::VerticalSplit( + PaneId::Terminal(pid), + )), + )) + .unwrap(); + pty_bus.server_stream.write_all(&api_command).unwrap(); + } + PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::HorizontalSplit( + PaneId::Terminal(pid), + )), + )) + .unwrap(); + pty_bus.server_stream.write_all(&api_command).unwrap(); + } + PtyInstruction::NewTab => { + if let Some(layout) = maybe_layout.clone() { + pty_bus.spawn_terminals_for_layout(layout, err_ctx); + } else { + let pid = pty_bus.spawn_terminal(None); + let api_command = bincode::serialize(&( + err_ctx, + ApiCommand::ToScreen(ScreenInstruction::NewTab(pid)), + )) + .unwrap(); + pty_bus.server_stream.write_all(&api_command).unwrap(); + } + } + PtyInstruction::ClosePane(id) => { + pty_bus.close_pane(id, err_ctx); + command_is_executing.done_closing_pane(); + } + PtyInstruction::CloseTab(ids) => { + pty_bus.close_tab(ids, err_ctx); + command_is_executing.done_closing_pane(); + } + PtyInstruction::Quit => { + break; + } + } + } + }) + .unwrap(); + + thread::Builder::new() + .name("ipc_server".to_string()) + .spawn({ + move || { + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut buffer = [0; 65535]; // TODO: more accurate + let bytes = stream + .read(&mut buffer) + .expect("failed to parse ipc message"); + println!("{}\n\n", bytes); + let (mut err_ctx, decoded): (ErrorContext, ApiCommand) = + bincode::deserialize(&buffer[0..bytes]) + .expect("failed to deserialize ipc message"); + err_ctx.add_call(ContextType::IPCServer); + send_pty_instructions.update(err_ctx); + send_app_instructions.update(err_ctx); + + match decoded { + ApiCommand::OpenFile(file_name) => { + let path = PathBuf::from(file_name); + send_pty_instructions + .send(PtyInstruction::SpawnTerminal(Some(path))) + .unwrap(); + } + ApiCommand::SplitHorizontally => { + send_pty_instructions + .send(PtyInstruction::SpawnTerminalHorizontally(None)) + .unwrap(); + } + ApiCommand::SplitVertically => { + send_pty_instructions + .send(PtyInstruction::SpawnTerminalVertically(None)) + .unwrap(); + } + ApiCommand::MoveFocus => { + send_app_instructions + .send(AppInstruction::ToScreen( + ScreenInstruction::MoveFocus, + )) + .unwrap(); + } + ApiCommand::ToPty(instruction) => { + send_pty_instructions.send(instruction).unwrap(); + } + ApiCommand::ToScreen(instruction) => { + send_app_instructions + .send(AppInstruction::ToScreen(instruction)) + .unwrap(); + } + ApiCommand::ClosePluginPane(pid) => { + send_app_instructions + .send(AppInstruction::ToPlugin(PluginInstruction::Unload( + pid, + ))) + .unwrap(); + } + ApiCommand::Quit => { + send_pty_instructions.send(PtyInstruction::Quit).unwrap(); + break; + } + } + } + Err(err) => { + panic!("err {:?}", err); + } + } + } + + let _ = pty_thread.join(); + } + }) + .unwrap() +}