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
This commit is contained in:
Aram Drevekenin 2024-09-16 21:02:04 +02:00 committed by GitHub
parent 5868aa297c
commit d78f3586dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 837 additions and 132 deletions

View file

@ -415,6 +415,22 @@ impl ZellijPlugin for State {
BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Alt]) => { BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Alt]) => {
close_tab_with_index(2); 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) => { Event::CustomMessage(message, payload) => {

View file

@ -74,6 +74,7 @@ pub enum PluginInstruction {
Option<TiledPaneLayout>, Option<TiledPaneLayout>,
Vec<FloatingPaneLayout>, Vec<FloatingPaneLayout>,
usize, // tab_index usize, // tab_index
bool, // should change focus to new tab
ClientId, ClientId,
), ),
ApplyCachedEvents { ApplyCachedEvents {
@ -377,6 +378,7 @@ pub(crate) fn plugin_thread_main(
mut tab_layout, mut tab_layout,
mut floating_panes_layout, mut floating_panes_layout,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
) => { ) => {
// prefer connected clients so as to avoid opening plugins in the background for // 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, floating_panes_layout,
tab_index, tab_index,
plugin_ids, plugin_ids,
should_change_focus_to_new_tab,
client_id, client_id,
))); )));
}, },

View file

@ -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( fn create_plugin_thread(
zellij_cwd: Option<PathBuf>, zellij_cwd: Option<PathBuf>,
) -> ( ) -> (
@ -8120,3 +8174,145 @@ pub fn close_tab_with_index_plugin_command() {
.clone(); .clone();
assert_snapshot!(format!("{:#?}", screen_instruction)); 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));
}

View file

@ -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,
},
)

View file

@ -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,
},
)

View file

@ -320,6 +320,26 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
PluginCommand::CloseTabWithIndex(tab_index) => { PluginCommand::CloseTabWithIndex(tab_index) => {
close_tab_with_index(env, 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) => { (PermissionStatus::Denied, permission) => {
log::error!( log::error!(
@ -1589,6 +1609,45 @@ fn close_tab_with_index(env: &PluginEnv, tab_index: usize) {
.send_to_screen(ScreenInstruction::CloseTabWithIndex(tab_index)); .send_to_screen(ScreenInstruction::CloseTabWithIndex(tab_index));
} }
fn break_panes_to_new_tab(
env: &PluginEnv,
pane_ids: Vec<PaneId>,
new_tab_name: Option<String>,
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<PaneId>,
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. // Custom panic handler for plugins.
// //
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the // 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::RerunCommandPane(..)
| PluginCommand::ResizePaneIdWithDirection(..) | PluginCommand::ResizePaneIdWithDirection(..)
| PluginCommand::CloseTabWithIndex(..) | PluginCommand::CloseTabWithIndex(..)
| PluginCommand::BreakPanesToNewTab(..)
| PluginCommand::BreakPanesToTabWithIndex(..)
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState, | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..) PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..) | PluginCommand::BlockCliPipeInput(..)

View file

@ -64,6 +64,7 @@ pub enum PtyInstruction {
Vec<FloatingPaneLayout>, Vec<FloatingPaneLayout>,
usize, // tab_index usize, // tab_index
HashMap<RunPluginOrAlias, Vec<u32>>, // plugin_ids HashMap<RunPluginOrAlias, Vec<u32>>, // plugin_ids
bool, // should change focus to new tab
ClientId, ClientId,
), // the String is the tab name ), // the String is the tab name
ClosePane(PaneId), ClosePane(PaneId),
@ -542,6 +543,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
floating_panes_layout, floating_panes_layout,
tab_index, tab_index,
plugin_ids, plugin_ids,
should_change_focus_to_new_tab,
client_id, client_id,
) => { ) => {
let err_context = || format!("failed to open new tab for client {}", 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<Layout>) -> Result<()> {
terminal_action.clone(), terminal_action.clone(),
plugin_ids, plugin_ids,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
) )
.with_context(err_context)?; .with_context(err_context)?;
@ -1015,6 +1018,7 @@ impl Pty {
default_shell: Option<TerminalAction>, default_shell: Option<TerminalAction>,
plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>,
tab_index: usize, tab_index: usize,
should_change_focus_to_new_tab: bool,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to spawn terminals for layout for client {client_id}"); 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, new_tab_floating_pane_ids,
plugin_ids, plugin_ids,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
)) ))
.with_context(err_context)?; .with_context(err_context)?;

