diff options
Diffstat (limited to 'zellij-server')
-rw-r--r-- | zellij-server/src/route.rs | 10 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 100 | ||||
-rw-r--r-- | zellij-server/src/unit/screen_tests.rs | 144 |
3 files changed, 254 insertions, 0 deletions
diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index b62bcd8ae..c8a7f4194 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -540,6 +540,16 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::UndoRenameTab(client_id)) .with_context(err_context)?; }, + Action::MoveTab(direction) => { + let screen_instr = match direction { + Direction::Left => ScreenInstruction::MoveTabLeft(client_id), + Direction::Right => ScreenInstruction::MoveTabRight(client_id), + _ => return Ok(false), + }; + senders + .send_to_screen(screen_instr) + .with_context(err_context)?; + }, Action::Quit => { senders .send_to_server(ServerInstruction::ClientExit(client_id)) diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 2e351cff4..4594fe3cb 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -7,6 +7,7 @@ use std::rc::Rc; use std::str; use std::time::Duration; +use log::{debug, warn}; use zellij_utils::data::{ Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo, }; @@ -238,6 +239,8 @@ pub enum ScreenInstruction { ToggleTab(ClientId), UpdateTabName(Vec<u8>, ClientId), UndoRenameTab(ClientId), + MoveTabLeft(ClientId), + MoveTabRight(ClientId), TerminalResize(Size), TerminalPixelDimensions(PixelDimensions), TerminalBackgroundColor(String), @@ -447,6 +450,8 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::GoToTabName(..) => ScreenContext::GoToTabName, ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName, ScreenInstruction::UndoRenameTab(..) => ScreenContext::UndoRenameTab, + ScreenInstruction::MoveTabLeft(..) => ScreenContext::MoveTabLeft, + ScreenInstruction::MoveTabRight(..) => ScreenContext::MoveTabRight, ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize, ScreenInstruction::TerminalPixelDimensions(..) => { ScreenContext::TerminalPixelDimensions @@ -1585,6 +1590,91 @@ impl Screen { } } + pub fn move_active_tab_to_left(&mut self, client_id: ClientId) -> Result<()> { + if self.tabs.len() < 2 { + debug!("cannot move tab to left: only one tab exists"); + return Ok(()); + } + let Some(client_id) = self.client_id(client_id) else { + return Ok(()); + }; + let Some(&active_tab_idx) = self.active_tab_indices.get(&client_id) else { + return Ok(()); + }; + + // wraps around: [tab1, tab2, tab3] => [tab1, tab2, tab3] + // ^ ^ + // active_tab_idx left_tab_idx + let left_tab_idx = (active_tab_idx + self.tabs.len() - 1) % self.tabs.len(); + + self.switch_tabs(active_tab_idx, left_tab_idx, client_id); + self.log_and_report_session_state() + .context("failed to move tab to left")?; + Ok(()) + } + + fn client_id(&mut self, client_id: ClientId) -> Option<u16> { + if self.get_active_tab(client_id).is_ok() { + Some(client_id) + } else { + self.get_first_client_id() + } + } + + fn switch_tabs(&mut self, active_tab_idx: usize, other_tab_idx: usize, client_id: u16) { + if !self.tabs.contains_key(&active_tab_idx) || !self.tabs.contains_key(&other_tab_idx) { + warn!( + "failed to switch tabs: index {} or {} not found in {:?}", + active_tab_idx, + other_tab_idx, + self.tabs.keys() + ); + return; + } + + // NOTE: Can `expect` here, because we checked that the keys exist above + let mut active_tab = self + .tabs + .remove(&active_tab_idx) + .expect("active tab not found"); + let mut other_tab = self + .tabs + .remove(&other_tab_idx) + .expect("other tab not found"); + + std::mem::swap(&mut active_tab.index, &mut other_tab.index); + std::mem::swap(&mut active_tab.position, &mut other_tab.position); + + // now, `active_tab.index` is changed, so we need to update it + self.active_tab_indices.insert(client_id, active_tab.index); + + self.tabs.insert(active_tab.index, active_tab); + self.tabs.insert(other_tab.index, other_tab); + } + + pub fn move_active_tab_to_right(&mut self, client_id: ClientId) -> Result<()> { + if self.tabs.len() < 2 { + debug!("cannot move tab to right: only one tab exists"); + return Ok(()); + } + let Some(client_id) = self.client_id(client_id) else { + return Ok(()); + }; + let Some(&active_tab_idx) = self.active_tab_indices.get(&client_id) else { + return Ok(()); + }; + + // wraps around: [tab1, tab2, tab3] => [tab1, tab2, tab3] + // ^ ^ + // active_tab_idx right_tab_idx + let right_tab_idx = (active_tab_idx + 1) % self.tabs.len(); + + self.switch_tabs(active_tab_idx, right_tab_idx, client_id); + self.log_and_report_session_state() + .context("failed to move active tab to right")?; + Ok(()) + } + pub fn change_mode(&mut self, mut mode_info: ModeInfo, client_id: ClientId) -> Result<()> { if mode_info.session_name.as_ref() != Some(&self.session_name) { mode_info.session_name = Some(self.session_name.clone()); @@ -2989,6 +3079,16 @@ pub(crate) fn screen_thread_main( screen.unblock_input()?; screen.render(None)?; }, + ScreenInstruction::MoveTabLeft(client_id) => { + screen.move_active_tab_to_left(client_id)?; + screen.unblock_input()?; + screen.render(None)?; + }, + ScreenInstruction::MoveTabRight(client_id) => { + screen.move_active_tab_to_right(client_id)?; + screen.unblock_input()?; + screen.render(None)?; + }, ScreenInstruction::TerminalResize(new_size) => { screen.resize_to_screen(new_size)?; screen.log_and_report_session_state()?; // update tabs so that the ui indication will be send to the plugins diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 87935313c..64b1e188f 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -796,6 +796,150 @@ fn move_focus_left_at_left_screen_edge_changes_tab() { } #[test] +fn basic_move_of_active_tab_to_left() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + assert_eq!(screen.get_active_tab(1).unwrap().position, 1); + + screen.move_active_tab_to_left(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 0, + "Active tab moved to left" + ); +} + +fn create_fixed_size_screen() -> Screen { + create_new_screen(Size { + cols: 121, + rows: 20, + }) +} + +#[test] +fn move_of_active_tab_to_left_when_there_is_only_one_tab() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + assert_eq!(screen.get_active_tab(1).unwrap().position, 0); + + screen.move_active_tab_to_left(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 0, + "Active tab moved to left" + ); +} + +#[test] +fn move_of_active_tab_to_left_multiple_times() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + new_tab(&mut screen, 3, 2); + assert_eq!(screen.get_active_tab(1).unwrap().position, 2); + + screen.move_active_tab_to_left(1).expect("TEST"); + screen.move_active_tab_to_left(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 0, + "Active tab moved to left twice" + ); +} + +#[test] +fn wrapping_move_of_active_tab_to_left() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + new_tab(&mut screen, 3, 2); + screen.move_focus_left_or_previous_tab(1).expect("TEST"); + screen.move_focus_left_or_previous_tab(1).expect("TEST"); + assert_eq!(screen.get_active_tab(1).unwrap().position, 0); + + screen.move_active_tab_to_left(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 2, + "Active tab moved to left until wrapped around" + ); +} + +#[test] +fn basic_move_of_active_tab_to_right() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + screen.move_focus_left_or_previous_tab(1).expect("TEST"); + assert_eq!(screen.get_active_tab(1).unwrap().position, 0); + + screen.move_active_tab_to_right(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 1, + "Active tab moved to right" + ); +} + +#[test] +fn move_of_active_tab_to_right_when_there_is_only_one_tab() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + assert_eq!(screen.get_active_tab(1).unwrap().position, 0); + + screen.move_active_tab_to_right(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 0, + "Active tab moved to left" + ); +} + +#[test] +fn move_of_active_tab_to_right_multiple_times() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + new_tab(&mut screen, 3, 2); + screen.move_focus_left_or_previous_tab(1).expect("TEST"); + screen.move_focus_left_or_previous_tab(1).expect("TEST"); + assert_eq!(screen.get_active_tab(1).unwrap().position, 0); + + screen.move_active_tab_to_right(1).expect("TEST"); + screen.move_active_tab_to_right(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 2, + "Active tab moved to right twice" + ); +} + +#[test] +fn wrapping_move_of_active_tab_to_right() { + let mut screen = create_fixed_size_screen(); + new_tab(&mut screen, 1, 0); + new_tab(&mut screen, 2, 1); + new_tab(&mut screen, 3, 2); + assert_eq!(screen.get_active_tab(1).unwrap().position, 2); + + screen.move_active_tab_to_right(1).expect("TEST"); + + assert_eq!( + screen.get_active_tab(1).unwrap().position, + 0, + "Active tab moved to right until wrapped around" + ); +} + +#[test] fn move_focus_right_at_right_screen_edge_changes_tab() { let size = Size { cols: 121, |