diff options
author | Kunal Mohan <44079328+kunalmohan@users.noreply.github.com> | 2021-05-06 22:11:07 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-06 22:11:07 +0530 |
commit | d5433f8f8995c155f231c823d49813882b6212bf (patch) | |
tree | 529d5ee706ded18cd24f26a3b0372bf17ecb81ed | |
parent | b18f5c9510b2dd5dd31cd5aa1a40f2fa08eca457 (diff) | |
parent | 7982636741df86e7323f73726631ef15745b15fc (diff) |
Merge pull request #223 from zellij-org/isolate-pty
Psuedo Client-Server model
37 files changed, 1876 insertions, 1200 deletions
diff --git a/Cargo.lock b/Cargo.lock index 3d04bc27f..b1fbaf3e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,6 +599,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] name = "funty" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1032,6 +1038,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" [[package]] +name = "names" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da" +dependencies = [ + "rand 0.3.23", +] + +[[package]] name = "nix" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1203,13 +1218,36 @@ checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" [[package]] name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.2", "rand_hc", ] @@ -1220,11 +1258,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", ] [[package]] name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" @@ -1238,7 +1291,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", ] [[package]] @@ -1267,6 +1320,15 @@ dependencies = [ ] [[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] name = "redox_syscall" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1593,7 +1655,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi", @@ -2205,6 +2267,7 @@ dependencies = [ "interprocess", "lazy_static", "libc", + "names", "nix", "nom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 705a09066..f59b1e8b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,8 @@ strum = "0.20.0" lazy_static = "1.4.0" wasmer = "1.0.0" wasmer-wasi = "1.0.0" -interprocess = "1.0.1" +interprocess = "1.1.1" +names = "0.11.0" colors-transform = "0.2.5" zellij-tile = { path = "zellij-tile/", version = "0.7.0" } diff --git a/src/cli.rs b/src/cli.rs index bc4e4a9e6..91b0ed39d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,22 +1,11 @@ use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV}; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use structopt::StructOpt; -#[derive(StructOpt, Default, Debug)] +#[derive(StructOpt, Default, Debug, Clone, Serialize, Deserialize)] #[structopt(name = "zellij")] pub struct CliArgs { - /// Send "split (direction h == horizontal / v == vertical)" to active zellij session - #[structopt(short, long)] - pub split: Option<char>, - - /// Send "move focused pane" to active zellij session - #[structopt(short, long)] - pub move_focus: bool, - - /// Send "open file in new pane" to active zellij session - #[structopt(short, long)] - pub open_file: Option<PathBuf>, - /// Maximum panes on screen, caution: opening more panes will close old ones #[structopt(long)] pub max_panes: Option<usize>, @@ -44,7 +33,7 @@ pub struct CliArgs { pub debug: bool, } -#[derive(Debug, StructOpt)] +#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] pub enum ConfigCli { /// Change the behaviour of zellij #[structopt(name = "option")] diff --git a/src/client/mod.rs b/src/client/mod.rs index cba12a46c..8a5834f06 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -4,4 +4,172 @@ pub mod pane_resizer; pub mod panes; pub mod tab; -pub fn _start_client() {} +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::sync::mpsc; +use std::thread; + +use crate::cli::CliArgs; +use crate::common::{ + command_is_executing::CommandIsExecuting, + errors::{ClientContext, ContextType}, + input::config::Config, + input::handler::input_loop, + os_input_output::ClientOsApi, + SenderType, SenderWithContext, SyncChannelWithContext, +}; +use crate::server::ServerInstruction; + +/// Instructions related to the client-side application and sent from server to client +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ClientInstruction { + Error(String), + Render(Option<String>), + UnblockInputThread, + Exit, +} + +pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) { + let take_snapshot = "\u{1b}[?1049h"; + os_input.unset_raw_mode(0); + let _ = os_input + .get_stdout_writer() + .write(take_snapshot.as_bytes()) + .unwrap(); + std::env::set_var(&"ZELLIJ", "0"); + + let mut command_is_executing = CommandIsExecuting::new(); + + let full_screen_ws = os_input.get_terminal_size_using_fd(0); + os_input.connect_to_server(); + os_input.send_to_server(ServerInstruction::NewClient(full_screen_ws, opts)); + os_input.set_raw_mode(0); + + let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< + ClientInstruction, + > = mpsc::sync_channel(50); + let send_client_instructions = + SenderWithContext::new(SenderType::SyncSender(send_client_instructions)); + + #[cfg(not(test))] + std::panic::set_hook({ + use crate::errors::handle_panic; + let send_client_instructions = send_client_instructions.clone(); + Box::new(move |info| { + handle_panic(info, &send_client_instructions); + }) + }); + + let _stdin_thread = thread::Builder::new() + .name("stdin_handler".to_string()) + .spawn({ + 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, + config, + command_is_executing, + send_client_instructions, + ) + } + }); + + let _signal_thread = thread::Builder::new() + .name("signal_listener".to_string()) + .spawn({ + let os_input = os_input.clone(); + move || { + os_input.receive_sigwinch(Box::new({ + let os_api = os_input.clone(); + move || { + os_api.send_to_server(ServerInstruction::TerminalResize( + os_api.get_terminal_size_using_fd(0), + )); + } + })); + } + }) + .unwrap(); + + let router_thread = thread::Builder::new() + .name("router".to_string()) + .spawn({ + let os_input = os_input.clone(); + move || { + loop { + let (instruction, mut err_ctx) = os_input.recv_from_server(); + err_ctx.add_call(ContextType::Client(ClientContext::from(&instruction))); + 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 (client_instruction, mut err_ctx) = receive_client_instructions + .recv() + .expect("failed to receive app instruction on channel"); + + err_ctx.add_call(ContextType::Client(ClientContext::from( + &client_instruction, + ))); + match client_instruction { + ClientInstruction::Exit => break, + ClientInstruction::Error(backtrace) => { + let _ = os_input.send_to_server(ServerInstruction::ClientExit); + os_input.unset_raw_mode(0); + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let restore_snapshot = "\u{1b}[?1049l"; + let error = format!( + "{}\n{}{}", + goto_start_of_last_line, restore_snapshot, backtrace + ); + let _ = os_input + .get_stdout_writer() + .write(error.as_bytes()) + .unwrap(); + std::process::exit(1); + } + ClientInstruction::Render(output) => { + if output.is_none() { + break; + } + let mut stdout = os_input.get_stdout_writer(); + stdout + .write_all(&output.unwrap().as_bytes()) + .expect("cannot write to stdout"); + stdout.flush().expect("could not flush"); + } + ClientInstruction::UnblockInputThread => { + command_is_executing.unblock_input_thread(); + } + } + } + + let _ = os_input.send_to_server(ServerInstruction::ClientExit); + router_thread.join().unwrap(); + + // cleanup(); + let reset_style = "\u{1b}[m"; + let show_cursor = "\u{1b}[?25h"; + let restore_snapshot = "\u{1b}[?1049l"; + let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); + let goodbye_message = format!( + "{}\n{}{}{}Bye from Zellij!\n", + goto_start_of_last_line, restore_snapshot, reset_style, show_cursor + ); + + os_input.unset_raw_mode(0); + let mut stdout = os_input.get_stdout_writer(); + let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); + stdout.flush().unwrap(); +} diff --git a/src/client/pane_resizer.rs b/src/client/pane_resizer.rs index f9ec71b82..73678499c 100644 --- a/src/client/pane_resizer.rs +++ b/src/client/pane_resizer.rs @@ -1,4 +1,4 @@ -use crate::os_input_output::OsApi; +use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize}; use crate::tab::Pane; use std::{ @@ -8,7 +8,7 @@ use std::{ pub struct PaneResizer<'a> { panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn OsApi>, + os_api: &'a mut Box<dyn ServerOsApi>, } // TODO: currently there are some functions here duplicated with Tab @@ -17,7 +17,7 @@ pub struct PaneResizer<'a> { impl<'a> PaneResizer<'a> { pub fn new( panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn OsApi>, + os_api: &'a mut Box<dyn ServerOsApi>, ) -> Self { PaneResizer { panes, os_api } } diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index a5a957e67..f58c3a273 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -1,6 +1,7 @@ use crate::tab::Pane; use ::nix::pty::Winsize; use ::std::os::unix::io::RawFd; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::time::Instant; @@ -10,7 +11,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? @@ -18,7 +19,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 b01e04e57..0098d78b6 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -2,22 +2,23 @@ //! as well as how they should be resized use crate::client::pane_resizer::PaneResizer; -use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext}; +use crate::common::{input::handler::parse_keys, SenderWithContext}; use crate::layout::Layout; -use crate::os_input_output::OsApi; +use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize, TerminalPane}; use crate::pty_bus::{PtyInstruction, VteBytes}; +use crate::server::ServerInstruction; use crate::utils::shared::adjust_to_size; use crate::wasm_vm::PluginInstruction; use crate::{boundaries::Boundaries, panes::PluginPane}; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; +use std::sync::mpsc::channel; use std::time::Instant; use std::{ cmp::Reverse, collections::{BTreeMap, HashSet}, }; -use std::{io::Write, sync::mpsc::channel}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette}; const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this @@ -67,11 +68,11 @@ pub struct Tab { max_panes: Option<usize>, full_screen_ws: PositionAndSize, fullscreen_is_active: bool, + os_api: Box<dyn ServerOsApi>, + send_plugin_instructions: SenderWithContext<PluginInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server_instructions: SenderWithContext<ServerInstruction>, synchronize_is_active: bool, - os_api: Box<dyn OsApi>, - pub send_pty_instructions: SenderWithContext<PtyInstruction>, - pub send_plugin_instructions: SenderWithContext<PluginInstruction>, - pub send_app_instructions: SenderWithContext<AppInstruction>, should_clear_display_before_rendering: bool, pub mode_info: ModeInfo, pub input_mode: InputMode, @@ -225,10 +226,10 @@ impl Tab { position: usize, name: String, full_screen_ws: &PositionAndSize, - mut os_api: Box<dyn OsApi>, - send_pty_instructions: SenderWithContext<PtyInstruction>, + mut os_api: Box<dyn ServerOsApi>, send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_app_instructions: SenderWithContext<AppInstruction>, + send_pty_instructions: SenderWithContext<PtyInstruction>, + send_server |