View file

@ -227,6 +227,7 @@ pub enum ScreenInstruction {
Vec<(u32, HoldForCommand)>, // new floating pane pids Vec<(u32, HoldForCommand)>, // new floating pane pids
HashMap<RunPluginOrAlias, Vec<u32>>, HashMap<RunPluginOrAlias, Vec<u32>>,
usize, // tab_index usize, // tab_index
bool, // should change focus to new tab
ClientId, ClientId,
), ),
SwitchTabNext(ClientId), SwitchTabNext(ClientId),
@ -395,6 +396,19 @@ pub enum ScreenInstruction {
TogglePaneIdFullscreen(PaneId), TogglePaneIdFullscreen(PaneId),
TogglePaneEmbedOrEjectForPaneId(PaneId), TogglePaneEmbedOrEjectForPaneId(PaneId),
CloseTabWithIndex(usize), 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 { impl From<&ScreenInstruction> for ScreenContext {
@ -602,6 +616,10 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenContext::TogglePaneEmbedOrEjectForPaneId ScreenContext::TogglePaneEmbedOrEjectForPaneId
}, },
ScreenInstruction::CloseTabWithIndex(..) => ScreenContext::CloseTabWithIndex, 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 { 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() { if output.is_dirty() {
let serialized_output = output.serialize().context(err_context)?; let serialized_output = output.serialize().context(err_context)?;
@ -1254,17 +1275,19 @@ impl Screen {
tab_index: usize, tab_index: usize,
swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>), swap_layouts: (Vec<SwapTiledLayout>, Vec<SwapFloatingLayout>),
tab_name: Option<String>, tab_name: Option<String>,
client_id: ClientId, client_id: Option<ClientId>,
) -> Result<()> { ) -> Result<()> {
let err_context = || format!("failed to create new tab for client {client_id:?}",); 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() { let client_id = client_id.map(|client_id| {
if self.get_active_tab(client_id).is_ok() {
client_id client_id
} else if let Some(first_client_id) = self.get_first_client_id() { } else if let Some(first_client_id) = self.get_first_client_id() {
first_client_id first_client_id
} else { } else {
client_id client_id
}; }
});
let tab_name = tab_name.unwrap_or_else(|| String::new()); let tab_name = tab_name.unwrap_or_else(|| String::new());
@ -1314,6 +1337,7 @@ impl Screen {
new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_floating_terminal_ids: Vec<(u32, HoldForCommand)>,
new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>,
tab_index: usize, tab_index: usize,
should_change_client_focus: bool,
client_id: ClientId, client_id: ClientId,
) -> Result<()> { ) -> Result<()> {
if self.tabs.get(&tab_index).is_none() { if self.tabs.get(&tab_index).is_none() {
@ -1332,12 +1356,17 @@ impl Screen {
let err_context = || format!("failed to apply layout for tab {tab_index:?}",); 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 // 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 drained_clients = if should_change_client_focus {
let client_mode_infos_in_source_tab = if self.session_is_mirrored {
if let Ok(active_tab) = self.get_active_tab_mut(client_id) { 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); let client_mode_infos_in_source_tab = active_tab.drain_connected_clients(None);
if active_tab.has_no_connected_clients() { 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) Some(client_mode_infos_in_source_tab)
} else { } else {
@ -1353,12 +1382,18 @@ impl Screen {
let client_mode_info_in_source_tab = let client_mode_info_in_source_tab =
active_tab.drain_connected_clients(Some(vec![client_id])); active_tab.drain_connected_clients(Some(vec![client_id]));
if active_tab.has_no_connected_clients() { 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();
} }
self.update_client_tab_focus(client_id, tab_index); self.update_client_tab_focus(client_id, tab_index);
Some(client_mode_info_in_source_tab) Some(client_mode_info_in_source_tab)
} else { } else {
None None
}
} else {
None
}; };
// apply the layout to the new tab // apply the layout to the new tab
@ -1375,10 +1410,13 @@ impl Screen {
client_id, client_id,
)?; )?;
tab.update_input_modes()?; tab.update_input_modes()?;
tab.visible(true)?;
if let Some(drained_clients) = drained_clients { if let Some(drained_clients) = drained_clients {
tab.visible(true)?;
tab.add_multiple_clients(drained_clients)?; tab.add_multiple_clients(drained_clients)?;
} }
tab.resize_whole_tab(self.size).with_context(err_context)?;
tab.set_force_render();
Ok(()) Ok(())
}) })
.with_context(err_context)?; .with_context(err_context)?;
@ -2083,7 +2121,7 @@ impl Screen {
default_layout.swap_tiled_layouts.clone(), default_layout.swap_tiled_layouts.clone(),
default_layout.swap_floating_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 tab = self.tabs.get_mut(&tab_index).with_context(err_context)?;
let (mut tiled_panes_layout, mut floating_panes_layout) = default_layout.new_tab(); let (mut tiled_panes_layout, mut floating_panes_layout) = default_layout.new_tab();
if pane_to_break_is_floating { if pane_to_break_is_floating {
@ -2099,12 +2137,14 @@ impl Screen {
tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?; tab.add_tiled_pane(active_pane, active_pane_id, Some(client_id))?;
tiled_panes_layout.ignore_run_instruction(active_pane_run_instruction.clone()); 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( self.bus.senders.send_to_plugin(PluginInstruction::NewTab(
None, None,
default_shell, default_shell,
Some(tiled_panes_layout), Some(tiled_panes_layout),
floating_panes_layout, floating_panes_layout,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
))?; ))?;
} else { } else {
@ -2122,6 +2162,63 @@ impl Screen {
} }
Ok(()) 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( pub fn break_pane_to_new_tab(
&mut self, &mut self,
direction: Direction, direction: Direction,
@ -2184,6 +2281,57 @@ impl Screen {
self.render(None)?; self.render(None)?;
Ok(()) 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( pub fn replace_pane(
&mut self, &mut self,
new_pane_id: PaneId, new_pane_id: PaneId,
@ -3246,8 +3394,9 @@ pub(crate) fn screen_thread_main(
client_id, client_id,
) => { ) => {
let tab_index = screen.get_new_tab_index(); let tab_index = screen.get_new_tab_index();
let should_change_focus_to_new_tab = true;
pending_tab_ids.insert(tab_index); 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 screen
.bus .bus
.senders .senders
@ -3257,6 +3406,7 @@ pub(crate) fn screen_thread_main(
layout, layout,
floating_panes_layout, floating_panes_layout,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
))?; ))?;
}, },
@ -3267,6 +3417,7 @@ pub(crate) fn screen_thread_main(
new_floating_pane_pids, new_floating_pane_pids,
new_plugin_ids, new_plugin_ids,
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
) => { ) => {
screen.apply_layout( screen.apply_layout(
@ -3276,6 +3427,7 @@ pub(crate) fn screen_thread_main(
new_floating_pane_pids, new_floating_pane_pids,
new_plugin_ids.clone(), new_plugin_ids.clone(),
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
)?; )?;
pending_tab_ids.remove(&tab_index); pending_tab_ids.remove(&tab_index);
@ -3363,7 +3515,13 @@ pub(crate) fn screen_thread_main(
screen.render(None)?; screen.render(None)?;
if create && !tab_exists { if create && !tab_exists {
let tab_index = screen.get_new_tab_index(); 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 screen
.bus .bus
.senders .senders
@ -3373,6 +3531,7 @@ pub(crate) fn screen_thread_main(
None, None,
vec![], vec![],
tab_index, tab_index,
should_change_focus_to_new_tab,
client_id, client_id,
))?; ))?;
} }
@ -4427,6 +4586,34 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::CloseTabWithIndex(tab_index) => { ScreenInstruction::CloseTabWithIndex(tab_index) => {
screen.close_tab_at_index(tab_index).non_fatal() 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(()) Ok(())

View file

@ -256,6 +256,19 @@ impl<'a> LayoutApplier<'a> {
position_and_size, position_and_size,
layout.borderless, 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 { } else {
log::error!( log::error!(
"Failed to find room for run instruction: {:?}", "Failed to find room for run instruction: {:?}",

View file

@ -546,7 +546,7 @@ impl Tab {
auto_layout: bool, auto_layout: bool,
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>, connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
session_is_mirrored: bool, session_is_mirrored: bool,
client_id: ClientId, client_id: Option<ClientId>,
copy_options: CopyOptions, copy_options: CopyOptions,
terminal_emulator_colors: Rc<RefCell<Palette>>, terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>, terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
@ -564,7 +564,9 @@ impl Tab {
}; };
let mut connected_clients = HashSet::new(); let mut connected_clients = HashSet::new();
if let Some(client_id) = client_id {
connected_clients.insert(client_id); connected_clients.insert(client_id);
}
let viewport: Viewport = display_area.into(); let viewport: Viewport = display_area.into();
let viewport = Rc::new(RefCell::new(viewport)); let viewport = Rc::new(RefCell::new(viewport));
let display_area = Rc::new(RefCell::new(display_area)); let display_area = Rc::new(RefCell::new(display_area));

View file

@ -243,7 +243,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -322,7 +322,7 @@ fn create_new_tab_with_swap_layouts(
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -403,7 +403,7 @@ fn create_new_tab_with_os_api(
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -470,7 +470,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -551,7 +551,7 @@ fn create_new_tab_with_mock_pty_writer(
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -623,7 +623,7 @@ fn create_new_tab_with_sixel_support(
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,

View file

@ -184,7 +184,7 @@ fn create_new_tab(size: Size) -> Tab {
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -248,7 +248,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,
@ -318,7 +318,7 @@ fn create_new_tab_with_cell_size(
auto_layout, auto_layout,
connected_clients, connected_clients,
session_is_mirrored, session_is_mirrored,
client_id, Some(client_id),
copy_options, copy_options,
terminal_emulator_colors, terminal_emulator_colors,
terminal_emulator_color_codes, terminal_emulator_color_codes,

View file

@ -387,6 +387,7 @@ impl MockScreen {
floating_pane_ids, floating_pane_ids,
plugin_ids, plugin_ids,
tab_index, tab_index,
true,
self.main_client_id, self.main_client_id,
)); ));
self.last_opened_tab_index = Some(tab_index); self.last_opened_tab_index = Some(tab_index);
@ -471,6 +472,7 @@ impl MockScreen {
floating_pane_ids, floating_pane_ids,
plugin_ids, plugin_ids,
tab_index, tab_index,
true,
self.main_client_id, self.main_client_id,
)); ));
self.last_opened_tab_index = Some(tab_index); self.last_opened_tab_index = Some(tab_index);
@ -502,6 +504,7 @@ impl MockScreen {
vec![], // floating panes ids vec![], // floating panes ids
plugin_ids, plugin_ids,
0, 0,
true,
self.main_client_id, self.main_client_id,
)); ));
self.last_opened_tab_index = Some(tab_index); 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_terminal_ids = vec![(pid, None)];
let new_plugin_ids = HashMap::new(); let new_plugin_ids = HashMap::new();
screen screen
.new_tab(tab_index, (vec![], vec![]), None, client_id) .new_tab(tab_index, (vec![], vec![]), None, Some(client_id))
.expect("TEST"); .expect("TEST");
screen screen
.apply_layout( .apply_layout(
@ -659,6 +662,7 @@ fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) {
vec![], // new floating terminal ids vec![], // new floating terminal ids
new_plugin_ids, new_plugin_ids,
tab_index, tab_index,
true,
client_id, client_id,
) )
.expect("TEST"); .expect("TEST");
@ -3248,6 +3252,7 @@ pub fn screen_can_break_pane_to_a_new_tab() {
vec![], // floating panes ids vec![], // floating panes ids
Default::default(), Default::default(),
1, 1,
true,
1, 1,
)); ));
std::thread::sleep(std::time::Duration::from_millis(100)); 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 vec![], // floating panes ids
Default::default(), Default::default(),
1, 1,
true,
1, 1,
)); ));
std::thread::sleep(std::time::Duration::from_millis(200)); 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 vec![], // floating panes ids
Default::default(), Default::default(),
1, 1,
true,
1, 1,
)); ));
std::thread::sleep(std::time::Duration::from_millis(100)); 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 vec![], // floating panes ids
Default::default(), Default::default(),
1, 1,
true,
1, 1,
)); ));
std::thread::sleep(std::time::Duration::from_millis(100)); std::thread::sleep(std::time::Duration::from_millis(100));

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2849 assertion_line: 3374
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_pane_to_eject ──────────────┐
06 (C): │ 06 (C): │ │ │
07 (C): │ ┌ floating_pane_to_eject ──────────────┐ 07 (C): │ │ │
08 (C): │ │ │ │ 08 (C): │ │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2849 assertion_line: 3374
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #2 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_pane_to_eject ──────────────┐
06 (C): │ 06 (C): │ │ │
07 (C): │ ┌ floating_pane_to_eject ──────────────┐ 07 (C): │ │ │
08 (C): │ │ │ │ 08 (C): │ │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3512 assertion_line: 3516
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐
06 (C): │ 06 (C): │ │Loading file:/path/to/fake/plugin │
07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ 07 (C): │ │ │
08 (C): │ │Loading file:/path/to/fake/plugin │ 08 (C): │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3512 assertion_line: 3516
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐
06 (C): │ 06 (C): │ │Loading file:/path/to/fake/plugin │
07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ 07 (C): │ │ │
08 (C): │ │Loading file:/path/to/fake/plugin │ 08 (C): │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3512 assertion_line: 3516
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐
06 (C): │ 06 (C): │ │Loading file:/path/to/fake/plugin │
07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ 07 (C): │ │ │
08 (C): │ │Loading file:/path/to/fake/plugin │ 08 (C): │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3512 assertion_line: 3516
expression: "format!(\"{}\", snapshot)" expression: "format!(\"{}\", snapshot)"
--- ---
00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐ 00 (C): ┌ Pane #1 ─────────────────────────────────────────────────────────────────────┐
@ -8,18 +8,18 @@ expression: "format!(\"{}\", snapshot)"
02 (C): │ │ 02 (C): │ │
03 (C): │ │ 03 (C): │ │
04 (C): │ │ 04 (C): │ │
05 (C): │ 05 (C): │ ┌ floating_plugin_pane_to_eject ───────┐
06 (C): │ 06 (C): │ │Loading file:/path/to/fake/plugin │
07 (C): │ ┌ floating_plugin_pane_to_eject ───────┐ 07 (C): │ │ │
08 (C): │ │Loading file:/path/to/fake/plugin │ 08 (C): │ │ │
09 (C): │ │ │ │ 09 (C): │ │ │ │
10 (C): │ │ │ │ 10 (C): │ │ │ │
11 (C): │ │ │ │ 11 (C): │ │ │ │
12 (C): │ │ │ │ 12 (C): │ │ │ │
13 (C): │ │ │ │ 13 (C): │ │ │ │
14 (C): │ │ │ 14 (C): │ └──────────────────────────────────────┘
15 (C): │ │ │ 15 (C): │
16 (C): │ └──────────────────────────────────────┘ 16 (C): │
17 (C): │ │ 17 (C): │ │
18 (C): │ │ 18 (C): │ │
19 (C): └──────────────────────────────────────────────────────────────────────────────┘ 19 (C): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2246 assertion_line: 2665
expression: "format!(\"{:#?}\", new_tab_action)" expression: "format!(\"{:#?}\", new_tab_action)"
--- ---
Some( Some(
@ -60,6 +60,7 @@ Some(
), ),
[], [],
0, 0,
true,
1, 1,
), ),
) )

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 2292 assertion_line: 2711
expression: "format!(\"{:#?}\", new_tab_instruction)" expression: "format!(\"{:#?}\", new_tab_instruction)"
--- ---
NewTab( NewTab(
@ -87,5 +87,6 @@ NewTab(
), ),
[], [],
1, 1,
true,
10, 10,
) )

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 1825 assertion_line: 2183
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" 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]

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 1065 assertion_line: 1423
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" 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]

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-server/src/./unit/screen_tests.rs source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 1039 assertion_line: 1397
expression: "format!(\"{:?}\", * received_pty_instructions.lock().unwrap())" 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]

