use super::{Output, Tab}; use crate::panes::sixel::SixelImageStore; use crate::screen::CopyOptions; use crate::Arc; use crate::Mutex; use crate::{ os_input_output::{AsyncReader, Pid, ServerOsApi}, panes::PaneId, plugins::PluginInstruction, thread_bus::ThreadSenders, ClientId, }; use std::path::PathBuf; use zellij_utils::channels::Receiver; use zellij_utils::data::Direction; use zellij_utils::data::Resize; use zellij_utils::data::ResizeStrategy; use zellij_utils::envs::set_session_name; use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::input::layout::{ FloatingPaneLayout, Layout, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout, }; use zellij_utils::input::plugins::PluginTag; use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::position::Position; use crate::pty_writer::PtyWriteInstruction; use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext}; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; use zellij_utils::{ data::{InputMode, ModeInfo, Palette, Style}, input::command::{RunCommand, TerminalAction}, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, }; #[derive(Clone, Default)] struct FakeInputOutput { file_dumps: Arc>>, pub tty_stdin_bytes: Arc>>>, } impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_terminal_id( &self, _id: u32, _cols: u16, _rows: u16, _width_in_pixels: Option, _height_in_pixels: Option, ) -> Result<()> { // noop Ok(()) } fn spawn_terminal( &self, _file_to_open: TerminalAction, _quit_db: Box, RunCommand) + Send>, _default_editor: Option, ) -> Result<(u32, RawFd, RawFd)> { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { unimplemented!() } fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } fn write_to_tty_stdin(&self, id: u32, buf: &[u8]) -> Result { self.tty_stdin_bytes .lock() .unwrap() .entry(id) .or_insert_with(|| vec![]) .extend_from_slice(buf); Ok(buf.len()) } fn tcdrain(&self, _id: u32) -> Result<()> { unimplemented!() } fn kill(&self, _pid: Pid) -> Result<()> { unimplemented!() } fn force_kill(&self, _pid: Pid) -> Result<()> { unimplemented!() } fn box_clone(&self) -> Box { Box::new((*self).clone()) } fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) -> Result<()> { unimplemented!() } fn new_client( &mut self, _client_id: ClientId, _stream: LocalSocketStream, ) -> Result> { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) -> Result<()> { unimplemented!() } fn load_palette(&self) -> Palette { unimplemented!() } fn get_cwd(&self, _pid: Pid) -> Option { unimplemented!() } fn write_to_file(&mut self, buf: String, name: Option) -> Result<()> { let f: String = match name { Some(x) => x, None => "tmp-name".to_owned(), }; self.file_dumps.lock().to_anyhow()?.insert(f, buf); Ok(()) } fn re_run_command_in_terminal( &self, _terminal_id: u32, _run_command: RunCommand, _quit_cb: Box, RunCommand) + Send>, // u32 is the exit status ) -> Result<(RawFd, RawFd)> { unimplemented!() } fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> { unimplemented!() } } struct MockPtyInstructionBus { output: Arc>>, pty_writer_sender: SenderWithContext, pty_writer_receiver: Arc>, handle: Option>, } impl MockPtyInstructionBus { fn new() -> Self { let output = Arc::new(Mutex::new(vec![])); let (pty_writer_sender, pty_writer_receiver): ChannelWithContext = channels::unbounded(); let pty_writer_sender = SenderWithContext::new(pty_writer_sender); let pty_writer_receiver = Arc::new(pty_writer_receiver); Self { output, pty_writer_sender, pty_writer_receiver, handle: None, } } fn start(&mut self) { let output = self.output.clone(); let pty_writer_receiver = self.pty_writer_receiver.clone(); let handle = std::thread::Builder::new() .name("pty_writer".to_string()) .spawn({ move || loop { let (event, _err_ctx) = pty_writer_receiver .recv() .expect("failed to receive event on channel"); match event { PtyWriteInstruction::Write(msg, _) => output .lock() .unwrap() .push(String::from_utf8_lossy(&msg).to_string()), PtyWriteInstruction::Exit => break, } } }) .unwrap(); self.handle = Some(handle); } fn exit(&mut self) { self.pty_writer_sender .send(PtyWriteInstruction::Exit) .unwrap(); let handle = self.handle.take().unwrap(); handle.join().unwrap(); } fn pty_write_sender(&self) -> SenderWithContext { self.pty_writer_sender.clone() } fn clone_output(&self) -> Vec { self.output.lock().unwrap().clone() } } // TODO: move to shared thingy with other test file fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput::default()); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = default_mode; let style = Style::default(); let draw_pane_frames = true; let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = Tab::new( index, position, name, size, character_cell_info, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), ); tab.apply_layout( TiledPaneLayout::default(), vec![], vec![(1, None)], vec![], HashMap::new(), client_id, ) .unwrap(); tab } fn create_new_tab_with_swap_layouts( size: Size, default_mode: ModeInfo, swap_layouts: (Vec, Vec), base_layout_and_ids: Option<( TiledPaneLayout, Vec, Vec<(u32, Option)>, Vec<(u32, Option)>, HashMap>, )>, draw_pane_frames: bool, ) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput::default()); let mut senders = ThreadSenders::default().silently_fail_on_send(); let (mock_plugin_sender, _mock_plugin_receiver): ChannelWithContext = channels::unbounded(); senders.replace_to_plugin(SenderWithContext::new(mock_plugin_sender)); let max_panes = None; let mode_info = default_mode; let style = Style::default(); let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = Tab::new( index, position, name, size, character_cell_info, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, swap_layouts, ); let ( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, ) = base_layout_and_ids.unwrap_or_default(); let new_terminal_ids = if new_terminal_ids.is_empty() { vec![(1, None)] } else { new_terminal_ids }; tab.apply_layout( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, client_id, ) .unwrap(); tab } fn create_new_tab_with_os_api( size: Size, default_mode: ModeInfo, os_api: &Box, ) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = os_api.clone(); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = default_mode; let style = Style::default(); let draw_pane_frames = true; let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = Tab::new( index, position, name, size, character_cell_info, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts ); tab.apply_layout( TiledPaneLayout::default(), vec![], vec![(1, None)], vec![], HashMap::new(), client_id, ) .unwrap(); tab } fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput::default()); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = default_mode; let style = Style::default(); let draw_pane_frames = true; let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let layout = Layout::from_str(layout, "layout_file_name".into(), None, None).unwrap(); let (tab_layout, floating_panes_layout) = layout.new_tab(); let mut tab = Tab::new( index, position, name, size, character_cell_info, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts ); let pane_ids = tab_layout .extract_run_instructions() .iter() .enumerate() .map(|(i, _)| (i as u32, None)) .collect(); let floating_pane_ids = floating_panes_layout .iter() .enumerate() .map(|(i, _)| (i as u32, None)) .collect(); tab.apply_layout( tab_layout, floating_panes_layout, pane_ids, floating_pane_ids, HashMap::new(), client_id, ) .unwrap(); tab } fn create_new_tab_with_mock_pty_writer( size: Size, default_mode: ModeInfo, mock_pty_writer: SenderWithContext, ) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput::default()); let mut senders = ThreadSenders::default().silently_fail_on_send(); senders.replace_to_pty_writer(mock_pty_writer); let max_panes = None; let mode_info = default_mode; let style = Style::default(); let draw_pane_frames = true; let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_info = Rc::new(RefCell::new(None)); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = Tab::new( index, position, name, size, character_cell_info, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts ); tab.apply_layout( TiledPaneLayout::default(), vec![], vec![(1, None)], vec![], HashMap::new(), client_id, ) .unwrap(); tab } fn create_new_tab_with_sixel_support( size: Size, sixel_image_store: Rc>, ) -> Tab { // this is like the create_new_tab function but includes stuff needed for sixel, // eg. character_cell_size set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput::default()); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = ModeInfo::default(); let style = Style::default(); let draw_pane_frames = true; let auto_layout = true; let client_id = 1; let session_is_mirrored = true; let mut connected_clients = HashSet::new(); connected_clients.insert(client_id); let connected_clients = Rc::new(RefCell::new(connected_clients)); let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { width: 8, height: 21, }))); let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); let copy_options = CopyOptions::default(); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let mut tab = Tab::new( index, position, name, size, character_cell_size, sixel_image_store, os_api, senders, max_panes, style, mode_info, draw_pane_frames, auto_layout, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts ); tab.apply_layout( TiledPaneLayout::default(), vec![], vec![(1, None)], vec![], HashMap::new(), client_id, ) .unwrap(); tab } fn read_fixture(fixture_name: &str) -> Vec { let mut path_to_file = std::path::PathBuf::new(); path_to_file.push("../src"); path_to_file.push("tests"); path_to_file.push("fixtures"); path_to_file.push(fixture_name); std::fs::read(path_to_file) .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name)) } use crate::panes::grid::Grid; use crate::panes::link_handler::LinkHandler; use insta::assert_snapshot; use zellij_utils::vte; fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String { let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { width: 8, height: 21, }))); let mut grid = Grid::new( rows, columns, Rc::new(RefCell::new(palette)), terminal_emulator_color_codes, Rc::new(RefCell::new(LinkHandler::new())), character_cell_size, sixel_image_store, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { vte_parser.advance(&mut grid, byte); } format!("{:?}", grid) } fn take_snapshot_with_sixel( ansi_instructions: &str, rows: usize, columns: usize, palette: Palette, sixel_image_store: Rc>, ) -> String { let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { width: 8, height: 21, }))); let mut grid = Grid::new( rows, columns, Rc::new(RefCell::new(palette)), terminal_emulator_color_codes, Rc::new(RefCell::new(LinkHandler::new())), character_cell_size, sixel_image_store, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { vte_parser.advance(&mut grid, byte); } format!("{:?}", grid) } fn take_snapshot_and_cursor_position( ansi_instructions: &str, rows: usize, columns: usize, palette: Palette, ) -> (String, Option<(usize, usize)>) { // snapshot, x_coordinates, y_coordinates let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); let mut grid = Grid::new( rows, columns, Rc::new(RefCell::new(palette)), terminal_emulator_color_codes, Rc::new(RefCell::new(LinkHandler::new())), Rc::new(RefCell::new(None)), sixel_image_store, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { vte_parser.advance(&mut grid, byte); } (format!("{:?}", grid), grid.cursor_coordinates()) } #[test] fn dump_screen() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let map = Arc::new(Mutex::new(HashMap::new())); tab.os_api = Box::new(FakeInputOutput { file_dumps: map.clone(), ..Default::default() }); let new_pane_id = PaneId::Terminal(2); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes())) .unwrap(); let file = "/tmp/log.sh"; tab.dump_active_terminal_screen(Some(file.to_string()), client_id, false) .unwrap(); assert_eq!( map.lock().unwrap().get(file).unwrap(), "scratch", "screen was dumped properly" ); } #[test] fn new_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn floating_panes_persist_across_toggles() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); // here we send bytes to the pane when it's not visible to make sure they're still handled and // we see them once we toggle the panes back tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn toggle_floating_panes_off() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn toggle_floating_panes_on() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn five_new_floating_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn increase_floating_pane_size() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn decrease_floating_pane_size() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize(client_id, ResizeStrategy::new(Resize::Decrease, None)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_floating_pane_left() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Left)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_floating_pane_right() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Right)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_floating_pane_up() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Up)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_floating_pane_down() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Down)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_focus_left() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.move_focus_left(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((3, 3)), "cursor coordinates moved to the pane on the left" ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_focus_right() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.move_focus_left(client_id).unwrap(); tab.move_focus_right(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((5, 5)), "cursor coordinates moved to the pane on the right" ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_focus_up() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.move_focus_up(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((3, 3)), "cursor coordinates moved to the pane above" ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_focus_down() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.move_focus_up(client_id).unwrap(); tab.move_focus_down(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((5, 5)), "cursor coordinates moved to the pane below" ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_focus_with_mouse() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_left_click(&Position::new(9, 71), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(9, 71), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((35, 10)), "cursor coordinates moved to the clicked pane" ); assert_snapshot!(snapshot); } #[test] fn move_pane_focus_with_mouse_to_non_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_left_click(&Position::new(4, 71), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(4, 71), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((1, 1)), "cursor coordinates moved to the clicked pane" ); assert_snapshot!(snapshot); } #[test] fn drag_pane_with_mouse() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((75, 11)), "cursor coordinates moved to the clicked pane" ); assert_snapshot!(snapshot); } #[test] fn mark_text_inside_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_left_click(&Position::new(6, 30), client_id) .unwrap(); assert!( tab.selecting_with_mouse, "started selecting with mouse on click" ); tab.handle_left_mouse_release(&Position::new(5, 15), client_id) .unwrap(); assert!( !tab.selecting_with_mouse, "stopped selecting with mouse on release" ); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((5, 5)), "cursor coordinates stayed in clicked pane" ); assert_snapshot!(snapshot); } #[test] fn resize_tab_with_floating_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 10, }) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.resize_whole_tab(Size { cols: 50, rows: 10 }).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_back() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())) .unwrap(); tab.resize_whole_tab(Size { cols: 50, rows: 10 }).unwrap(); tab.resize_whole_tab(Size { cols: 121, rows: 20, }) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn embed_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn float_embedded_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn embed_floating_pane_without_pane_frames() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.set_pane_frames(false); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn float_embedded_pane_without_pane_frames() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.set_pane_frames(false); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn cannot_float_only_embedded_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn replacing_existing_wide_characters() { // this is a real world use case using ncmpcpp with wide characters and scrolling // the reason we don't break it down is that it exposes quite a few edge cases with wide // characters that we should handle properly let size = Size { cols: 238, rows: 48, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); let pane_content = read_fixture("ncmpcpp-wide-chars"); tab.handle_pty_bytes(1, pane_content).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn rename_embedded_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ) .unwrap(); tab.update_active_pane_name("Renamed empedded pane".as_bytes().to_vec(), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn rename_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am a floating pane".as_bytes()), ) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.update_active_pane_name("Renamed floating pane".as_bytes().to_vec(), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn wide_characters_in_left_title_side() { // this test makes sure the title doesn't overflow when it has wide characters let size = Size { cols: 238, rows: 48, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); let pane_content = read_fixture("title-wide-chars"); tab.handle_pty_bytes(1, pane_content).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn save_cursor_position_across_resizes() { // the save cursor position ANSI instruction (CSI s) needs to point to the same character after we // resize the pane let size = Size { cols: 100, rows: 5 }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\n\rI am some text\n\rI am another line of text\n\rLet's save the cursor position here \u{1b}[sI should be ovewritten".as_bytes()), ).unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 3 }).unwrap(); tab.handle_pty_bytes(1, Vec::from("\u{1b}[uthis overwrote me!".as_bytes())) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn move_floating_pane_with_sixel_image() { let new_pane_id = PaneId::Terminal(2); let size = Size { cols: 121, rows: 20, }; let client_id = 1; let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone()); let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { width: 8, height: 21, }))); let mut output = Output::new(sixel_image_store.clone(), character_cell_size); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(2, fixture).unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot_with_sixel( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), sixel_image_store, ); assert_snapshot!(snapshot); } #[test] fn floating_pane_above_sixel_image() { let new_pane_id = PaneId::Terminal(2); let size = Size { cols: 121, rows: 20, }; let client_id = 1; let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); let mut tab = create_new_tab_with_sixel_support(size, sixel_image_store.clone()); let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels { width: 8, height: 21, }))); let mut output = Output::new(sixel_image_store.clone(), character_cell_size); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(1, fixture).unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot_with_sixel( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), sixel_image_store, ); assert_snapshot!(snapshot); } #[test] fn suppress_tiled_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id).unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn suppress_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_suppressing_tiled_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id).unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes())) .unwrap(); tab.close_pane(new_pane_id, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_suppressing_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes())) .unwrap(); tab.close_pane(editor_pane_id, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn suppress_tiled_pane_float_it_and_close() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id).unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes())) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.close_pane(new_pane_id, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn suppress_floating_pane_embed_it_and_close_it() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes())) .unwrap(); tab.toggle_pane_embed_or_floating(client_id).unwrap(); tab.close_pane(editor_pane_id, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_whole_tab_while_tiled_pane_is_suppressed() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id).unwrap(); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 10, }) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn resize_whole_tab_while_floting_pane_is_suppressed() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); tab.suppress_active_pane(editor_pane_id, client_id).unwrap(); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())) .unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 10, }) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn enter_search_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mode_info = ModeInfo { mode: InputMode::Search, ..Default::default() }; let mut tab = create_new_tab(size, mode_info); let mut output = Output::default(); let pane_content = read_fixture("grid_copy"); tab.handle_pty_bytes(1, pane_content).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_tab_nothing_highlighted", snapshot); // Pane title should show 'tortor' as search term // Only lines containing 'tortor' get marked as render-targets, so // only those are updated (search-styling is not visible here). tab.update_search_term("tortor".as_bytes().to_vec(), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_tab_highlight_tortor", snapshot); // Pane title should show search modifiers tab.toggle_search_wrap(client_id); tab.toggle_search_whole_words(client_id); tab.toggle_search_case_sensitivity(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_tab_highlight_tortor_modified", snapshot); // And only the search term again tab.toggle_search_wrap(client_id); tab.toggle_search_whole_words(client_id); tab.toggle_search_case_sensitivity(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_tab_highlight_tortor", snapshot); } #[test] fn enter_search_floating_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mode_info = ModeInfo { mode: InputMode::Search, ..Default::default() }; let mut tab = create_new_tab(size, mode_info); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id, None, None, Some(client_id)) .unwrap(); let pane_content = read_fixture("grid_copy"); tab.handle_pty_bytes(2, pane_content).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_floating_tab_nothing_highlighted", snapshot); // Only the line inside the floating tab which contain 'fring' should be in the new snapshot tab.update_search_term("fring".as_bytes().to_vec(), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!("search_floating_tab_highlight_fring", snapshot); } #[test] fn pane_in_sgr_button_event_tracking_mouse_mode() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1006h"); // button event tracking (1002) with SGR encoding (1006) tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec()) .unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_left(&Position::new(9, 72), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_right_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_right(&Position::new(9, 72), client_id) .unwrap(); tab.handle_right_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_middle_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id) .unwrap(); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id) .unwrap(); pty_instruction_bus.exit(); assert_eq!( pty_instruction_bus.clone_output(), vec![ "\u{1b}[<0;71;5M".to_string(), // SGR left click "\u{1b}[<32;72;9M".to_string(), // SGR left click (hold) "\u{1b}[<0;75;7m".to_string(), // SGR left button release "\u{1b}[<2;71;5M".to_string(), // SGR right click "\u{1b}[<34;72;9M".to_string(), // SGR right click (hold) "\u{1b}[<2;75;7m".to_string(), // SGR right button release "\u{1b}[<1;71;5M".to_string(), // SGR middle click "\u{1b}[<33;72;9M".to_string(), // SGR middle click (hold) "\u{1b}[<1;75;7m".to_string(), // SGR middle button release "\u{1b}[<64;71;5M".to_string(), // SGR scroll up "\u{1b}[<65;71;5M".to_string(), // SGR scroll down ] ); } #[test] fn pane_in_sgr_normal_event_tracking_mouse_mode() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1006h"); // normal event tracking (1000) with sgr encoding (1006) tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec()) .unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_left(&Position::new(9, 72), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_right_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_right(&Position::new(9, 72), client_id) .unwrap(); tab.handle_right_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_middle_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id) .unwrap(); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id) .unwrap(); pty_instruction_bus.exit(); assert_eq!( pty_instruction_bus.clone_output(), vec![ "\u{1b}[<0;71;5M".to_string(), // SGR left click // no hold event here, as hold events are not reported in normal mode "\u{1b}[<0;75;7m".to_string(), // SGR left button release "\u{1b}[<2;71;5M".to_string(), // SGR right click // no hold event here, as hold events are not reported in normal mode "\u{1b}[<2;75;7m".to_string(), // SGR right button release "\u{1b}[<1;71;5M".to_string(), // SGR middle click // no hold event here, as hold events are not reported in normal mode "\u{1b}[<1;75;7m".to_string(), // SGR middle button release "\u{1b}[<64;71;5M".to_string(), // SGR scroll up "\u{1b}[<65;71;5M".to_string(), // SGR scroll down ] ); } #[test] fn pane_in_utf8_button_event_tracking_mouse_mode() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let sgr_mouse_mode_any_button = String::from("\u{1b}[?1002;1005h"); // button event tracking (1002) with utf8 encoding (1005) tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec()) .unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_left(&Position::new(9, 72), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_right_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_right(&Position::new(9, 72), client_id) .unwrap(); tab.handle_right_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_middle_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id) .unwrap(); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id) .unwrap(); pty_instruction_bus.exit(); assert_eq!( pty_instruction_bus.clone_output(), vec![ "\u{1b}[M g%".to_string(), // utf8 left click "\u{1b}[M@h)".to_string(), // utf8 left click (hold) "\u{1b}[M#k'".to_string(), // utf8 left button release "\u{1b}[M\"g%".to_string(), // utf8 right click "\u{1b}[MBh)".to_string(), // utf8 right click (hold) "\u{1b}[M#k'".to_string(), // utf8 right button release "\u{1b}[M!g%".to_string(), // utf8 middle click "\u{1b}[MAh)".to_string(), // utf8 middle click (hold) "\u{1b}[M#k'".to_string(), // utf8 middle click release "\u{1b}[M`g%".to_string(), // utf8 scroll up "\u{1b}[Mag%".to_string(), // utf8 scroll down ] ); } #[test] fn pane_in_utf8_normal_event_tracking_mouse_mode() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let sgr_mouse_mode_any_button = String::from("\u{1b}[?1000;1005h"); // normal event tracking (1000) with sgr encoding (1006) tab.handle_pty_bytes(1, sgr_mouse_mode_any_button.as_bytes().to_vec()) .unwrap(); tab.handle_left_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_left(&Position::new(9, 72), client_id) .unwrap(); tab.handle_left_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_right_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_right(&Position::new(9, 72), client_id) .unwrap(); tab.handle_right_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_middle_click(&Position::new(5, 71), client_id) .unwrap(); tab.handle_mouse_hold_middle(&Position::new(9, 72), client_id) .unwrap(); tab.handle_middle_mouse_release(&Position::new(7, 75), client_id) .unwrap(); tab.handle_scrollwheel_up(&Position::new(5, 71), 1, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(5, 71), 1, client_id) .unwrap(); pty_instruction_bus.exit(); assert_eq!( pty_instruction_bus.clone_output(), vec![ "\u{1b}[M g%".to_string(), // utf8 left click // no hold event here, as hold events are not reported in normal mode "\u{1b}[M#k'".to_string(), // utf8 left button release "\u{1b}[M\"g%".to_string(), // utf8 right click // no hold event here, as hold events are not reported in normal mode "\u{1b}[M#k'".to_string(), // utf8 right button release "\u{1b}[M!g%".to_string(), // utf8 middle click // no hold event here, as hold events are not reported in normal mode "\u{1b}[M#k'".to_string(), // utf8 middle click release "\u{1b}[M`g%".to_string(), // utf8 scroll up "\u{1b}[Mag%".to_string(), // utf8 scroll down ] ); } #[test] fn tab_with_basic_layout() { let layout = r#" layout { pane split_direction="Vertical" { pane pane split_direction="Horizontal" { pane pane } } } "#; let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); let mut output = Output::default(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn tab_with_layout_that_has_floating_panes() { let layout = r#" layout { pane floating_panes { pane pane } } "#; let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); let mut output = Output::default(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn tab_with_nested_layout() { let layout = r#" layout { pane_template name="top-and-vertical-sandwich" { pane vertical-sandwich { pane } } pane_template name="vertical-sandwich" split_direction="vertical" { pane children pane } pane_template name="nested-vertical-sandwich" split_direction="vertical" { pane top-and-vertical-sandwich pane } nested-vertical-sandwich } "#; let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); let mut output = Output::default(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn tab_with_nested_uneven_layout() { let layout = r#" layout { pane_template name="horizontal-with-vertical-top" { pane split_direction="Vertical" { pane children } pane } horizontal-with-vertical-top name="my tab" { pane pane } } "#; let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut tab = create_new_tab_with_layout(size, ModeInfo::default(), layout); let mut output = Output::default(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() { // regression test for: https://github.com/zellij-org/zellij/issues/1687 let size = Size { cols: 121, rows: 20, }; let client_id: u16 = 1; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201 tab.write_to_active_terminal(bracketed_paste_start, client_id) .unwrap(); tab.write_to_active_terminal("test".as_bytes().to_vec(), client_id) .unwrap(); tab.write_to_active_terminal(bracketed_paste_end, client_id) .unwrap(); pty_instruction_bus.exit(); assert_eq!(pty_instruction_bus.clone_output(), vec!["", "test", ""]); } #[test] fn pane_faux_scrolling_in_alternate_mode() { let size = Size { cols: 121, rows: 20, }; let client_id: u16 = 1; let lines_to_scroll = 3; let mut pty_instruction_bus = MockPtyInstructionBus::new(); let mut tab = create_new_tab_with_mock_pty_writer( size, ModeInfo::default(), pty_instruction_bus.pty_write_sender(), ); pty_instruction_bus.start(); let enable_alternate_screen = String::from("\u{1b}[?1049h"); // CSI ? 1049 h -> switch to the Alternate Screen Buffer let set_application_mode = String::from("\u{1b}[?1h"); // no output since alternate scren not active yet tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); tab.handle_pty_bytes(1, enable_alternate_screen.as_bytes().to_vec()) .unwrap(); // CSI A * lines_to_scroll, CSI B * lines_to_scroll tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); tab.handle_pty_bytes(1, set_application_mode.as_bytes().to_vec()) .unwrap(); // SS3 A * lines_to_scroll, SS3 B * lines_to_scroll tab.handle_scrollwheel_up(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); tab.handle_scrollwheel_down(&Position::new(1, 1), lines_to_scroll, client_id) .unwrap(); pty_instruction_bus.exit(); let mut expected: Vec<&str> = Vec::new(); expected.append(&mut vec!["\u{1b}[A"; lines_to_scroll]); expected.append(&mut vec!["\u{1b}[B"; lines_to_scroll]); expected.append(&mut vec!["\u{1b}OA"; lines_to_scroll]); expected.append(&mut vec!["\u{1b}OB"; lines_to_scroll]); assert_eq!(pty_instruction_bus.clone_output(), expected); } #[test] fn move_pane_focus_sends_tty_csi_event() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); let os_api = Box::new(FakeInputOutput { tty_stdin_bytes: tty_stdin_bytes.clone(), ..Default::default() }); let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); let new_pane_id_1 = PaneId::Terminal(2); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 1, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 2, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.move_focus_left(client_id).unwrap(); assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); } #[test] fn move_floating_pane_focus_sends_tty_csi_event() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); let os_api = Box::new(FakeInputOutput { tty_stdin_bytes: tty_stdin_bytes.clone(), ..Default::default() }); let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 1, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 2, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 3, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.move_focus_left(client_id).unwrap(); assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); } #[test] fn toggle_floating_panes_on_sends_tty_csi_event() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); let os_api = Box::new(FakeInputOutput { tty_stdin_bytes: tty_stdin_bytes.clone(), ..Default::default() }); let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.handle_pty_bytes( 1, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 2, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 3, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); } #[test] fn toggle_floating_panes_off_sends_tty_csi_event() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); let os_api = Box::new(FakeInputOutput { tty_stdin_bytes: tty_stdin_bytes.clone(), ..Default::default() }); let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.handle_pty_bytes( 1, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 2, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.handle_pty_bytes( 3, // subscribe to focus events Vec::from("\u{1b}[?1004h".as_bytes()), ) .unwrap(); tab.toggle_floating_panes(Some(client_id), None).unwrap(); assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); } #[test] fn can_swap_tiled_layout_at_runtime() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab max_panes=2 split_direction="vertical" { pane pane } } swap_tiled_layout { tab max_panes=2 { pane pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_swap_floating_layout_at_runtime() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_floating_layout { floating_panes max_panes=2 { pane pane } } swap_floating_layout { floating_panes max_panes=2 { pane { x "0%" } pane { x "100%" } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); tab.toggle_floating_panes(Some(client_id), None).unwrap(); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swapping_layouts_after_resize_snaps_to_current_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane } } swap_tiled_layout { tab { pane split_direction="vertical" { pane pane } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None)) .unwrap(); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_tiled_layout_with_stacked_children() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_tiled_layout_with_stacked_children_and_no_pane_frames() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, false, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn move_focus_up_with_stacked_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn move_focus_down_with_stacked_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.move_focus_down(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn move_focus_right_into_stacked_panes() { // here we make sure that when we focus right into a stack, // we will always focus on the "main" pane of the stack // and not on one of its folds let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); for i in 0..12 { let new_pane_id = i + 2; tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id)) .unwrap(); } tab.move_focus_left(client_id); tab.horizontal_split(PaneId::Terminal(16), None, client_id) .unwrap(); tab.move_focus_up(client_id); tab.move_focus_right(client_id); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 12)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn move_focus_left_into_stacked_panes() { // here we make sure that when we focus left into a stack, // we will always focus on the "main" pane of the stack // and not on one of its folds let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane stacked=true { children; } pane focus=true } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); for i in 0..13 { let new_pane_id = i + 2; tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id)) .unwrap(); } tab.move_focus_right(client_id); tab.horizontal_split(PaneId::Terminal(1), None, client_id) .unwrap(); tab.move_focus_up(client_id); tab.move_focus_left(client_id); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((1, 12)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn move_focus_up_into_stacked_panes() { // here we make sure that when we focus up into a stack, // the main pane will become the lowest pane and the sizes // in the stack will be adjusted accordingly let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); for i in 0..4 { let new_pane_id = i + 3; tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id)) .unwrap(); } tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.move_focus_left(client_id); tab.move_focus_down(client_id); tab.vertical_split(PaneId::Terminal(7), None, client_id) .unwrap(); tab.move_focus_up(client_id); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 9)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn move_focus_down_into_stacked_panes() { // here we make sure that when we focus down into a stack, // the main pane will become the highest pane and the sizes // in the stack will be adjusted accordingly let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); for i in 0..4 { let new_pane_id = i + 3; tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id)) .unwrap(); } tab.move_focus_left(client_id); tab.move_focus_up(client_id); tab.vertical_split(PaneId::Terminal(7), None, client_id) .unwrap(); tab.move_focus_down(client_id); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 8)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn close_main_stacked_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.close_pane(new_pane_id_2, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_main_stacked_pane_in_mid_stack() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.move_focus_up(client_id); tab.close_pane(new_pane_id_3, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_one_liner_stacked_pane_below_main_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_left(client_id); tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.move_focus_up(client_id); tab.close_pane(new_pane_id_2, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_one_liner_stacked_pane_above_main_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.move_focus_up(client_id); tab.move_focus_up(client_id); tab.close_pane(new_pane_id_1, false, None); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_of_main_pane_in_stack_horizontally() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Left)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_of_main_pane_in_stack_vertically() { let size = Size { cols: 121, rows: 40, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Down)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_of_main_pane_in_stack_non_directionally() { let size = Size { cols: 121, rows: 40, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); let _ = tab.move_focus_up(client_id); let _ = tab.move_focus_right(client_id); tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_into_pane_stack_horizontally() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Right)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_into_pane_stack_vertically() { let size = Size { cols: 121, rows: 40, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_right(client_id); tab.move_focus_down(client_id); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Up)), ) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn can_increase_size_into_pane_stack_non_directionally() { let size = Size { cols: 121, rows: 40, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); let _ = tab.move_focus_up(client_id); tab.resize(client_id, ResizeStrategy::new(Resize::Increase, None)) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn decreasing_size_of_whole_tab_treats_stacked_panes_properly() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 10, }); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn increasing_size_of_whole_tab_treats_stacked_panes_properly() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.resize_whole_tab(Size { cols: 100, rows: 10, }); tab.resize_whole_tab(Size { cols: 121, rows: 20, }); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn cannot_decrease_stack_size_beyond_minimum_height() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_down(client_id); for _ in 0..6 { tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Up)), ) .unwrap(); } tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn focus_stacked_pane_over_flexible_pane_with_the_mouse() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_left_click(&Position::new(1, 71), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn focus_stacked_pane_under_flexible_pane_with_the_mouse() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_left_click(&Position::new(1, 71), client_id) .unwrap(); tab.handle_left_click(&Position::new(9, 71), client_id) .unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn close_stacked_pane_with_previously_focused_other_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.handle_left_click(&Position::new(2, 71), client_id) .unwrap(); tab.handle_left_click(&Position::new(1, 71), client_id) .unwrap(); tab.close_pane(PaneId::Terminal(4), false, None); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 2)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn close_pane_near_stacked_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane pane stacked=true { children; } } } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.close_pane(PaneId::Terminal(6), false, None); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 4)), "cursor coordinates moved to the main pane of the stack", ); assert_snapshot!(snapshot); } #[test] fn focus_next_pane_expands_stacked_panes() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_left(client_id); tab.focus_next_pane(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn stacked_panes_can_become_fullscreen() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane stacked=true { children; } } pane } } } "#; let layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = layout.swap_tiled_layouts.clone(); let swap_floating_layouts = layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), None, true, ); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); let new_pane_id_4 = PaneId::Terminal(5); let new_pane_id_5 = PaneId::Terminal(6); tab.new_pane(new_pane_id_1, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_2, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_3, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_4, None, None, Some(client_id)) .unwrap(); tab.new_pane(new_pane_id_5, None, None, Some(client_id)) .unwrap(); tab.move_focus_up(client_id); tab.toggle_active_pane_fullscreen(client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn layout_with_plugins_and_commands_swaped_properly() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane command="command1" pane pane command="command2" } pane size=2 borderless=true { plugin location="zellij:status-bar" } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 borderless=true { plugin location="zellij:status-bar" } pane command="command2" pane command="command1" pane pane size=1 borderless=true { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_floating_terminal_ids = vec![]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn base_layout_is_included_in_swap_layouts() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane command="command1" pane pane command="command2" } pane size=2 borderless=true { plugin location="zellij:status-bar" } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 borderless=true { plugin location="zellij:status-bar" } pane command="command2" pane command="command1" pane pane size=1 borderless=true { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_floating_terminal_ids = vec![]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.previous_swap_layout(Some(client_id)).unwrap(); // move back to the base layout tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_layouts_including_command_panes_absent_from_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane pane pane } pane size=2 borderless=true { plugin location="zellij:status-bar" } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 borderless=true { plugin location="zellij:status-bar" } pane command="command2" pane command="command1" pane pane size=1 borderless=true { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_layouts_not_including_command_panes_present_in_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane command="command1" pane pane command="command2" } pane size=2 borderless=true { plugin location="zellij:status-bar" } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 borderless=true { plugin location="zellij:status-bar" } pane pane pane pane size=1 borderless=true { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_floating_terminal_ids = vec![]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_layouts_including_plugin_panes_absent_from_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=2 borderless=true pane split_direction="vertical" { pane pane pane } pane size=1 borderless=true } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 borderless=true { plugin location="zellij:status-bar" } pane pane pane pane size=1 borderless=true { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_layouts_not_including_plugin_panes_present_in_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane size=1 borderless=true { plugin location="zellij:tab-bar" } pane split_direction="vertical" { pane command="command1" pane pane command="command2" } pane size=2 borderless=true { plugin location="zellij:status-bar" } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_tiled_layout { tab { pane size=2 pane pane pane pane size=1 } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_floating_terminal_ids = vec![]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn new_pane_in_auto_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout "#; let swap_layouts = r#" layout { swap_tiled_layout { tab max_panes=5 { pane split_direction="vertical" { pane pane { children; } } } tab max_panes=8 { pane split_direction="vertical" { pane { children; } pane { pane; pane; pane; pane; } } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let mut expected_cursor_coordinates = vec![ (62, 1), (62, 11), (62, 15), (62, 16), (1, 11), (1, 15), (1, 16), ]; for i in 0..7 { let new_pane_id = i + 2; tab.new_pane(PaneId::Terminal(new_pane_id), None, None, Some(client_id)) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); let (expected_x, expected_y) = expected_cursor_coordinates.remove(0); assert_eq!( cursor_coordinates, Some((expected_x, expected_y)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } } #[test] fn when_swapping_tiled_layouts_in_a_damaged_state_layout_and_pane_focus_are_unchanged() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane pane pane } "#; let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane pane pane } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.move_focus_down(client_id); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Down)), ) .unwrap(); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((1, 8)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_swapping_tiled_layouts_in_an_undamaged_state_pane_focuses_on_focused_node() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane pane pane } "#; let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane focus=true pane pane } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.move_focus_down(client_id); tab.next_swap_layout(Some(client_id), true).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((1, 1)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_swapping_tiled_layouts_in_an_undamaged_state_with_no_focus_node_pane_focuses_on_deepest_node( ) { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane pane pane } "#; let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane pane pane } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.move_focus_down(client_id); tab.next_swap_layout(Some(client_id), true).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((82, 1)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_closing_a_pane_in_auto_layout_the_focus_goes_to_last_focused_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { pane pane pane } "#; let swap_layouts = r#" layout { swap_tiled_layout { tab { pane split_direction="vertical" { pane pane pane } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.move_focus_down(client_id); let _ = tab.move_focus_down(client_id); tab.close_pane(PaneId::Terminal(3), false, Some(client_id)); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((62, 1)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn floating_layout_with_plugins_and_commands_swaped_properly() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane x=0 y=0 { plugin location="zellij:tab-bar" } pane x=0 y=10 command="command1" pane pane x=50 y=10 command="command2" pane x=50 y=0 { plugin location="zellij:status-bar" } } } "#; // this swap layout swaps between the location of the plugins and the commands let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane x=0 y=0 { plugin location="zellij:status-bar" } pane x=0 y=10 command="command2" pane pane x=50 y=10 command="command1" pane x=50 y=0 { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_terminal_ids = vec![(4, None)]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn base_floating_layout_is_included_in_swap_layouts() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane x=0 y=0 { plugin location="zellij:tab-bar" } pane x=0 y=10 command="command1" pane pane x=50 y=10 command="command2" pane x=50 y=0 { plugin location="zellij:status-bar" } } } "#; // this swap layout swaps between the location of the plugins and the commands let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane x=0 y=0 { plugin location="zellij:status-bar" } pane x=0 y=10 command="command2" pane pane x=50 y=10 command="command1" pane x=50 y=0 { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_terminal_ids = vec![(4, None)]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.previous_swap_layout(Some(client_id)).unwrap(); // move back to the base layout tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_floating_layouts_including_command_panes_absent_from_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane { plugin location="zellij:tab-bar" } pane pane pane pane { plugin location="zellij:status-bar" } } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane { plugin location="zellij:status-bar" } pane x=0 y=0 command="command1" pane x=10 y=10 command="command2" pane pane { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_floating_layouts_not_including_command_panes_present_in_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane { plugin location="zellij:tab-bar" } pane command="command1" pane pane command="command2" pane { plugin location="zellij:status-bar" } } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane { plugin location="zellij:status-bar" } pane pane pane pane { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_terminal_ids = vec![(4, None)]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_floating_layouts_including_plugin_panes_absent_from_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane pane pane } } "#; let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane { plugin location="zellij:status-bar" } pane pane { plugin location="zellij:tab-bar" } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn swap_floating_layouts_not_including_plugin_panes_present_in_existing_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane { plugin location="zellij:tab-bar" } pane pane { plugin location="zellij:status-bar" } } } "#; // this swap layout changes both the split direction of the two command panes and the location // of the plugins - we want to make sure that they are all placed properly and not switched // around let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane pane pane } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let mut command_1 = RunCommand::default(); command_1.command = PathBuf::from("command1"); let mut command_2 = RunCommand::default(); command_2.command = PathBuf::from("command2"); let new_floating_terminal_ids = vec![(1, Some(command_1)), (2, None), (3, Some(command_2))]; let new_terminal_ids = vec![(4, None)]; let mut new_plugin_ids = HashMap::new(); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("tab-bar")), vec![1], ); new_plugin_ids.insert( RunPluginLocation::Zellij(PluginTag::new("status-bar")), vec![2], ); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let _ = tab.handle_plugin_bytes(1, 1, "I am a tab bar".as_bytes().to_vec()); let _ = tab.handle_plugin_bytes(2, 1, "I am a\n\rstatus bar".as_bytes().to_vec()); tab.next_swap_layout(Some(client_id), false).unwrap(); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); } #[test] fn new_floating_pane_in_auto_layout() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout "#; let swap_layouts = r#" layout { swap_floating_layout name="spread" { floating_panes max_panes=1 { pane {y "50%"; x "50%"; } } floating_panes max_panes=2 { pane { x "1%"; y "25%"; width "45%"; } pane { x "50%"; y "25%"; width "45%"; } } floating_panes max_panes=3 { pane focus=true { y "55%"; width "45%"; height "45%"; } pane { x "1%"; y "1%"; width "45%"; } pane { x "50%"; y "1%"; width "45%"; } } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_terminal_ids = vec![(1, None)]; let new_floating_terminal_ids = vec![]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let mut expected_cursor_coordinates = vec![(62, 11), (62, 6), (31, 12)]; for i in 0..3 { let new_pane_id = i + 2; let should_float = true; tab.new_pane( PaneId::Terminal(new_pane_id), None, Some(should_float), Some(client_id), ) .unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); let (expected_x, expected_y) = expected_cursor_coordinates.remove(0); assert_eq!( cursor_coordinates, Some((expected_x, expected_y)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } } #[test] fn when_swapping_floating_layouts_in_a_damaged_state_layout_and_pane_focus_are_unchanged() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane x=0 y=0 pane pane } } "#; let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane pane pane } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.resize( client_id, ResizeStrategy::new(Resize::Increase, Some(Direction::Down)), ) .unwrap(); tab.next_swap_layout(Some(client_id), true).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((33, 8)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_swapping_floating_layouts_in_an_undamaged_state_pane_focuses_on_focused_node() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane x=0 y=0 pane pane } } "#; let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane focus=true pane pane } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.next_swap_layout(Some(client_id), true).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((31, 6)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_swapping_floating_layouts_in_an_undamaged_state_with_no_focus_node_pane_focuses_on_deepest_node( ) { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane focus=true pane pane } } "#; let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane pane pane } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.next_swap_layout(Some(client_id), true).unwrap(); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((35, 10)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_closing_a_floating_pane_in_auto_layout_the_focus_goes_to_last_focused_pane() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane pane pane } } "#; let swap_layouts = r#" layout { swap_floating_layout { floating_panes { pane pane pane } } } "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); tab.move_focus_up(client_id); tab.move_focus_up(client_id); tab.close_pane(PaneId::Terminal(1), false, Some(client_id)); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((31, 6)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); } #[test] fn when_resizing_whole_tab_with_auto_layout_and_floating_panes_the_layout_is_maintained() { let size = Size { cols: 121, rows: 20, }; let client_id = 1; let mut output = Output::default(); let base_layout = r#" layout { floating_panes { pane pane pane } } "#; let swap_layouts = r#" layout "#; let (base_layout, base_floating_layout) = Layout::from_kdl(base_layout, "file_name.kdl".into(), None, None) .unwrap() .template .unwrap(); let new_floating_terminal_ids = vec![(1, None), (2, None), (3, None)]; let new_terminal_ids = vec![(4, None)]; let new_plugin_ids = HashMap::new(); let swap_layout = Layout::from_kdl(swap_layouts, "file_name.kdl".into(), None, None).unwrap(); let swap_tiled_layouts = swap_layout.swap_tiled_layouts.clone(); let swap_floating_layouts = swap_layout.swap_floating_layouts.clone(); let mut tab = create_new_tab_with_swap_layouts( size, ModeInfo::default(), (swap_tiled_layouts, swap_floating_layouts), Some(( base_layout, base_floating_layout, new_terminal_ids, new_floating_terminal_ids, new_plugin_ids, )), true, ); let new_size = Size { cols: 150, rows: 30, }; tab.resize_whole_tab(new_size); tab.render(&mut output, None).unwrap(); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().unwrap().get(&client_id).unwrap(), new_size.rows, new_size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((43, 13)), "cursor coordinates moved to the new pane", ); assert_snapshot!(snapshot); }