aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src/screen.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-09-16 21:02:04 +0200
committerGitHub <noreply@github.com>2024-09-16 21:02:04 +0200
commitd78f3586dd3de75b0ae6a71500b6f15a1ef8d71d (patch)
tree6e12313db4515a03f7653c099d1239e34ae4eb86 /zellij-server/src/screen.rs
parentdocs(readme): update sponsors (diff)
downloadsl0ppij-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 '')
-rw-r--r--zellij-server/src/screen.rs245
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(())