summaryrefslogtreecommitdiffstats
path: root/zellij-server
diff options
context:
space:
mode:
Diffstat (limited to 'zellij-server')
-rw-r--r--zellij-server/src/route.rs10
-rw-r--r--zellij-server/src/screen.rs100
-rw-r--r--zellij-server/src/unit/screen_tests.rs144
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,