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, thread_bus::ThreadSenders, ClientId, }; use std::convert::TryInto; use std::path::PathBuf; use zellij_utils::envs::set_session_name; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::position::Position; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; use zellij_utils::nix; use zellij_utils::{ data::{ModeInfo, Palette, Style}, input::command::TerminalAction, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, }; #[derive(Clone)] struct FakeInputOutput { file_dumps: Arc>>, } impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } fn spawn_terminal( &self, _file_to_open: TerminalAction, _quit_cb: Box, _default_editor: Option, ) -> Result<(RawFd, RawFd), &'static str> { 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, _fd: RawFd, _buf: &[u8]) -> Result { unimplemented!() } fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { unimplemented!() } fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } fn box_clone(&self) -> Box { Box::new((*self).clone()) } fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) { unimplemented!() } fn new_client( &mut self, _client_id: ClientId, _stream: LocalSocketStream, ) -> IpcReceiverWithContext { unimplemented!() } fn remove_client(&mut self, _client_id: ClientId) { 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) { let f: String = match name { Some(x) => x, None => "tmp-name".to_owned(), }; self.file_dumps.lock().unwrap().insert(f, buf); } } // TODO: move to shared thingy with other test file fn create_new_tab(size: Size) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; let name = String::new(); let os_api = Box::new(FakeInputOutput { file_dumps: Arc::new(Mutex::new(HashMap::new())), }); 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 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, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), vec![1], index, client_id, ); 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 { file_dumps: Arc::new(Mutex::new(HashMap::new())), }); 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 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, connected_clients, session_is_mirrored, client_id, copy_options, terminal_emulator_colors, terminal_emulator_color_codes, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), vec![1], index, client_id, ); 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); let map = Arc::new(Mutex::new(HashMap::new())); tab.os_api = Box::new(FakeInputOutput { file_dumps: map.clone(), }); let new_pane_id = PaneId::Terminal(2); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes(2, Vec::from("scratch".as_bytes())); let file = "/tmp/log.sh"; tab.dump_active_terminal_screen(Some(file.to_string()), client_id); 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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.toggle_floating_panes(client_id, None); // 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()), ); tab.toggle_floating_panes(client_id, None); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.toggle_floating_panes(client_id, None); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.toggle_floating_panes(client_id, None); tab.toggle_floating_panes(client_id, None); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_increase(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_decrease(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_left(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_right(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_up(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.resize_down(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.move_focus_left(client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((71, 9)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.move_focus_left(client_id); tab.move_focus_right(client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((80, 3)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.move_focus_up(client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((71, 9)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.move_focus_up(client_id); tab.move_focus_down(client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((80, 13)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.handle_left_click(&Position::new(9, 71), client_id); tab.handle_mouse_release(&Position::new(9, 71), client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((71, 9)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.handle_left_click(&Position::new(4, 71), client_id); tab.handle_mouse_release(&Position::new(4, 71), client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.handle_left_click(&Position::new(5, 71), client_id); tab.handle_mouse_release(&Position::new(7, 75), client_id); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.handle_left_click(&Position::new(9, 71), client_id); assert!( tab.selecting_with_mouse, "started selecting with mouse on click" ); tab.handle_mouse_release(&Position::new(8, 50), client_id); assert!( !tab.selecting_with_mouse, "stopped selecting with mouse on release" ); tab.render(&mut output, None); let (snapshot, cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_eq!( cursor_coordinates, Some((71, 9)), "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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.resize_whole_tab(Size { cols: 100, rows: 10, }); tab.render(&mut output, None); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.resize_whole_tab(Size { cols: 50, rows: 10 }); tab.render(&mut output, None); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().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); 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(client_id, None); tab.new_pane(new_pane_id_1, Some(client_id)); tab.new_pane(new_pane_id_2, Some(client_id)); tab.new_pane(new_pane_id_3, Some(client_id)); tab.new_pane(new_pane_id_4, Some(client_id)); tab.new_pane(new_pane_id_5, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.handle_pty_bytes(3, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(4, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(5, Vec::from("\u{1b}#8".as_bytes())); tab.handle_pty_bytes(6, Vec::from("\u{1b}#8".as_bytes())); tab.resize_whole_tab(Size { cols: 50, rows: 10 }); tab.resize_whole_tab(Size { cols: 121, rows: 20, }); tab.render(&mut output, None); let (snapshot, _cursor_coordinates) = take_snapshot_and_cursor_position( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am scratch terminal".as_bytes()), ); tab.toggle_pane_embed_or_floating(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ); tab.toggle_pane_embed_or_floating(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ); tab.toggle_pane_embed_or_floating(client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let mut output = Output::default(); let pane_content = read_fixture("ncmpcpp-wide-chars"); tab.handle_pty_bytes(1, pane_content); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\n\n I am an embedded pane".as_bytes()), ); tab.update_active_pane_name("Renamed empedded pane".as_bytes().to_vec(), client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, Some(client_id)); tab.handle_pty_bytes( 2, Vec::from("\n\n\n I am a floating pane".as_bytes()), ); tab.toggle_pane_embed_or_floating(client_id); tab.update_active_pane_name("Renamed floating pane".as_bytes().to_vec(), client_id); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let mut output = Output::default(); let pane_content = read_fixture("title-wide-chars"); tab.handle_pty_bytes(1, pane_content); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let mut output = Output::default(); tab.handle_pty_bytes( 1, Vec::from("\n\nI am some text\nI am another line of text\nLet's save the cursor position here \u{1b}[sI should be ovewritten".as_bytes()), ); tab.resize_whole_tab(Size { cols: 100, rows: 3 }); tab.handle_pty_bytes(1, Vec::from("\u{1b}[uthis overwrote me!".as_bytes())); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(2, fixture); tab.handle_left_click(&Position::new(5, 71), client_id); tab.handle_mouse_release(&Position::new(7, 75), client_id); tab.render(&mut output, None); let snapshot = take_snapshot_with_sixel( output.serialize().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(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); let fixture = read_fixture("sixel-image-500px.six"); tab.handle_pty_bytes(1, fixture); tab.handle_left_click(&Position::new(5, 71), client_id); tab.handle_mouse_release(&Position::new(7, 75), client_id); tab.render(&mut output, None); let snapshot = take_snapshot_with_sixel( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.suppress_active_pane(editor_pane_id, client_id); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes())); tab.close_pane(new_pane_id, false); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.suppress_active_pane(editor_pane_id, client_id); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes())); tab.close_pane(editor_pane_id, false); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.handle_pty_bytes(1, Vec::from("\n\n\nI am the original pane".as_bytes())); tab.toggle_pane_embed_or_floating(client_id); tab.close_pane(new_pane_id, false); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.suppress_active_pane(editor_pane_id, client_id); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am the original pane".as_bytes())); tab.toggle_pane_embed_or_floating(client_id); tab.close_pane(editor_pane_id, false); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); tab.handle_pty_bytes(2, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.resize_whole_tab(Size { cols: 100, rows: 10, }); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().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); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); tab.new_pane(new_pane_id, Some(client_id)); tab.suppress_active_pane(editor_pane_id, client_id); tab.handle_pty_bytes(3, Vec::from("\n\n\nI am an editor pane".as_bytes())); tab.resize_whole_tab(Size { cols: 100, rows: 10, }); tab.render(&mut output, None); let snapshot = take_snapshot( output.serialize().get(&client_id).unwrap(), size.rows, size.cols, Palette::default(), ); assert_snapshot!(snapshot); }