diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index 85300612..92935346 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -415,6 +415,22 @@ impl ZellijPlugin for State { BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Alt]) => { close_tab_with_index(2); }, + BareKey::Char('u') if key.has_modifiers(&[KeyModifier::Alt]) => { + let should_change_focus_to_new_tab = true; + break_panes_to_new_tab( + &[PaneId::Terminal(1), PaneId::Plugin(2)], + Some("new_tab_name".to_owned()), + should_change_focus_to_new_tab, + ); + }, + BareKey::Char('v') if key.has_modifiers(&[KeyModifier::Alt]) => { + let should_change_focus_to_target_tab = true; + break_panes_to_tab_with_index( + &[PaneId::Terminal(1), PaneId::Plugin(2)], + 2, + should_change_focus_to_target_tab, + ); + }, _ => {}, }, Event::CustomMessage(message, payload) => { diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 593afbe0..9f7ef13a 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -74,6 +74,7 @@ pub enum PluginInstruction { Option, Vec, usize, // tab_index + bool, // should change focus to new tab ClientId, ), ApplyCachedEvents { @@ -377,6 +378,7 @@ pub(crate) fn plugin_thread_main( mut tab_layout, mut floating_panes_layout, tab_index, + should_change_focus_to_new_tab, client_id, ) => { // prefer connected clients so as to avoid opening plugins in the background for @@ -444,6 +446,7 @@ pub(crate) fn plugin_thread_main( floating_panes_layout, tab_index, plugin_ids, + should_change_focus_to_new_tab, client_id, ))); }, diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 392985a1..0d20cea0 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -239,6 +239,60 @@ macro_rules! grant_permissions_and_log_actions_in_thread_naked_variant { }; } +macro_rules! grant_permissions_and_log_actions_in_thread_struct_variant { + ( $arc_mutex_log:expr, $exit_event:path, $receiver:expr, $exit_after_count:expr, $permission_type:expr, $cache_path:expr, $plugin_thread_sender:expr, $client_id:expr ) => { + std::thread::Builder::new() + .name("fake_screen_thread".to_string()) + .spawn({ + let log = $arc_mutex_log.clone(); + let mut exit_event_count = 0; + let cache_path = $cache_path.clone(); + let plugin_thread_sender = $plugin_thread_sender.clone(); + move || loop { + let (event, _err_ctx) = $receiver + .recv() + .expect("failed to receive event on channel"); + match event { + $exit_event { .. } => { + exit_event_count += 1; + log.lock().unwrap().push(event); + if exit_event_count == $exit_after_count { + break; + } + }, + ScreenInstruction::RequestPluginPermissions(_, plugin_permission) => { + if plugin_permission.permissions.contains($permission_type) { + let _ = plugin_thread_sender.send( + PluginInstruction::PermissionRequestResult( + 0, + Some($client_id), + plugin_permission.permissions, + PermissionStatus::Granted, + Some(cache_path.clone()), + ), + ); + } else { + let _ = plugin_thread_sender.send( + PluginInstruction::PermissionRequestResult( + 0, + Some($client_id), + plugin_permission.permissions, + PermissionStatus::Denied, + Some(cache_path.clone()), + ), + ); + } + }, + _ => { + log.lock().unwrap().push(event); + }, + } + } + }) + .unwrap() + }; +} + fn create_plugin_thread( zellij_cwd: Option, ) -> ( @@ -8120,3 +8174,145 @@ pub fn close_tab_with_index_plugin_command() { .clone(); assert_snapshot!(format!("{:#?}", screen_instruction)); } + +#[test] +#[ignore] +pub fn break_panes_to_new_tab_plugin_command() { + let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its + // destructor removes the directory + let plugin_host_folder = PathBuf::from(temp_folder.path()); + let cache_path = plugin_host_folder.join("permissions_test.kdl"); + let (plugin_thread_sender, screen_receiver, teardown) = + create_plugin_thread(Some(plugin_host_folder)); + let plugin_should_float = Some(false); + let plugin_title = Some("test_plugin".to_owned()); + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), + configuration: Default::default(), + ..Default::default() + }); + let tab_index = 1; + let client_id = 1; + let size = Size { + cols: 121, + rows: 20, + }; + let received_screen_instructions = Arc::new(Mutex::new(vec![])); + let screen_thread = grant_permissions_and_log_actions_in_thread_struct_variant!( + received_screen_instructions, + ScreenInstruction::BreakPanesToNewTab, + screen_receiver, + 1, + &PermissionType::ChangeApplicationState, + cache_path, + plugin_thread_sender, + client_id + ); + + let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id)); + let _ = plugin_thread_sender.send(PluginInstruction::Load( + plugin_should_float, + false, + plugin_title, + run_plugin, + tab_index, + None, + client_id, + size, + None, + false, + )); + std::thread::sleep(std::time::Duration::from_millis(500)); + let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( + None, + Some(client_id), + Event::Key(KeyWithModifier::new(BareKey::Char('u')).with_alt_modifier()), // this triggers the enent in the fixture plugin + )])); + screen_thread.join().unwrap(); // this might take a while if the cache is cold + teardown(); + let screen_instruction = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::BreakPanesToNewTab { .. } = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn break_panes_to_tab_with_index_plugin_command() { + let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its + // destructor removes the directory + let plugin_host_folder = PathBuf::from(temp_folder.path()); + let cache_path = plugin_host_folder.join("permissions_test.kdl"); + let (plugin_thread_sender, screen_receiver, teardown) = + create_plugin_thread(Some(plugin_host_folder)); + let plugin_should_float = Some(false); + let plugin_title = Some("test_plugin".to_owned()); + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { + _allow_exec_host_cmd: false, + location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), + configuration: Default::default(), + ..Default::default() + }); + let tab_index = 1; + let client_id = 1; + let size = Size { + cols: 121, + rows: 20, + }; + let received_screen_instructions = Arc::new(Mutex::new(vec![])); + let screen_thread = grant_permissions_and_log_actions_in_thread_struct_variant!( + received_screen_instructions, + ScreenInstruction::BreakPanesToTabWithIndex, + screen_receiver, + 1, + &PermissionType::ChangeApplicationState, + cache_path, + plugin_thread_sender, + client_id + ); + + let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id)); + let _ = plugin_thread_sender.send(PluginInstruction::Load( + plugin_should_float, + false, + plugin_title, + run_plugin, + tab_index, + None, + client_id, + size, + None, + false, + )); + std::thread::sleep(std::time::Duration::from_millis(500)); + let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( + None, + Some(client_id), + Event::Key(KeyWithModifier::new(BareKey::Char('v')).with_alt_modifier()), // this triggers the enent in the fixture plugin + )])); + screen_thread.join().unwrap(); // this might take a while if the cache is cold + teardown(); + let screen_instruction = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::BreakPanesToTabWithIndex { .. } = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_new_tab_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_new_tab_plugin_command.snap new file mode 100644 index 00000000..fa3cb222 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_new_tab_plugin_command.snap @@ -0,0 +1,34 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 8246 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + BreakPanesToNewTab { + pane_ids: [ + Terminal( + 1, + ), + Plugin( + 2, + ), + ], + default_shell: Some( + RunCommand( + RunCommand { + command: ".", + args: [], + cwd: None, + hold_on_close: false, + hold_on_start: false, + originating_plugin: None, + }, + ), + ), + should_change_focus_to_new_tab: true, + new_tab_name: Some( + "new_tab_name", + ), + client_id: 1, + }, +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_tab_with_index_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_tab_with_index_plugin_command.snap new file mode 100644 index 00000000..2ebd70fc --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__break_panes_to_tab_with_index_plugin_command.snap @@ -0,0 +1,20 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 8317 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + BreakPanesToTabWithIndex { + pane_ids: [ + Terminal( + 1, + ), + Plugin( + 2, + ), + ], + tab_index: 2, + should_change_focus_to_new_tab: true, + client_id: 1, + }, +) diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 89093c50..6bdabaa3 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -320,6 +320,26 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { PluginCommand::CloseTabWithIndex(tab_index) => { close_tab_with_index(env, tab_index) }, + PluginCommand::BreakPanesToNewTab( + pane_ids, + new_tab_name, + should_change_focus_to_new_tab, + ) => break_panes_to_new_tab( + env, + pane_ids.into_iter().map(|p_id| p_id.into()).collect(), + new_tab_name, + should_change_focus_to_new_tab, + ), + PluginCommand::BreakPanesToTabWithIndex( + pane_ids, + should_change_focus_to_new_tab, + tab_index, + ) => break_panes_to_tab_with_index( + env, + pane_ids.into_iter().map(|p_id| p_id.into()).collect(), + tab_index, + should_change_focus_to_new_tab, + ), }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1589,6 +1609,45 @@ fn close_tab_with_index(env: &PluginEnv, tab_index: usize) { .send_to_screen(ScreenInstruction::CloseTabWithIndex(tab_index)); } +fn break_panes_to_new_tab( + env: &PluginEnv, + pane_ids: Vec, + new_tab_name: Option, + should_change_focus_to_new_tab: bool, +) { + let default_shell = env.default_shell.clone().or_else(|| { + Some(TerminalAction::RunCommand(RunCommand { + command: env.path_to_default_shell.clone(), + ..Default::default() + })) + }); + let _ = env + .senders + .send_to_screen(ScreenInstruction::BreakPanesToNewTab { + pane_ids, + default_shell, + new_tab_name, + should_change_focus_to_new_tab, + client_id: env.client_id, + }); +} + +fn break_panes_to_tab_with_index( + env: &PluginEnv, + pane_ids: Vec, + should_change_focus_to_new_tab: bool, + tab_index: usize, +) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::BreakPanesToTabWithIndex { + pane_ids, + tab_index, + client_id: env.client_id, + should_change_focus_to_new_tab, + }); +} + // Custom panic handler for plugins. // // This is called when a panic occurs in a plugin. Since most panics will likely originate in the @@ -1734,6 +1793,8 @@ fn check_command_permission( | PluginCommand::RerunCommandPane(..) | PluginCommand::ResizePaneIdWithDirection(..) | PluginCommand::CloseTabWithIndex(..) + | PluginCommand::BreakPanesToNewTab(..) + | PluginCommand::BreakPanesToTabWithIndex(..) | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState, PluginCommand::UnblockCliPipeInput(..) | PluginCommand::BlockCliPipeInput(..) diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index a03464fa..92270943 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -64,6 +64,7 @@ pub enum PtyInstruction { Vec, usize, // tab_index HashMap>, // plugin_ids + bool, // should change focus to new tab ClientId, ), // the String is the tab name ClosePane(PaneId), @@ -542,6 +543,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { floating_panes_layout, tab_index, plugin_ids, + should_change_focus_to_new_tab, client_id, ) => { let err_context = || format!("failed to open new tab for client {}", client_id); @@ -558,6 +560,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { terminal_action.clone(), plugin_ids, tab_index, + should_change_focus_to_new_tab, client_id, ) .with_context(err_context)?; @@ -1015,6 +1018,7 @@ impl Pty { default_shell: Option, plugin_ids: HashMap>, tab_index: usize, + should_change_focus_to_new_tab: bool, client_id: ClientId, ) -> Result<()> { let err_context = || format!("failed to spawn terminals for layout for client {client_id}"); @@ -1079,6 +1083,7 @@ impl Pty { new_tab_floating_pane_ids, plugin_ids, tab_index, + should_change_focus_to_new_tab, client_id, )) .with_context(err_context)?; diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 1bbec5cf..cebe5b26 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -227,6 +227,7 @@ pub enum ScreenInstruction { Vec<(u32, HoldForCommand)>, // new floating pane pids HashMap>, usize, // tab_index + bool, // should change focus to new tab ClientId, ), SwitchTabNext(ClientId), @@ -395,6 +396,19 @@ pub enum ScreenInstruction { TogglePaneIdFullscreen(PaneId), TogglePaneEmbedOrEjectForPaneId(PaneId), CloseTabWithIndex(usize), + BreakPanesToNewTab { + pane_ids: Vec, + default_shell: Option, + should_change_focus_to_new_tab: bool, + new_tab_name: Option, + client_id: ClientId, + }, + BreakPanesToTabWithIndex { + pane_ids: Vec, + tab_index: usize, + should_change_focus_to_new_tab: bool, + client_id: ClientId, + }, } impl From<&ScreenInstruction> for ScreenContext { @@ -602,6 +616,10 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenContext::TogglePaneEmbedOrEjectForPaneId }, ScreenInstruction::CloseTabWithIndex(..) => ScreenContext::CloseTabWithIndex, + ScreenInstruction::BreakPanesToNewTab { .. } => ScreenContext::BreakPanesToNewTab, + ScreenInstruction::BreakPanesToTabWithIndex { .. } => { + ScreenContext::BreakPanesToTabWithIndex + }, } } } @@ -1169,7 +1187,10 @@ impl Screen { } } for tab_index in tabs_to_close { - self.close_tab_at_index(tab_index).context(err_context)?; + // cleanup as needed + self.close_tab_at_index(tab_index) + .context(err_context) + .non_fatal(); } if output.is_dirty() { let serialized_output = output.serialize().context(err_context)?; @@ -1254,17 +1275,19 @@ impl Screen { tab_index: usize, swap_layouts: (Vec, Vec), tab_name: Option, - client_id: ClientId, + client_id: Option, ) -> Result<()> { let err_context = || format!("failed to create new tab for client {client_id:?}",); - let client_id = if self.get_active_tab(client_id).is_ok() { - client_id - } else if let Some(first_client_id) = self.get_first_client_id() { - first_client_id - } else { - client_id - }; + let client_id = client_id.map(|client_id| { + if self.get_active_tab(client_id).is_ok() { + client_id + } else if let Some(first_client_id) = self.get_first_client_id() { + first_client_id + } else { + client_id + } + }); let tab_name = tab_name.unwrap_or_else(|| String::new()); @@ -1314,6 +1337,7 @@ impl Screen { new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_plugin_ids: HashMap>, tab_index: usize, + should_change_client_focus: bool, client_id: ClientId, ) -> Result<()> { if self.tabs.get(&tab_index).is_none() { @@ -1332,31 +1356,42 @@ impl Screen { let err_context = || format!("failed to apply layout for tab {tab_index:?}",); // move the relevant clients out of the current tab and place them in the new one - let drained_clients = if self.session_is_mirrored { - let client_mode_infos_in_source_tab = - if let Ok(active_tab) = self.get_active_tab_mut(client_id) { + let drained_clients = if should_change_client_focus { + if self.session_is_mirrored { + let client_mode_infos_in_source_tab = if let Ok(active_tab) = + self.get_active_tab_mut(client_id) + { let client_mode_infos_in_source_tab = active_tab.drain_connected_clients(None); if active_tab.has_no_connected_clients() { - active_tab.visible(false).with_context(err_context)?; + active_tab + .visible(false) + .with_context(err_context) + .non_fatal(); } Some(client_mode_infos_in_source_tab) } else { None }; - let all_connected_clients: Vec = - self.connected_clients.borrow().iter().copied().collect(); - for client_id in all_connected_clients { + let all_connected_clients: Vec = + self.connected_clients.borrow().iter().copied().collect(); + for client_id in all_connected_clients { + self.update_client_tab_focus(client_id, tab_index); + } + client_mode_infos_in_source_tab + } else if let Ok(active_tab) = self.get_active_tab_mut(client_id) { + let client_mode_info_in_source_tab = + active_tab.drain_connected_clients(Some(vec![client_id])); + if active_tab.has_no_connected_clients() { + active_tab + .visible(false) + .with_context(err_context) + .non_fatal(); + } self.update_client_tab_focus(client_id, tab_index); + Some(client_mode_info_in_source_tab) + } else { + None } - client_mode_infos_in_source_tab - } else if let Ok(active_tab) = self.get_active_tab_mut(client_id) { - let client_mode_info_in_source_tab = - active_tab.drain_connected_clients(Some(vec![client_id])); - if active_tab.has_no_connected_clients() { - active_tab.visible(false).with_context(err_context)?; - } - self.update_client_tab_focus(client_id, tab_index); - Some(client_mode_info_in_source_tab) } else { None }; @@ -1375,10 +1410,13 @@ impl Screen { client_id, )?; tab.update_input_modes()?; - tab.visible(true)?; + if let Some(drained_clients) = drained_clients { + tab.visible(true)?; tab.add_multiple_clients(drained_clients)?; } + tab.resize_whole_tab(self.size).with_context(err_context)?; + tab.set_force_render(); Ok(()) }) .with_context(err_context)?; @@ -2083,7 +2121,7 @@ impl Screen { default_layout.swap_tiled_layouts.clone(), default_layout.swap_floating_layouts.clone(), ); - self.new_tab(tab_index, swap_layouts, None, client_id)?; + self.new_tab(tab_index, swap_layouts, None, Some(client_id))?; let tab = self.tabs.get_mut(&tab_index).with_context(err_context)?; let (mut tiled_panes_layout, mut floating_panes_layout) = default_layout.new_tab(); if pane_to_break_is_floating { @@ -2099,12 +2137,14 @@ impl Screen { tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?; tiled_panes_layout.ignore_run_instruction(active_pane_run_instruction.clone()); } + let should_change_focus_to_new_tab = true; self.bus.senders.send_to_plugin(PluginInstruction::NewTab( None, default_shell, Some(tiled_panes_layout), floating_panes_layout, tab_index, + should_change_focus_to_new_tab, client_id, ))?; } else { @@ -2122,6 +2162,63 @@ impl Screen { } Ok(()) } + pub fn break_multiple_panes_to_new_tab( + &mut self, + pane_ids: Vec, + default_shell: Option, + should_change_focus_to_new_tab: bool, + new_tab_name: Option, + client_id: ClientId, + ) -> Result<()> { + let err_context = || "failed break multiple panes to a new tab".to_string(); + + let all_tabs = self.get_tabs_mut(); + let mut extracted_panes = vec![]; + for pane_id in pane_ids { + for tab in all_tabs.values_mut() { + // here we pass None instead of the client_id we have because we do not need to + // necessarily trigger a relayout for this tab + if let Some(pane) = tab.extract_pane(pane_id, true, None).take() { + extracted_panes.push(pane); + break; + } + } + } + + let (mut tiled_panes_layout, floating_panes_layout) = self.default_layout.new_tab(); + let tab_index = self.get_new_tab_index(); + let swap_layouts = ( + self.default_layout.swap_tiled_layouts.clone(), + self.default_layout.swap_floating_layouts.clone(), + ); + if should_change_focus_to_new_tab { + self.new_tab(tab_index, swap_layouts, None, Some(client_id))?; + } else { + self.new_tab(tab_index, swap_layouts, None, None)?; + } + let mut tab = self.tabs.get_mut(&tab_index).with_context(err_context)?; + if let Some(new_tab_name) = new_tab_name { + tab.name = new_tab_name.clone(); + } + for pane in extracted_panes { + let run_instruction = pane.invoked_with().clone(); + let pane_id = pane.pid(); + // here we pass None instead of the ClientId, because we do not want this pane to be + // necessarily focused + tab.add_tiled_pane(pane, pane_id, None)?; + tiled_panes_layout.ignore_run_instruction(run_instruction.clone()); + } + self.bus.senders.send_to_plugin(PluginInstruction::NewTab( + None, + default_shell, + Some(tiled_panes_layout), + floating_panes_layout, + tab_index, + should_change_focus_to_new_tab, + client_id, + ))?; + Ok(()) + } pub fn break_pane_to_new_tab( &mut self, direction: Direction, @@ -2184,6 +2281,57 @@ impl Screen { self.render(None)?; Ok(()) } + pub fn break_multiple_panes_to_tab_with_index( + &mut self, + pane_ids: Vec, + tab_index: usize, + should_change_focus_to_new_tab: bool, + client_id: ClientId, + ) -> Result<()> { + let all_tabs = self.get_tabs_mut(); + let has_tab_with_index = all_tabs + .values() + .find(|t| t.position == tab_index) + .is_some(); + if !has_tab_with_index { + log::error!("Cannot find tab with index: {tab_index}"); + return Ok(()); + } + let mut extracted_panes = vec![]; + for pane_id in pane_ids { + for tab in all_tabs.values_mut() { + if tab.position == tab_index { + continue; + } + // here we pass None instead of the client_id we have because we do not need to + // necessarily trigger a relayout for this tab + if let Some(pane) = tab.extract_pane(pane_id, true, None).take() { + extracted_panes.push(pane); + break; + } + } + } + + if should_change_focus_to_new_tab { + self.go_to_tab(tab_index + 1, client_id)?; + } + if extracted_panes.is_empty() { + // nothing to do here... + return Ok(()); + } + if let Some(new_active_tab) = self.get_indexed_tab_mut(tab_index) { + for pane in extracted_panes { + let pane_id = pane.pid(); + // here we pass None instead of the ClientId, because we do not want this pane to be + // necessarily focused + new_active_tab.add_tiled_pane(pane, pane_id, None)?; + } + } else { + log::error!("Could not find tab with index: {:?}", tab_index); + } + self.log_and_report_session_state()?; + Ok(()) + } pub fn replace_pane( &mut self, new_pane_id: PaneId, @@ -3246,8 +3394,9 @@ pub(crate) fn screen_thread_main( client_id, ) => { let tab_index = screen.get_new_tab_index(); + let should_change_focus_to_new_tab = true; pending_tab_ids.insert(tab_index); - screen.new_tab(tab_index, swap_layouts, tab_name.clone(), client_id)?; + screen.new_tab(tab_index, swap_layouts, tab_name.clone(), Some(client_id))?; screen .bus .senders @@ -3257,6 +3406,7 @@ pub(crate) fn screen_thread_main( layout, floating_panes_layout, tab_index, + should_change_focus_to_new_tab, client_id, ))?; }, @@ -3267,6 +3417,7 @@ pub(crate) fn screen_thread_main( new_floating_pane_pids, new_plugin_ids, tab_index, + should_change_focus_to_new_tab, client_id, ) => { screen.apply_layout( @@ -3276,6 +3427,7 @@ pub(crate) fn screen_thread_main( new_floating_pane_pids, new_plugin_ids.clone(), tab_index, + should_change_focus_to_new_tab, client_id, )?; pending_tab_ids.remove(&tab_index); @@ -3363,7 +3515,13 @@ pub(crate) fn screen_thread_main( screen.render(None)?; if create && !tab_exists { let tab_index = screen.get_new_tab_index(); - screen.new_tab(tab_index, swap_layouts, Some(tab_name), client_id)?; + let should_change_focus_to_new_tab = true; + screen.new_tab( + tab_index, + swap_layouts, + Some(tab_name), + Some(client_id), + )?; screen .bus .senders @@ -3373,6 +3531,7 @@ pub(crate) fn screen_thread_main( None, vec![], tab_index, + should_change_focus_to_new_tab, client_id, ))?; } @@ -4427,6 +4586,34 @@ pub(crate) fn screen_thread_main( ScreenInstruction::CloseTabWithIndex(tab_index) => { screen.close_tab_at_index(tab_index).non_fatal() }, + ScreenInstruction::BreakPanesToNewTab { + pane_ids, + default_shell, + should_change_focus_to_new_tab, + new_tab_name, + client_id, + } => { + screen.break_multiple_panes_to_new_tab( + pane_ids, + default_shell, + should_change_focus_to_new_tab, + new_tab_name, + client_id, + )?; + }, + ScreenInstruction::BreakPanesToTabWithIndex { + pane_ids, + tab_index, + should_change_focus_to_new_tab, + client_id, + } => { + screen.break_multiple_panes_to_tab_with_index( + pane_ids, + tab_index, + should_change_focus_to_new_tab, + client_id, + )?; + }, } } Ok(()) diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index ff33abb9..2cc2b7f8 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -256,6 +256,19 @@ impl<'a> LayoutApplier<'a> { position_and_size, layout.borderless, ); + } else if let Some(position) = + positions_in_layout + .iter() + .position(|(layout, _position_and_size)| { + Run::is_terminal(&layout.run) && Run::is_terminal(&run_instruction) + }) + { + let (layout, position_and_size) = positions_in_layout.remove(position); + self.tiled_panes.set_geom_for_pane_with_run( + run_instruction, + position_and_size, + layout.borderless, + ); } else { log::error!( "Failed to find room for run instruction: {:?}", diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 7b8e01e4..ae238fa0 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -546,7 +546,7 @@ impl Tab { auto_layout: bool, connected_clients_in_app: Rc>>, session_is_mirrored: bool, - client_id: ClientId, + client_id: Option, copy_options: CopyOptions, terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, @@ -564,7 +564,9 @@ impl Tab { }; let mut connected_clients = HashSet::new(); - connected_clients.insert(client_id); + if let Some(client_id) = client_id { + connected_clients.insert(client_id); + } let viewport: Viewport = display_area.into(); let viewport = Rc::new(RefCell::new(viewport)); let display_area = Rc::new(RefCell::new(display_area)); diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 4d94a777..fa937eae 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -243,7 +243,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -322,7 +322,7 @@ fn create_new_tab_with_swap_layouts( auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -403,7 +403,7 @@ fn create_new_tab_with_os_api( auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -470,7 +470,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -551,7 +551,7 @@ fn create_new_tab_with_mock_pty_writer( auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -623,7 +623,7 @@ fn create_new_tab_with_sixel_support( auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index a0c04088..b664910f 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -184,7 +184,7 @@ fn create_new_tab(size: Size) -> Tab { auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -248,7 +248,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, @@ -318,7 +318,7 @@ fn create_new_tab_with_cell_size( auto_layout, connected_clients, session_is_mirrored, - client_id, + Some(client_id), copy_options, terminal_emulator_colors, terminal_emulator_color_codes, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index f3554286..9f3317c0 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -387,6 +387,7 @@ impl MockScreen { floating_pane_ids, plugin_ids, tab_index, + true, self.main_client_id, )); self.last_opened_tab_index = Some(tab_index); @@ -471,6 +472,7 @@ impl MockScreen { floating_pane_ids, plugin_ids, tab_index, + true, self.main_client_id, )); self.last_opened_tab_index = Some(tab_index); @@ -502,6 +504,7 @@ impl MockScreen { vec![], // floating panes ids plugin_ids, 0, + true, self.main_client_id, )); self.last_opened_tab_index = Some(tab_index); @@ -649,7 +652,7 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) { let new_terminal_ids = vec![(pid, None)]; let new_plugin_ids = HashMap::new(); screen - .new_tab(tab_index, (vec![], vec![]), None, client_id) + .new_tab(tab_index, (vec![], vec![]), None, Some(client_id)) .expect("TEST"); screen .apply_layout( @@ -659,6 +662,7 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) { vec![], // new floating terminal ids new_plugin_ids, tab_index, + true, client_id, ) .expect("TEST"); @@ -3248,6 +3252,7 @@ pub fn screen_can_break_pane_to_a_new_tab() { vec![], // floating panes ids Default::default(), 1, + true, 1, )); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -3349,6 +3354,7 @@ pub fn screen_can_break_floating_pane_to_a_new_tab() { vec![], // floating panes ids Default::default(), 1, + true, 1, )); std::thread::sleep(std::time::Duration::from_millis(200)); @@ -3418,6 +3424,7 @@ pub fn screen_can_break_plugin_pane_to_a_new_tab() { vec![], // floating panes ids Default::default(), 1, + true, 1, )); std::thread::sleep(std::time::Duration::from_millis(100)); @@ -3491,6 +3498,7 @@ pub fn screen_can_break_floating_plugin_pane_to_a_new_tab() { vec![], // floating panes ids Default::default(), 1, + true, 1, )); std::thread::sleep(std::time::Duration::from_millis(100)); diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-2.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-2.snap index 5fa4bb26..e5dd722d 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-2.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-2.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2849 +assertion_line: 3374 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_pane_to_eject ──────────────┐ │ -08 (C): │ │ │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_pane_to_eject ──────────────┐ │ +06 (C): │ │ │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-4.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-4.snap index 5fa4bb26..e5dd722d 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-4.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_pane_to_a_new_tab-4.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2849 +assertion_line: 3374 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_pane_to_eject ──────────────┐ │ -08 (C): │ │ │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_pane_to_eject ──────────────┐ │ +06 (C): │ │ │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-3.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-3.snap index c7646030..2c160dd8 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-3.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-3.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 3512 +assertion_line: 3516 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ -08 (C): │ │Loading file:/path/to/fake/plugin │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ +06 (C): │ │Loading file:/path/to/fake/plugin │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-4.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-4.snap index c7646030..2c160dd8 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-4.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-4.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 3512 +assertion_line: 3516 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ -08 (C): │ │Loading file:/path/to/fake/plugin │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ +06 (C): │ │Loading file:/path/to/fake/plugin │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-6.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-6.snap index c7646030..2c160dd8 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-6.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-6.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 3512 +assertion_line: 3516 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ -08 (C): │ │Loading file:/path/to/fake/plugin │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ +06 (C): │ │Loading file:/path/to/fake/plugin │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-7.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-7.snap index c7646030..2c160dd8 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-7.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__screen_can_break_floating_plugin_pane_to_a_new_tab-7.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 3512 +assertion_line: 3516 expression: "format!(\"{}\", snapshot)" --- 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ @@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)" 02 (C): │ │ 03 (C): │ │ 04 (C): │ │ -05 (C): │ │ -06 (C): │ │ -07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ -08 (C): │ │Loading file:/path/to/fake/plugin │ │ -09 (C): │ │ │ │ -10 (C): │ │ │ │ -11 (C): │ │ │ │ -12 (C): │ │ │ │ -13 (C): │ │ │ │ -14 (C): │ │ │ │ -15 (C): │ │ │ │ -16 (C): │ └──────────────────────────────────────┘ │ +05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ │ +06 (C): │ │Loading file:/path/to/fake/plugin │ │ +07 (C): │ │ │ │ +08 (C): │ │ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │ │ │ +12 (C): │ │ │ │ +13 (C): │ │ │ │ +14 (C): │ └──────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ 17 (C): │ │ 18 (C): │ │ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap index 7ad89e77..8f9ca5e8 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_default_params.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2246 +assertion_line: 2665 expression: "format!(\"{:#?}\", new_tab_action)" --- Some( @@ -60,6 +60,7 @@ Some( ), [], 0, + true, 1, ), ) diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap index f3bcbecb..aa1892c7 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_new_tab_action_with_name_and_layout.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2292 +assertion_line: 2711 expression: "format!(\"{:#?}\", new_tab_instruction)" --- NewTab( @@ -87,5 +87,6 @@ NewTab( ), [], 1, + true, 10, ) diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_toggle_active_tab_sync_action.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_toggle_active_tab_sync_action.snap index 1c2440eb..a75f02a9 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_toggle_active_tab_sync_action.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_toggle_active_tab_sync_action.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1825 +assertion_line: 2183 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), Write([102, 111, 111], 1), ApplyCachedResizes, Exit] +[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 58, 18, None, None), ResizePty(1, 59, 18, None, None), ResizePty(0, 59, 18, None, None), ResizePty(1, 58, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), Write([102, 111, 111], 1), ApplyCachedResizes, Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_action_to_screen.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_action_to_screen.snap index aad2ad25..153ca11f 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_action_to_screen.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_action_to_screen.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1065 +assertion_line: 1423 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), ApplyCachedResizes, Exit] +[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([102, 111, 111], 0), ApplyCachedResizes, Exit] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_chars_action_to_screen.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_chars_action_to_screen.snap index ca6c9635..34a3eb4e 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_chars_action_to_screen.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_write_chars_action_to_screen.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 1039 +assertion_line: 1397 expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" --- -[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([105, 110, 112, 117, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 99, 108, 105], 0), ApplyCachedResizes, Exit] +[StartCachingResizes, ApplyCachedResizes, StartCachingResizes, ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ResizePty(0, 119, 18, None, None), ApplyCachedResizes, ApplyCachedResizes, StartCachingResizes, ApplyCachedResizes, StartCachingResizes, Write([105, 110, 112, 117, 116, 32, 102, 114, 111, 109, 32, 116, 104, 101, 32, 99, 108, 105], 0), ApplyCachedResizes, Exit] diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 9b57d1a9..268e10f2 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -1030,6 +1030,38 @@ where unsafe { host_run_plugin_command() }; } +/// Create a new tab that includes the specified pane ids +pub fn break_panes_to_new_tab( + pane_ids: &[PaneId], + new_tab_name: Option, + should_change_focus_to_new_tab: bool, +) { + let plugin_command = PluginCommand::BreakPanesToNewTab( + pane_ids.to_vec(), + new_tab_name, + should_change_focus_to_new_tab, + ); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Create a new tab that includes the specified pane ids +pub fn break_panes_to_tab_with_index( + pane_ids: &[PaneId], + tab_index: usize, + should_change_focus_to_new_tab: bool, +) { + let plugin_command = PluginCommand::BreakPanesToTabWithIndex( + pane_ids.to_vec(), + tab_index, + should_change_focus_to_new_tab, + ); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + // Utility Functions #[allow(unused)] diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 7ebbf11d..110e1669 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -5,7 +5,7 @@ pub struct PluginCommand { pub name: i32, #[prost( oneof = "plugin_command::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85" )] pub payload: ::core::option::Option, } @@ -164,10 +164,34 @@ pub mod plugin_command { ), #[prost(message, tag = "83")] CloseTabWithIndexPayload(super::CloseTabWithIndexPayload), + #[prost(message, tag = "84")] + BreakPanesToNewTabPayload(super::BreakPanesToNewTabPayload), + #[prost(message, tag = "85")] + BreakPanesToTabWithIndexPayload(super::BreakPanesToTabWithIndexPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct BreakPanesToTabWithIndexPayload { + #[prost(message, repeated, tag = "1")] + pub pane_ids: ::prost::alloc::vec::Vec, + #[prost(uint32, tag = "2")] + pub tab_index: u32, + #[prost(bool, tag = "3")] + pub should_change_focus_to_target_tab: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BreakPanesToNewTabPayload { + #[prost(message, repeated, tag = "1")] + pub pane_ids: ::prost::alloc::vec::Vec, + #[prost(bool, tag = "2")] + pub should_change_focus_to_new_tab: bool, + #[prost(string, optional, tag = "3")] + pub new_tab_name: ::core::option::Option<::prost::alloc::string::String>, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct MovePaneWithPaneIdPayload { #[prost(message, optional, tag = "1")] pub pane_id: ::core::option::Option, @@ -634,6 +658,8 @@ pub enum CommandName { TogglePaneIdFullscreen = 105, TogglePaneEmbedOrEjectForPaneId = 106, CloseTabWithIndex = 107, + BreakPanesToNewTab = 108, + BreakPanesToTabWithIndex = 109, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -752,6 +778,8 @@ impl CommandName { "TogglePaneEmbedOrEjectForPaneId" } CommandName::CloseTabWithIndex => "CloseTabWithIndex", + CommandName::BreakPanesToNewTab => "BreakPanesToNewTab", + CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -867,6 +895,8 @@ impl CommandName { Some(Self::TogglePaneEmbedOrEjectForPaneId) } "CloseTabWithIndex" => Some(Self::CloseTabWithIndex), + "BreakPanesToNewTab" => Some(Self::BreakPanesToNewTab), + "BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 2087977b..eb717945 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1836,4 +1836,10 @@ pub enum PluginCommand { TogglePaneIdFullscreen(PaneId), TogglePaneEmbedOrEjectForPaneId(PaneId), CloseTabWithIndex(usize), // usize - tab_index + BreakPanesToNewTab(Vec, Option, bool), // bool - + // should_change_focus_to_new_tab, + // Option - optional name for + // the new tab + BreakPanesToTabWithIndex(Vec, usize, bool), // usize - tab_index, bool - + // should_change_focus_to_new_tab } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index f86595a3..64f21936 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -370,6 +370,8 @@ pub enum ScreenContext { TogglePaneIdFullscreen, TogglePaneEmbedOrEjectForPaneId, CloseTabWithIndex, + BreakPanesToNewTab, + BreakPanesToTabWithIndex, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index 8278f638..dbcd4181 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -938,7 +938,16 @@ impl TiledPaneLayout { .len() .saturating_sub(successfully_ignored) { - if let Some(position) = run_instructions.iter().position(|i| i == &None) { + if let Some(position) = run_instructions.iter().position(|i| { + match i { + // this is because a bare CWD instruction should be overidden by a terminal + // in run_instructions_to_ignore (for cases where the cwd for example comes + // from a global layout cwd and the pane is actually just a bare pane that + // wants to be overidden) + Some(Run::Cwd(_)) | None => true, + _ => false, + } + }) { run_instructions.remove(position); } } diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 6e77643b..758d4e77 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -119,6 +119,8 @@ enum CommandName { TogglePaneIdFullscreen = 105; TogglePaneEmbedOrEjectForPaneId = 106; CloseTabWithIndex = 107; + BreakPanesToNewTab = 108; + BreakPanesToTabWithIndex = 109; } message PluginCommand { @@ -197,9 +199,23 @@ message PluginCommand { TogglePaneIdFullscreenPayload toggle_pane_id_fullscreen_payload = 81; TogglePaneEmbedOrEjectForPaneIdPayload toggle_pane_embed_or_eject_for_pane_id_payload = 82; CloseTabWithIndexPayload close_tab_with_index_payload = 83; + BreakPanesToNewTabPayload break_panes_to_new_tab_payload = 84; + BreakPanesToTabWithIndexPayload break_panes_to_tab_with_index_payload = 85; } } +message BreakPanesToTabWithIndexPayload { + repeated PaneId pane_ids = 1; + uint32 tab_index = 2; + bool should_change_focus_to_target_tab = 3; +} + +message BreakPanesToNewTabPayload { + repeated PaneId pane_ids = 1; + bool should_change_focus_to_new_tab = 2; + optional string new_tab_name = 3; +} + message MovePaneWithPaneIdPayload { PaneId pane_id = 1; } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 155907d3..15c56d03 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -3,9 +3,10 @@ pub use super::generated_api::api::{ event::{EventNameList as ProtobufEventNameList, Header}, input_mode::InputMode as ProtobufInputMode, plugin_command::{ - plugin_command::Payload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, - CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload, - EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, + plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload, + ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseTabWithIndexPayload, CommandName, + ContextItem, EditScrollbackForPaneWithIdPayload, EnvVariable, ExecCmdPayload, + FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload, @@ -1174,6 +1175,34 @@ impl TryFrom for PluginCommand { ), _ => Err("Mismatched payload for CloseTabWithIndex"), }, + Some(CommandName::BreakPanesToNewTab) => match protobuf_plugin_command.payload { + Some(Payload::BreakPanesToNewTabPayload(break_panes_to_new_tab_payload)) => { + Ok(PluginCommand::BreakPanesToNewTab( + break_panes_to_new_tab_payload + .pane_ids + .into_iter() + .filter_map(|p_id| p_id.try_into().ok()) + .collect(), + break_panes_to_new_tab_payload.new_tab_name, + break_panes_to_new_tab_payload.should_change_focus_to_new_tab, + )) + }, + _ => Err("Mismatched payload for BreakPanesToNewTab"), + }, + Some(CommandName::BreakPanesToTabWithIndex) => match protobuf_plugin_command.payload { + Some(Payload::BreakPanesToTabWithIndexPayload( + break_panes_to_tab_with_index_payload, + )) => Ok(PluginCommand::BreakPanesToTabWithIndex( + break_panes_to_tab_with_index_payload + .pane_ids + .into_iter() + .filter_map(|p_id| p_id.try_into().ok()) + .collect(), + break_panes_to_tab_with_index_payload.tab_index as usize, + break_panes_to_tab_with_index_payload.should_change_focus_to_target_tab, + )), + _ => Err("Mismatched payload for BreakPanesToTabWithIndex"), + }, None => Err("Unrecognized plugin command"), } } @@ -1922,6 +1951,40 @@ impl TryFrom for ProtobufPluginCommand { }, )), }), + PluginCommand::BreakPanesToNewTab( + pane_ids, + new_tab_name, + should_change_focus_to_new_tab, + ) => Ok(ProtobufPluginCommand { + name: CommandName::BreakPanesToNewTab as i32, + payload: Some(Payload::BreakPanesToNewTabPayload( + BreakPanesToNewTabPayload { + pane_ids: pane_ids + .into_iter() + .filter_map(|p_id| p_id.try_into().ok()) + .collect(), + should_change_focus_to_new_tab, + new_tab_name, + }, + )), + }), + PluginCommand::BreakPanesToTabWithIndex( + pane_ids, + tab_index, + should_change_focus_to_target_tab, + ) => Ok(ProtobufPluginCommand { + name: CommandName::BreakPanesToTabWithIndex as i32, + payload: Some(Payload::BreakPanesToTabWithIndexPayload( + BreakPanesToTabWithIndexPayload { + pane_ids: pane_ids + .into_iter() + .filter_map(|p_id| p_id.try_into().ok()) + .collect(), + tab_index: tab_index as u32, + should_change_focus_to_target_tab, + }, + )), + }), } } }