use super::{Screen, ScreenInstruction}; use crate::panes::PaneId; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ os_input_output::{AsyncReader, Pid, ServerOsApi}, thread_bus::Bus, ClientId, }; use std::convert::TryInto; use std::path::PathBuf; use zellij_utils::input::command::TerminalAction; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::input::options::Clipboard; use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::{Size, SizeInPixels}; use std::os::unix::io::RawFd; use zellij_utils::ipc::{ClientAttributes, PixelDimensions}; use zellij_utils::nix; use zellij_utils::{ interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, }; #[derive(Clone)] struct FakeInputOutput {} 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_db: Box, ) -> (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, _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 create_new_screen(size: Size) -> Screen { let mut bus: Bus = Bus::empty(); let fake_os_input = FakeInputOutput {}; bus.os_input = Some(Box::new(fake_os_input)); let client_attributes = ClientAttributes { size, ..Default::default() }; let max_panes = None; let mode_info = ModeInfo::default(); let draw_pane_frames = false; let session_is_mirrored = true; let copy_command = None; let copy_clipboard = Clipboard::default(); Screen::new( bus, &client_attributes, max_panes, mode_info, draw_pane_frames, session_is_mirrored, copy_command, copy_clipboard, ) } fn new_tab(screen: &mut Screen, pid: i32) { let client_id = 1; screen.new_tab( LayoutTemplate::default().try_into().unwrap(), vec![pid], client_id, ); } #[test] fn open_new_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); assert_eq!(screen.tabs.len(), 2, "Screen now has two tabs"); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to new tab" ); } #[test] pub fn switch_to_prev_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); screen.switch_tab_prev(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous tab" ); } #[test] pub fn switch_to_next_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); screen.switch_tab_prev(1); screen.switch_tab_next(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to next tab" ); } #[test] pub fn close_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); screen.close_tab(1); assert_eq!(screen.tabs.len(), 1, "Only one tab left"); assert_eq!( screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous tab" ); } #[test] pub fn close_the_middle_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); screen.switch_tab_prev(1); screen.close_tab(1); assert_eq!(screen.tabs.len(), 2, "Two tabs left"); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to previous tab" ); } #[test] fn move_focus_left_at_left_screen_edge_changes_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); screen.switch_tab_prev(1); screen.move_focus_left_or_previous_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous" ); } #[test] fn move_focus_right_at_right_screen_edge_changes_tab() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); screen.switch_tab_prev(1); screen.move_focus_right_or_next_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 2, "Active tab switched to next" ); } #[test] pub fn toggle_to_previous_tab_simple() { let position_and_size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(position_and_size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); screen.go_to_tab(1, 1); screen.go_to_tab(2, 1); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 0, "Active tab toggler to previous tab" ); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); } #[test] pub fn toggle_to_previous_tab_create_tabs_only() { let position_and_size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(position_and_size); new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1], "Tab history is invalid" ); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 2], "Tab history is invalid" ); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1], "Tab history is invalid" ); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); } #[test] pub fn toggle_to_previous_tab_delete() { let position_and_size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(position_and_size); new_tab(&mut screen, 1); // 0 new_tab(&mut screen, 2); // 1 new_tab(&mut screen, 3); // 2 new_tab(&mut screen, 4); // 3 assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1, 2], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 3, "Active tab toggler to previous tab" ); screen.toggle_tab(1); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1, 3], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); screen.toggle_tab(1); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1, 2], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 3, "Active tab toggler to previous tab" ); screen.switch_tab_prev(1); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 1, 3], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); screen.switch_tab_prev(1); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 3, 2], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); screen.close_tab(1); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 3], "Tab history is invalid" ); assert_eq!( screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); screen.toggle_tab(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); assert_eq!( screen.tab_history.get(&1).unwrap(), &[0, 2], "Tab history is invalid" ); } #[test] fn switch_to_tab_with_fullscreen() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); new_tab(&mut screen, 1); { let active_tab = screen.get_active_tab_mut(1).unwrap(); active_tab.new_pane(PaneId::Terminal(2), Some(1)); active_tab.toggle_active_pane_fullscreen(1); } new_tab(&mut screen, 2); screen.switch_tab_prev(1); assert_eq!( screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous" ); assert_eq!( screen .get_active_tab(1) .unwrap() .get_active_pane_id(1) .unwrap(), PaneId::Terminal(2), "Active pane is still the fullscreen pane" ); } #[test] fn update_screen_pixel_dimensions() { let size = Size { cols: 121, rows: 20, }; let mut screen = create_new_screen(size); let initial_pixel_dimensions = screen.pixel_dimensions; screen.update_pixel_dimensions(PixelDimensions { character_cell_size: Some(SizeInPixels { height: 10, width: 5, }), text_area_size: None, }); let pixel_dimensions_after_first_update = screen.pixel_dimensions; screen.update_pixel_dimensions(PixelDimensions { character_cell_size: None, text_area_size: Some(SizeInPixels { height: 100, width: 50, }), }); let pixel_dimensions_after_second_update = screen.pixel_dimensions; screen.update_pixel_dimensions(PixelDimensions { character_cell_size: None, text_area_size: None, }); let pixel_dimensions_after_third_update = screen.pixel_dimensions; assert_eq!( initial_pixel_dimensions, PixelDimensions { character_cell_size: None, text_area_size: None }, "Initial pixel dimensions empty" ); assert_eq!( pixel_dimensions_after_first_update, PixelDimensions { character_cell_size: Some(SizeInPixels { height: 10, width: 5 }), text_area_size: None }, "character_cell_size updated properly", ); assert_eq!( pixel_dimensions_after_second_update, PixelDimensions { character_cell_size: Some(SizeInPixels { height: 10, width: 5 }), text_area_size: Some(SizeInPixels { height: 100, width: 50, }), }, "text_area_size updated properly without overriding character_cell_size", ); assert_eq!( pixel_dimensions_after_third_update, PixelDimensions { character_cell_size: Some(SizeInPixels { height: 10, width: 5 }), text_area_size: Some(SizeInPixels { height: 100, width: 50, }), }, "empty update does not delete existing data", ); }