View file

@ -1030,6 +1030,38 @@ where
unsafe { host_run_plugin_command() }; 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<String>,
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 // Utility Functions
#[allow(unused)] #[allow(unused)]

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32, pub name: i32,
#[prost( #[prost(
oneof = "plugin_command::Payload", 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<plugin_command::Payload>, pub payload: ::core::option::Option<plugin_command::Payload>,
} }
@ -164,10 +164,34 @@ pub mod plugin_command {
), ),
#[prost(message, tag = "83")] #[prost(message, tag = "83")]
CloseTabWithIndexPayload(super::CloseTabWithIndexPayload), CloseTabWithIndexPayload(super::CloseTabWithIndexPayload),
#[prost(message, tag = "84")]
BreakPanesToNewTabPayload(super::BreakPanesToNewTabPayload),
#[prost(message, tag = "85")]
BreakPanesToTabWithIndexPayload(super::BreakPanesToTabWithIndexPayload),
} }
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct BreakPanesToTabWithIndexPayload {
#[prost(message, repeated, tag = "1")]
pub pane_ids: ::prost::alloc::vec::Vec<PaneId>,
#[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<PaneId>,
#[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 { pub struct MovePaneWithPaneIdPayload {
#[prost(message, optional, tag = "1")] #[prost(message, optional, tag = "1")]
pub pane_id: ::core::option::Option<PaneId>, pub pane_id: ::core::option::Option<PaneId>,
@ -634,6 +658,8 @@ pub enum CommandName {
TogglePaneIdFullscreen = 105, TogglePaneIdFullscreen = 105,
TogglePaneEmbedOrEjectForPaneId = 106, TogglePaneEmbedOrEjectForPaneId = 106,
CloseTabWithIndex = 107, CloseTabWithIndex = 107,
BreakPanesToNewTab = 108,
BreakPanesToTabWithIndex = 109,
} }
impl CommandName { impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -752,6 +778,8 @@ impl CommandName {
"TogglePaneEmbedOrEjectForPaneId" "TogglePaneEmbedOrEjectForPaneId"
} }
CommandName::CloseTabWithIndex => "CloseTabWithIndex", CommandName::CloseTabWithIndex => "CloseTabWithIndex",
CommandName::BreakPanesToNewTab => "BreakPanesToNewTab",
CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -867,6 +895,8 @@ impl CommandName {
Some(Self::TogglePaneEmbedOrEjectForPaneId) Some(Self::TogglePaneEmbedOrEjectForPaneId)
} }
"CloseTabWithIndex" => Some(Self::CloseTabWithIndex), "CloseTabWithIndex" => Some(Self::CloseTabWithIndex),
"BreakPanesToNewTab" => Some(Self::BreakPanesToNewTab),
"BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex),
_ => None, _ => None,
} }
} }

