use super::input_loop; use zellij_utils::input::actions::{Action, Direction}; use zellij_utils::input::config::Config; use zellij_utils::input::options::Options; use zellij_utils::nix; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers}; use zellij_utils::zellij_tile::data::Palette; use crate::InputInstruction; use crate::{ os_input_output::{ClientOsApi, StdinPoller}, ClientInstruction, CommandIsExecuting, }; use std::path::Path; use zellij_utils::zellij_tile; use std::io; use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; use zellij_tile::data::InputMode; use zellij_utils::{ errors::ErrorContext, ipc::{ClientToServerMsg, PixelDimensions, ServerToClientMsg}, }; use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext}; #[allow(unused)] pub mod commands { pub const QUIT: [u8; 1] = [17]; // ctrl-q pub const ESC: [u8; 1] = [27]; pub const ENTER: [u8; 1] = [10]; // char '\n' pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~ pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201 pub const SLEEP: [u8; 0] = []; } #[derive(Default, Clone)] struct FakeStdoutWriter { buffer: Arc>>, } impl FakeStdoutWriter { pub fn new(buffer: Arc>>) -> Self { FakeStdoutWriter { buffer } } } impl io::Write for FakeStdoutWriter { fn write(&mut self, buf: &[u8]) -> Result { self.buffer.lock().unwrap().extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> Result<(), io::Error> { Ok(()) } } #[derive(Clone)] struct FakeClientOsApi { events_sent_to_server: Arc>>, command_is_executing: Arc>, stdout_buffer: Arc>>, } impl FakeClientOsApi { pub fn new( events_sent_to_server: Arc>>, command_is_executing: CommandIsExecuting, ) -> Self { // while command_is_executing itself is implemented with an Arc, we have to have an // Arc here because we need interior mutability, otherwise we'll have to change the // ClientOsApi trait, and that will cause a lot of havoc let command_is_executing = Arc::new(Mutex::new(command_is_executing)); let stdout_buffer = Arc::new(Mutex::new(vec![])); FakeClientOsApi { events_sent_to_server, command_is_executing, stdout_buffer, } } pub fn stdout_buffer(&self) -> Vec { self.stdout_buffer.lock().unwrap().drain(..).collect() } } impl ClientOsApi for FakeClientOsApi { fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size { unimplemented!() } fn set_raw_mode(&mut self, _fd: RawFd) { unimplemented!() } fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> { unimplemented!() } fn get_stdout_writer(&self) -> Box { let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone()); Box::new(fake_stdout_writer) } fn get_stdin_reader(&self) -> Box { unimplemented!() } fn read_from_stdin(&self) -> Vec { unimplemented!() } fn box_clone(&self) -> Box { unimplemented!() } fn send_to_server(&self, msg: ClientToServerMsg) { { let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap(); events_sent_to_server.push(msg); } { let mut command_is_executing = self.command_is_executing.lock().unwrap(); command_is_executing.unblock_input_thread(); } } fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> { unimplemented!() } fn handle_signals(&self, _sigwinch_cb: Box, _quit_cb: Box) { unimplemented!() } fn connect_to_server(&self, _path: &Path) { unimplemented!() } fn load_palette(&self) -> Palette { unimplemented!() } fn enable_mouse(&self) {} fn disable_mouse(&self) {} fn stdin_poller(&self) -> StdinPoller { unimplemented!() } } fn extract_actions_sent_to_server( events_sent_to_server: Arc>>, ) -> Vec { let events_sent_to_server = events_sent_to_server.lock().unwrap(); events_sent_to_server.iter().fold(vec![], |mut acc, event| { if let ClientToServerMsg::Action(action, None) = event { acc.push(action.clone()); } acc }) } fn extract_pixel_events_sent_to_server( events_sent_to_server: Arc>>, ) -> Vec { let events_sent_to_server = events_sent_to_server.lock().unwrap(); events_sent_to_server.iter().fold(vec![], |mut acc, event| { if let ClientToServerMsg::TerminalPixelDimensions(pixel_dimensions) = event { acc.push(*pixel_dimensions); } acc }) } #[test] pub fn quit_breaks_input_loop() { let stdin_events = vec![( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), )]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = Box::new(FakeClientOsApi::new( events_sent_to_server.clone(), command_is_executing.clone(), )); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; input_loop( client_os_api, config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let expected_actions_sent_to_server = vec![Action::Quit]; let received_actions = extract_actions_sent_to_server(events_sent_to_server); assert_eq!( expected_actions_sent_to_server, received_actions, "All actions sent to server properly" ); } #[test] pub fn move_focus_left_in_normal_mode() { let stdin_events = vec![ ( commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('h'), modifiers: Modifiers::ALT, }), ), ( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), ), ]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = Box::new(FakeClientOsApi::new( events_sent_to_server.clone(), command_is_executing.clone(), )); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; input_loop( client_os_api, config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let expected_actions_sent_to_server = vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit]; let received_actions = extract_actions_sent_to_server(events_sent_to_server); assert_eq!( expected_actions_sent_to_server, received_actions, "All actions sent to server properly" ); } #[test] pub fn pixel_info_queried_from_terminal_emulator() { let stdin_events = vec![( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), )]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = FakeClientOsApi::new(events_sent_to_server, command_is_executing.clone()); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; let client_os_api_clone = client_os_api.clone(); input_loop( Box::new(client_os_api), config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let extracted_stdout_buffer = client_os_api_clone.stdout_buffer(); assert_eq!( String::from_utf8(extracted_stdout_buffer), Ok(String::from( "\u{1b}[14t\u{1b}[16t\u{1b}]11;?\u{1b}\\\u{1b}]10;?\u{1b}\\" )), ); } #[test] pub fn pixel_info_sent_to_server() { let stdin_events = vec![ ( vec![27], InputEvent::Key(KeyEvent { key: KeyCode::Escape, modifiers: Modifiers::NONE, }), ), ( "[".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('['), modifiers: Modifiers::NONE, }), ), ( "6".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('6'), modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "1".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('1'), modifiers: Modifiers::NONE, }), ), ( "0".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('0'), modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "5".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('5'), modifiers: Modifiers::NONE, }), ), ( "t".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('t'), modifiers: Modifiers::NONE, }), ), ( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), ), ]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; input_loop( Box::new(client_os_api), config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); assert_eq!(actions_sent_to_server, vec![Action::Quit]); assert_eq!( pixel_events_sent_to_server, vec![PixelDimensions { character_cell_size: Some(SizeInPixels { height: 10, width: 5 }), text_area_size: None }], ); } #[test] pub fn corrupted_pixel_info_sent_as_key_events() { let stdin_events = vec![ ( vec![27], InputEvent::Key(KeyEvent { key: KeyCode::Escape, modifiers: Modifiers::NONE, }), ), ( "[".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('['), modifiers: Modifiers::NONE, }), ), ( "f".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('f'), modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "1".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('1'), modifiers: Modifiers::NONE, }), ), ( "0".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('0'), modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "5".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('5'), modifiers: Modifiers::NONE, }), ), ( "t".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('t'), modifiers: Modifiers::NONE, }), ), ( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), ), ]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; input_loop( Box::new(client_os_api), config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); assert_eq!( actions_sent_to_server, vec![ Action::Write(vec![27]), Action::Write(vec![b'[']), Action::Write(vec![b'f']), Action::Write(vec![b';']), Action::Write(vec![b'1']), Action::Write(vec![b'0']), Action::Write(vec![b';']), Action::Write(vec![b'5']), Action::Write(vec![b't']), Action::Quit ] ); assert_eq!(pixel_events_sent_to_server, vec![],); } #[test] pub fn esc_in_the_middle_of_pixelinfo_breaks_out_of_it() { let stdin_events = vec![ ( vec![27], InputEvent::Key(KeyEvent { key: KeyCode::Escape, modifiers: Modifiers::NONE, }), ), ( "[".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('['), modifiers: Modifiers::NONE, }), ), ( vec![27], InputEvent::Key(KeyEvent { key: KeyCode::Escape, modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "1".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('1'), modifiers: Modifiers::NONE, }), ), ( "0".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('0'), modifiers: Modifiers::NONE, }), ), ( ";".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char(';'), modifiers: Modifiers::NONE, }), ), ( "5".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('5'), modifiers: Modifiers::NONE, }), ), ( "t".as_bytes().to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('t'), modifiers: Modifiers::NONE, }), ), ( commands::QUIT.to_vec(), InputEvent::Key(KeyEvent { key: KeyCode::Char('q'), modifiers: Modifiers::CTRL, }), ), ]; let events_sent_to_server = Arc::new(Mutex::new(vec![])); let command_is_executing = CommandIsExecuting::new(); let client_os_api = FakeClientOsApi::new(events_sent_to_server.clone(), command_is_executing.clone()); let config = Config::from_default_assets().unwrap(); let options = Options::default(); let (send_client_instructions, _receive_client_instructions): ChannelWithContext< ClientInstruction, > = channels::bounded(50); let send_client_instructions = SenderWithContext::new(send_client_instructions); let (send_input_instructions, receive_input_instructions): ChannelWithContext< InputInstruction, > = channels::bounded(50); let send_input_instructions = SenderWithContext::new(send_input_instructions); for event in stdin_events { send_input_instructions .send(InputInstruction::KeyEvent(event.1, event.0)) .unwrap(); } let default_mode = InputMode::Normal; input_loop( Box::new(client_os_api), config, options, command_is_executing, send_client_instructions, default_mode, receive_input_instructions, ); let actions_sent_to_server = extract_actions_sent_to_server(events_sent_to_server.clone()); let pixel_events_sent_to_server = extract_pixel_events_sent_to_server(events_sent_to_server); assert_eq!( actions_sent_to_server, vec![ Action::Write(vec![27]), Action::Write(vec![b'[']), Action::Write(vec![27]), Action::Write(vec![b';']), Action::Write(vec![b'1']), Action::Write(vec![b'0']), Action::Write(vec![b';']), Action::Write(vec![b'5']), Action::Write(vec![b't']), Action::Quit ] ); assert_eq!(pixel_events_sent_to_server, vec![],); }