diff options
| author | Aram Drevekenin <aram@poor.dev> | 2024-09-16 21:02:04 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-16 21:02:04 +0200 |
| commit | d78f3586dd3de75b0ae6a71500b6f15a1ef8d71d (patch) | |
| tree | 6e12313db4515a03f7653c099d1239e34ae4eb86 /zellij-server/src/screen.rs | |
| parent | docs(readme): update sponsors (diff) | |
| download | sl0ppij-d78f3586dd3de75b0ae6a71500b6f15a1ef8d71d.tar.gz sl0ppij-d78f3586dd3de75b0ae6a71500b6f15a1ef8d71d.tar.xz sl0ppij-d78f3586dd3de75b0ae6a71500b6f15a1ef8d71d.zip | |
feat(plugins): APIs to break multiple panes into a new tab or an existing tab (#3610)
* feat(plugins): break multiple panes to a new tab
* fix(layouts): properly ignore run instructions when breaking panes
* feat(plugins): break multiple panes to existing tab
* feat(apis): allow these methods to also specify whether they want focus changed to the tab
* various fixes
* allow specifying name for the new tab when breaking out panes
* style(fmt): rustfmt
Diffstat (limited to 'zellij-server/src/screen.rs')
| -rw-r--r-- | zellij-server/src/screen.rs | 245 |
1 files changed, 216 insertions, 29 deletions
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<RunPluginOrAlias, Vec<u32>>, 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<PaneId>, + default_shell: Option<TerminalAction>, + should_change_focus_to_new_tab: bool, + new_tab_name: Option<String>, + client_id: ClientId, + }, + BreakPanesToTabWithIndex { + pane_ids: Vec<PaneId>, + 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<SwapTiledLayout>, Vec<SwapFloatingLayout>), tab_name: Option<String>, - client_id: ClientId, + client_id: Option<ClientId>, ) -> 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<RunPluginOrAlias, Vec<u32>>, 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<ClientId> = - self.connected_clients.borrow().iter().copied().collect(); - for client_id in all_connected_clients { + let all_connected_clients: Vec<ClientId> = + 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<PaneId>, + default_shell: Option<TerminalAction>, + should_change_focus_to_new_tab: bool, + new_tab_name: Option<String>, + 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<PaneId>, + 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(()) |