View file

@ -1836,4 +1836,10 @@ pub enum PluginCommand {
TogglePaneIdFullscreen(PaneId), TogglePaneIdFullscreen(PaneId),
TogglePaneEmbedOrEjectForPaneId(PaneId), TogglePaneEmbedOrEjectForPaneId(PaneId),
CloseTabWithIndex(usize), // usize - tab_index CloseTabWithIndex(usize), // usize - tab_index
BreakPanesToNewTab(Vec<PaneId>, Option<String>, bool), // bool -
// should_change_focus_to_new_tab,
// Option<String> - optional name for
// the new tab
BreakPanesToTabWithIndex(Vec<PaneId>, usize, bool), // usize - tab_index, bool -
// should_change_focus_to_new_tab
} }

View file

@ -370,6 +370,8 @@ pub enum ScreenContext {
TogglePaneIdFullscreen, TogglePaneIdFullscreen,
TogglePaneEmbedOrEjectForPaneId, TogglePaneEmbedOrEjectForPaneId,
CloseTabWithIndex, CloseTabWithIndex,
BreakPanesToNewTab,
BreakPanesToTabWithIndex,
} }
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.

View file

@ -938,7 +938,16 @@ impl TiledPaneLayout {
.len() .len()
.saturating_sub(successfully_ignored) .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); run_instructions.remove(position);
} }
} }

