diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index eb711fa3a0..e764786c44 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -307,6 +307,7 @@ impl InputHandler { | Action::GoToPreviousTab | Action::CloseTab | Action::GoToTab(_) + | Action::GoToTabName(_, _) | Action::ToggleTab | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 2ca58cc448..dce1c9ac0f 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -479,6 +479,16 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::GoToTab(i, Some(client_id))) .with_context(err_context)?; }, + Action::GoToTabName(name, create) => { + session + .senders + .send_to_screen(ScreenInstruction::GoToTabName( + name, + create, + Some(client_id), + )) + .with_context(err_context)?; + }, Action::TabNameInput(c) => { session .senders diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index fdca620d91..9a196f35db 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -196,6 +196,7 @@ pub enum ScreenInstruction { ToggleActiveSyncTab(ClientId), CloseTab(ClientId), GoToTab(u32, Option), // this Option is a hacky workaround, please do not copy this behaviour + GoToTabName(String, bool, Option), ToggleTab(ClientId), UpdateTabName(Vec, ClientId), UndoRenameTab(ClientId), @@ -317,6 +318,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::SwitchTabPrev(..) => ScreenContext::SwitchTabPrev, ScreenInstruction::CloseTab(..) => ScreenContext::CloseTab, ScreenInstruction::GoToTab(..) => ScreenContext::GoToTab, + ScreenInstruction::GoToTabName(..) => ScreenContext::GoToTabName, ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName, ScreenInstruction::UndoRenameTab(..) => ScreenContext::UndoRenameTab, ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize, @@ -623,6 +625,18 @@ impl Screen { Ok(()) } + /// A helper function to switch to a new tab with specified name. Return true if tab [name] has + /// been created, else false. + fn switch_active_tab_name(&mut self, name: String, client_id: ClientId) -> Result { + match self.tabs.values().find(|t| t.name == name) { + Some(new_tab) => { + self.switch_active_tab(new_tab.position, client_id)?; + Ok(true) + }, + None => Ok(false), + } + } + /// Sets this [`Screen`]'s active [`Tab`] to the next tab. pub fn switch_tab_next(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to switch to next tab for client {client_id}"); @@ -678,6 +692,10 @@ impl Screen { self.switch_active_tab(tab_index.saturating_sub(1), client_id) } + pub fn go_to_tab_name(&mut self, name: String, client_id: ClientId) -> Result { + self.switch_active_tab_name(name, client_id) + } + fn close_tab_at_index(&mut self, tab_index: usize) -> Result<()> { let err_context = || format!("failed to close tab at index {tab_index:?}"); @@ -1981,6 +1999,39 @@ pub(crate) fn screen_thread_main( screen.render()?; } }, + ScreenInstruction::GoToTabName(tab_name, create, client_id) => { + let client_id = if client_id.is_none() { + None + } else if screen + .active_tab_indices + .contains_key(&client_id.expect("This is checked above")) + { + client_id + } else { + screen.active_tab_indices.keys().next().copied() + }; + if let Some(client_id) = client_id { + if let Ok(tab_exists) = screen.go_to_tab_name(tab_name.clone(), client_id) { + screen.unblock_input()?; + screen.render()?; + if create && !tab_exists { + let tab_index = screen.get_new_tab_index(); + screen.new_tab(tab_index, client_id)?; + screen + .bus + .senders + .send_to_plugin(PluginInstruction::NewTab( + None, + None, + vec![], + Some(tab_name), + tab_index, + client_id, + ))?; + } + } + } + }, ScreenInstruction::UpdateTabName(c, client_id) => { screen.update_active_tab_name(c, client_id)?; screen.unblock_input()?; diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index e9b399618a..7b1be55e05 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -546,6 +546,40 @@ pub fn switch_to_next_tab() { ); } +#[test] +pub fn switch_to_tab_name() { + let size = Size { + cols: 121, + rows: 20, + }; + let mut screen = create_new_screen(size); + + new_tab(&mut screen, 1, 1); + new_tab(&mut screen, 2, 2); + + assert_eq!( + screen + .switch_active_tab_name("Tab #1".to_string(), 1) + .expect("TEST"), + false, + "Active tab switched to tab by name" + ); + assert_eq!( + screen + .switch_active_tab_name("Tab #2".to_string(), 1) + .expect("TEST"), + true, + "Active tab switched to tab by name" + ); + assert_eq!( + screen + .switch_active_tab_name("Tab #3".to_string(), 1) + .expect("TEST"), + true, + "Active tab switched to tab by name" + ); +} + #[test] pub fn close_tab() { let size = Size { diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 82c34eecd6..1ef43e27fd 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -315,6 +315,13 @@ pub enum CliAction { CloseTab, /// Go to tab with index [index] GoToTab { index: u32 }, + /// Go to tab with name [name] + GoToTabName { + name: String, + /// Create a tab if one does not exist. + #[clap(short, long, value_parser)] + create: bool, + }, /// Renames the focused pane RenameTab { name: String }, /// Remove a previously set tab name diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 50c8f9b28f..a7986de8d0 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -285,6 +285,7 @@ pub enum ScreenContext { SwitchTabPrev, CloseTab, GoToTab, + GoToTabName, UpdateTabName, UndoRenameTab, TerminalResize, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 1207023aad..b5d6acb13a 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -174,6 +174,7 @@ pub enum Action { /// Close the current tab. CloseTab, GoToTab(u32), + GoToTabName(String, bool), ToggleTab, TabNameInput(Vec), UndoRenameTab, @@ -321,6 +322,7 @@ impl Action { CliAction::GoToPreviousTab => Ok(vec![Action::GoToPreviousTab]), CliAction::CloseTab => Ok(vec![Action::CloseTab]), CliAction::GoToTab { index } => Ok(vec![Action::GoToTab(index)]), + CliAction::GoToTabName { name, create } => Ok(vec![Action::GoToTabName(name, create)]), CliAction::RenameTab { name } => Ok(vec![ Action::TabNameInput(vec![0]), Action::TabNameInput(name.as_bytes().to_vec()),