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]) => {
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) => {

View file

@ -74,6 +74,7 @@ pub enum PluginInstruction {
Option<TiledPaneLayout>,
Vec<FloatingPaneLayout>,
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,
)));
},

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(
zellij_cwd: Option<PathBuf>,
) -> (
@ -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));
}

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) => {
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<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.
//
// 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(..)

View file

@ -64,6 +64,7 @@ pub enum PtyInstruction {
Vec<FloatingPaneLayout>,
usize, // tab_index
HashMap<RunPluginOrAlias, Vec<u32>>, // 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<Layout>) -> 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<Layout>) -> 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<TerminalAction>,
plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>,
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)?;

View file

@ -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(())

View file

@ -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: {:?}",

View file

@ -546,7 +546,7 @@ impl Tab {
auto_layout: bool,
connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>,
session_is_mirrored: bool,
client_id: ClientId,
client_id: Option<ClientId>,
copy_options: CopyOptions,
terminal_emulator_colors: Rc<RefCell<Palette>>,
terminal_emulator_color_codes: Rc<RefCell<HashMap<usize, String>>>,
@ -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));

View file

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

View file

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

View file

@ -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));

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

@ -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): └──────────────────────────────────────────────────────────────────────────────┘

View file

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

View file

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

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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<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
#[allow(unused)]

View file

@ -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<plugin_command::Payload>,
}
@ -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<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 {
#[prost(message, optional, tag = "1")]
pub pane_id: ::core::option::Option<PaneId>,
@ -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,
}
}

View file

@ -1836,4 +1836,10 @@ pub enum PluginCommand {
TogglePaneIdFullscreen(PaneId),
TogglePaneEmbedOrEjectForPaneId(PaneId),
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,
TogglePaneEmbedOrEjectForPaneId,
CloseTabWithIndex,
BreakPanesToNewTab,
BreakPanesToTabWithIndex,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.

View file

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

View file

@ -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;
}

View file

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