View file

@ -119,6 +119,8 @@ enum CommandName {
TogglePaneIdFullscreen = 105; TogglePaneIdFullscreen = 105;
TogglePaneEmbedOrEjectForPaneId = 106; TogglePaneEmbedOrEjectForPaneId = 106;
CloseTabWithIndex = 107; CloseTabWithIndex = 107;
BreakPanesToNewTab = 108;
BreakPanesToTabWithIndex = 109;
} }
message PluginCommand { message PluginCommand {
@ -197,9 +199,23 @@ message PluginCommand {
TogglePaneIdFullscreenPayload toggle_pane_id_fullscreen_payload = 81; TogglePaneIdFullscreenPayload toggle_pane_id_fullscreen_payload = 81;
TogglePaneEmbedOrEjectForPaneIdPayload toggle_pane_embed_or_eject_for_pane_id_payload = 82; TogglePaneEmbedOrEjectForPaneIdPayload toggle_pane_embed_or_eject_for_pane_id_payload = 82;
CloseTabWithIndexPayload close_tab_with_index_payload = 83; 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 { message MovePaneWithPaneIdPayload {
PaneId pane_id = 1; PaneId pane_id = 1;
} }

View file

@ -3,9 +3,10 @@ pub use super::generated_api::api::{
event::{EventNameList as ProtobufEventNameList, Header}, event::{EventNameList as ProtobufEventNameList, Header},
input_mode::InputMode as ProtobufInputMode, input_mode::InputMode as ProtobufInputMode,
plugin_command::{ plugin_command::{
plugin_command::Payload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload,
CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseTabWithIndexPayload, CommandName,
EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, ContextItem, EditScrollbackForPaneWithIdPayload, EnvVariable, ExecCmdPayload,
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue, FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload,
@ -1174,6 +1175,34 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
), ),
_ => Err("Mismatched payload for CloseTabWithIndex"), _ => 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"), None => Err("Unrecognized plugin command"),
} }
} }
@ -1922,6 +1951,40 @@ impl TryFrom<PluginCommand> 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,
},
)),
}),
} }
} }
} }