pub mod boundaries; pub mod layout; 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 crate::cli::CliArgs; use crate::common::{ command_is_executing::CommandIsExecuting, errors::{ClientContext, ContextType}, input::config::Config, input::handler::input_loop, input::options::Options, os_input_output::ClientOsApi, thread_bus::{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), UnblockInputThread, Exit, } pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; let bracketed_paste = "\u{1b}[?2004h"; os_input.unset_raw_mode(0); let _ = os_input .get_stdout_writer() .write(take_snapshot.as_bytes()) .unwrap(); let _ = os_input .get_stdout_writer() .write(clear_client_terminal_attributes.as_bytes()) .unwrap(); std::env::set_var(&"ZELLIJ", "0"); let mut command_is_executing = CommandIsExecuting::new(); let config_options = Options::from_cli(&config.options, opts.option.clone()); 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, config_options, )); os_input.set_raw_mode(0); let _ = os_input .get_stdout_writer() .write(bracketed_paste.as_bytes()) .unwrap(); 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(); }