feat(ux): stack panes command (#3905)

* working across tabs and floating panes through the cli

* finalize cli command

* plugin api

* style(fmt): rustfmt

* fix: re-focus pane in stack if it was focused

* style(fmt): rustfmt

* remove outdated comment
This commit is contained in:
Aram Drevekenin 2024-12-31 12:37:23 +01:00 committed by GitHub
parent cd24da052e
commit 42adc8cd04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 373 additions and 10 deletions

View file

@ -818,6 +818,16 @@ impl TiledPanes {
},
}
}
pub fn set_geom_for_pane_with_id(&mut self, pane_id: &PaneId, geom: PaneGeom) {
match self.panes.get_mut(pane_id) {
Some(pane) => {
pane.set_geom(geom);
},
None => {
log::error!("Failed to find pane with id: {:?}", pane_id);
},
}
}
pub fn resize(&mut self, new_screen_size: Size) {
// this is blocked out to appease the borrow checker
{
@ -1827,6 +1837,9 @@ impl TiledPanes {
}
pane_infos
}
pub fn pane_id_is_focused(&self, pane_id: &PaneId) -> bool {
self.active_panes.pane_id_is_focused(pane_id)
}
pub fn update_pane_themes(&mut self, theme: Palette) {
self.style.colors = theme;
for pane in self.panes.values_mut() {
@ -1844,6 +1857,14 @@ impl TiledPanes {
pane.update_rounded_corners(rounded_corners);
}
}
pub fn stack_panes(
&mut self,
root_pane_id: PaneId,
pane_count_in_stack: usize,
) -> Vec<PaneGeom> {
StackedPanes::new_from_btreemap(&mut self.panes, &self.panes_to_hide)
.new_stack(root_pane_id, pane_count_in_stack)
}
}
#[allow(clippy::borrowed_box)]

View file

@ -310,7 +310,6 @@ impl<'a> StackedPanes<'a> {
adjust_stack_geoms(new_flexible_pane_geom)?;
} else {
if new_rows < all_stacked_pane_positions.len() {
// TODO: test this!! we don't want crashes...
return Err(anyhow!("Not enough room for stacked panes"));
}
let rows_deficit = current_rows - new_rows;
@ -465,6 +464,35 @@ impl<'a> StackedPanes<'a> {
}
Err(anyhow!("Not enough room for another pane!"))
}
pub fn new_stack(&mut self, root_pane_id: PaneId, pane_count_in_stack: usize) -> Vec<PaneGeom> {
let mut stacked_geoms = vec![];
let panes = self.panes.borrow();
let running_stack_geom = panes.get(&root_pane_id).map(|p| p.position_and_size());
let Some(mut running_stack_geom) = running_stack_geom else {
log::error!("Pane not found"); // TODO: better error
return stacked_geoms;
};
running_stack_geom.is_stacked = true;
let mut pane_index_in_stack = 0;
loop {
if pane_index_in_stack == pane_count_in_stack {
break;
}
let is_last_pane_in_stack =
pane_index_in_stack == pane_count_in_stack.saturating_sub(1);
let mut geom_for_pane = running_stack_geom.clone();
if !is_last_pane_in_stack {
geom_for_pane.rows = Dimension::fixed(1);
running_stack_geom.y += 1;
running_stack_geom
.rows
.set_inner(running_stack_geom.rows.as_usize().saturating_sub(1));
}
stacked_geoms.push(geom_for_pane);
pane_index_in_stack += 1;
}
stacked_geoms
}
fn get_all_stacks(&self) -> Result<Vec<Vec<(PaneId, PaneGeom)>>> {
let err_context = || "Failed to get positions in stack";
let panes = self.panes.borrow();

View file

@ -358,6 +358,9 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
PluginCommand::SetFloatingPanePinned(pane_id, should_be_pinned) => {
set_floating_pane_pinned(env, pane_id.into(), should_be_pinned)
},
PluginCommand::StackPanes(pane_ids) => {
stack_panes(env, pane_ids.into_iter().map(|p_id| p_id.into()).collect())
},
},
(PermissionStatus::Denied, permission) => {
log::error!(
@ -1521,6 +1524,12 @@ fn set_floating_pane_pinned(env: &PluginEnv, pane_id: PaneId, should_be_pinned:
});
}
fn stack_panes(env: &PluginEnv, pane_ids: Vec<PaneId>) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::StackPanes(pane_ids));
}
fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) {
if !folder_to_scan.starts_with("/host") {
log::error!(
@ -1940,6 +1949,7 @@ fn check_command_permission(
| PluginCommand::ReloadPlugin(..)
| PluginCommand::LoadNewPlugin { .. }
| PluginCommand::SetFloatingPanePinned(..)
| PluginCommand::StackPanes(..)
| PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState,
PluginCommand::UnblockCliPipeInput(..)
| PluginCommand::BlockCliPipeInput(..)

View file

@ -974,6 +974,13 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::TogglePanePinned(client_id))
.with_context(err_context)?;
},
Action::StackPanes(pane_ids_to_stack) => {
senders
.send_to_screen(ScreenInstruction::StackPanes(
pane_ids_to_stack.iter().map(|p| PaneId::from(*p)).collect(),
))
.with_context(err_context)?;
},
}
Ok(should_break)
}

View file

@ -407,6 +407,7 @@ pub enum ScreenInstruction {
ListClientsToPlugin(PluginId, ClientId),
TogglePanePinned(ClientId),
SetFloatingPanePinned(PaneId, bool),
StackPanes(Vec<PaneId>),
}
impl From<&ScreenInstruction> for ScreenContext {
@ -621,6 +622,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ListClientsToPlugin(..) => ScreenContext::ListClientsToPlugin,
ScreenInstruction::TogglePanePinned(..) => ScreenContext::TogglePanePinned,
ScreenInstruction::SetFloatingPanePinned(..) => ScreenContext::SetFloatingPanePinned,
ScreenInstruction::StackPanes(..) => ScreenContext::StackPanes,
}
}
}
@ -2376,7 +2378,6 @@ impl Screen {
pane_title: Option<InitialTitle>,
client_id_tab_index_or_pane_id: ClientTabIndexOrPaneId,
) -> Result<()> {
let err_context = || format!("failed to replace pane");
let suppress_pane = |tab: &mut Tab, pane_id: PaneId, new_pane_id: PaneId| {
let _ = tab.suppress_pane_and_replace_with_pid(pane_id, new_pane_id, run);
if let Some(pane_title) = pane_title {
@ -2522,6 +2523,62 @@ impl Screen {
);
}
}
pub fn stack_panes(&mut self, mut pane_ids_to_stack: Vec<PaneId>) {
if pane_ids_to_stack.is_empty() {
log::error!("Got an empty list of pane_ids to stack");
return;
}
let stack_size = pane_ids_to_stack.len();
let root_pane_id = pane_ids_to_stack.remove(0);
let Some(root_tab_id) = self
.tabs
.iter()
.find_map(|(tab_id, tab)| {
if tab.has_pane_with_pid(&root_pane_id) {
Some(tab_id)
} else {
None
}
})
.copied()
else {
log::error!("Failed to find tab for root_pane_id: {:?}", root_pane_id);
return;
};
let target_tab_has_room_for_stack = self
.tabs
.get(&root_tab_id)
.map(|t| t.has_room_for_stack(root_pane_id, stack_size))
.unwrap_or(false);
if !target_tab_has_room_for_stack {
log::error!("No room for stack with root pane id: {:?}", root_pane_id);
return;
}
let mut panes_to_stack = vec![];
for (tab_id, tab) in self.tabs.iter_mut() {
if tab_id == &root_tab_id {
// we do this before we extract panes so that the extraction won't trigger a
// relayout according to the next swapped tiled pane
tab.set_tiled_panes_damaged();
}
for pane_id in &pane_ids_to_stack {
if tab.has_pane_with_pid(&pane_id) {
match tab.extract_pane(*pane_id, false) {
Some(pane) => {
panes_to_stack.push(pane);
},
None => {
log::error!("Failed to extract pane: {:?}", pane_id);
},
}
}
}
}
self.tabs
.get_mut(&root_tab_id)
.map(|t| t.stack_panes(root_pane_id, panes_to_stack));
}
fn unblock_input(&self) -> Result<()> {
self.bus
.senders
@ -3894,7 +3951,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!(
screen,
client_id,
|tab: &mut Tab, client_id: ClientId| tab.previous_swap_layout(),
|tab: &mut Tab, _client_id: ClientId| tab.previous_swap_layout(),
?
);
screen.render(None)?;
@ -3905,7 +3962,7 @@ pub(crate) fn screen_thread_main(
active_tab_and_connected_client_id!(
screen,
client_id,
|tab: &mut Tab, client_id: ClientId| tab.next_swap_layout(),
|tab: &mut Tab, _client_id: ClientId| tab.next_swap_layout(),
?
);
screen.render(None)?;
@ -4739,6 +4796,11 @@ pub(crate) fn screen_thread_main(
ScreenInstruction::SetFloatingPanePinned(pane_id, should_be_pinned) => {
screen.set_floating_pane_pinned(pane_id, should_be_pinned);
},
ScreenInstruction::StackPanes(pane_ids_to_stack) => {
screen.stack_panes(pane_ids_to_stack);
let _ = screen.unblock_input();
let _ = screen.render(None);
},
}
}
Ok(())

View file

@ -4131,7 +4131,7 @@ impl Tab {
},
}
}
pub fn suppress_pane(&mut self, pane_id: PaneId, client_id: Option<ClientId>) {
pub fn suppress_pane(&mut self, pane_id: PaneId, _client_id: Option<ClientId>) {
// this method places a pane in the suppressed pane with its own ID - this means we'll
// not take it out of there when another pane is closed (eg. like happens with the
// scrollback editor), but it has to take itself out on its own (eg. a plugin using the
@ -4388,6 +4388,55 @@ impl Tab {
self.set_force_render();
}
}
pub fn has_room_for_stack(&self, root_pane_id: PaneId, stack_size: usize) -> bool {
if self.floating_panes.panes_contain(&root_pane_id)
|| self.suppressed_panes.contains_key(&root_pane_id)
{
log::error!("Root pane of stack cannot be floating or suppressed");
return false;
}
self.get_pane_with_id(root_pane_id)
.map(|p| p.position_and_size().rows.as_usize() >= stack_size + MIN_TERMINAL_HEIGHT)
.unwrap_or(false)
}
pub fn set_tiled_panes_damaged(&mut self) {
self.swap_layouts.set_is_tiled_damaged();
}
pub fn stack_panes(&mut self, root_pane_id: PaneId, mut panes_to_stack: Vec<Box<dyn Pane>>) {
if panes_to_stack.is_empty() {
// nothing to do
return;
}
self.swap_layouts.set_is_tiled_damaged(); // TODO: verify we can do all the below first
// + 1 for the root pane
let mut stack_geoms = self
.tiled_panes
.stack_panes(root_pane_id, panes_to_stack.len() + 1);
if stack_geoms.is_empty() {
log::error!("Failed to find room for stacked panes");
return;
}
self.tiled_panes
.set_geom_for_pane_with_id(&root_pane_id, stack_geoms.remove(0));
let mut focused_pane_id_in_stack = None;
for mut pane in panes_to_stack.drain(..) {
let pane_id = pane.pid();
let stack_geom = stack_geoms.remove(0);
pane.set_geom(stack_geom);
self.tiled_panes.add_pane_with_existing_geom(pane_id, pane);
if self.tiled_panes.pane_id_is_focused(&pane_id) {
focused_pane_id_in_stack = Some(pane_id);
}
}
// if we had a focused pane in the stack, we expand it
if let Some(focused_pane_id_in_stack) = focused_pane_id_in_stack {
self.tiled_panes
.expand_pane_in_stack(focused_pane_id_in_stack);
} else if self.tiled_panes.pane_id_is_focused(&root_pane_id) {
self.tiled_panes.expand_pane_in_stack(root_pane_id);
}
}
fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane {
let next_terminal_position = self.get_next_terminal_position();
let mut new_pane = TerminalPane::new(

View file

@ -3647,3 +3647,43 @@ pub fn screen_can_move_pane_to_a_new_tab_left() {
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_stack_panes_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![
TiledPaneLayout::default(),
TiledPaneLayout::default(),
TiledPaneLayout::default(),
TiledPaneLayout::default(),
TiledPaneLayout::default(),
];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let stack_panes_action = CliAction::StackPanes {
pane_ids: vec!["1".to_owned(), "2".to_owned(), "3".to_owned()],
};
send_cli_action_to_server(&session_metadata, stack_panes_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}

View file

@ -0,0 +1,16 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3687
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ─────┐┌ Pane #2 ─────────────────────────────────────┐┌ Pane #5 ─────┐
01 (C): │ │┌ Pane #3 ─────────────────────────────────────┐│ │
02 (C): │ │┌ Pane #4 ─────────────────────────────────────┐│ │
03 (C): │ ││ ││ │
04 (C): │ ││ ││ │
05 (C): │ ││ ││ │
06 (C): │ ││ ││ │
07 (C): │ ││ ││ │
08 (C): │ ││ ││ │
09 (C): └──────────────┘└──────────────────────────────────────────────┘└──────────────┘

View file

@ -0,0 +1,6 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3689
expression: "format!(\"{}\", snapshot_count)"
---
2

View file

@ -0,0 +1,16 @@
---
source: zellij-server/src/./unit/screen_tests.rs
assertion_line: 3687
expression: "format!(\"{}\", snapshot)"
---
00 (C): ┌ Pane #1 ─────┐┌ Pane #2 ─────┐┌ Pane #3 ─────┐┌ Pane #4 ─────┐┌ Pane #5 ─────┐
01 (C): │ ││ ││ ││ ││ │
02 (C): │ ││ ││ ││ ││ │
03 (C): │ ││ ││ ││ ││ │
04 (C): │ ││ ││ ││ ││ │
05 (C): │ ││ ││ ││ ││ │
06 (C): │ ││ ││ ││ ││ │
07 (C): │ ││ ││ ││ ││ │
08 (C): │ ││ ││ ││ ││ │
09 (C): └──────────────┘└──────────────┘└──────────────┘└──────────────┘└──────────────┘

View file

@ -1142,6 +1142,13 @@ pub fn set_floating_pane_pinned(pane_id: PaneId, should_be_pinned: bool) {
unsafe { host_run_plugin_command() };
}
pub fn stack_panes(pane_ids: Vec<PaneId>) {
let plugin_command = PluginCommand::StackPanes(pane_ids);
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, 84, 85, 86, 87, 88, 89, 90"
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, 86, 87, 88, 89, 90, 91"
)]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
@ -178,10 +178,18 @@ pub mod plugin_command {
ChangeHostFolderPayload(super::ChangeHostFolderPayload),
#[prost(message, tag = "90")]
SetFloatingPanePinnedPayload(super::SetFloatingPanePinnedPayload),
#[prost(message, tag = "91")]
StackPanesPayload(super::StackPanesPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StackPanesPayload {
#[prost(message, repeated, tag = "1")]
pub pane_ids: ::prost::alloc::vec::Vec<PaneId>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SetFloatingPanePinnedPayload {
#[prost(message, optional, tag = "1")]
pub pane_id: ::core::option::Option<PaneId>,
@ -738,6 +746,7 @@ pub enum CommandName {
ListClients = 113,
ChangeHostFolder = 114,
SetFloatingPanePinned = 115,
StackPanes = 116,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -864,6 +873,7 @@ impl CommandName {
CommandName::ListClients => "ListClients",
CommandName::ChangeHostFolder => "ChangeHostFolder",
CommandName::SetFloatingPanePinned => "SetFloatingPanePinned",
CommandName::StackPanes => "StackPanes",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -987,6 +997,7 @@ impl CommandName {
"ListClients" => Some(Self::ListClients),
"ChangeHostFolder" => Some(Self::ChangeHostFolder),
"SetFloatingPanePinned" => Some(Self::SetFloatingPanePinned),
"StackPanes" => Some(Self::StackPanes),
_ => None,
}
}

View file

@ -760,4 +760,15 @@ tail -f /tmp/my-live-logfile | zellij action pipe --name logs --plugin https://e
},
ListClients,
TogglePanePinned,
/// Stack pane ids
/// Ids are a space separated list of pane ids.
/// They should either be in the form of `terminal_<int>` (eg. terminal_1), `plugin_<int>` (eg.
/// plugin_1) or bare integers in which case they'll be considered terminals (eg. 1 is
/// the equivalent of terminal_1)
///
/// Example: zellij action stack-panes -- terminal_1 plugin_2 3
StackPanes {
#[clap(last(true), required(true))]
pane_ids: Vec<String>,
},
}

View file

@ -1902,4 +1902,5 @@ pub enum PluginCommand {
ListClients,
ChangeHostFolder(PathBuf),
SetFloatingPanePinned(PaneId, bool), // bool -> should be pinned
StackPanes(Vec<PaneId>),
}

View file

@ -375,6 +375,7 @@ pub enum ScreenContext {
ListClientsToPlugin,
TogglePanePinned,
SetFloatingPanePinned,
StackPanes,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.

View file

@ -6,7 +6,7 @@ use super::layout::{
SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
};
use crate::cli::CliAction;
use crate::data::{Direction, KeyWithModifier, Resize};
use crate::data::{Direction, KeyWithModifier, PaneId, Resize};
use crate::data::{FloatingPaneCoordinates, InputMode};
use crate::home::{find_default_config_dir, get_layout_dir};
use crate::input::config::{Config, ConfigError, KdlError};
@ -301,6 +301,7 @@ pub enum Action {
},
ListClients,
TogglePanePinned,
StackPanes(Vec<PaneId>),
}
impl Action {
@ -742,6 +743,53 @@ impl Action {
},
CliAction::ListClients => Ok(vec![Action::ListClients]),
CliAction::TogglePanePinned => Ok(vec![Action::TogglePanePinned]),
CliAction::StackPanes { pane_ids } => {
let mut malformed_ids = vec![];
let pane_ids = pane_ids
.iter()
.filter_map(|stringified_pane_id| {
if let Some(terminal_pane_id) =
stringified_pane_id.strip_prefix("terminal_")
{
u32::from_str_radix(terminal_pane_id, 10)
.ok()
.or_else(|| {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Terminal(id))
} else if let Some(plugin_pane_id) =
stringified_pane_id.strip_prefix("plugin_")
{
u32::from_str_radix(plugin_pane_id, 10)
.ok()
.or_else(|| {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Plugin(id))
} else {
u32::from_str_radix(stringified_pane_id, 10)
.ok()
.or_else(|| {
malformed_ids.push(stringified_pane_id.to_owned());
None
})
.map(|id| PaneId::Terminal(id))
}
})
.collect();
if !malformed_ids.is_empty() {
Err(
format!(
"Malformed pane ids: {}, expecting a space separated list of either a bare integer (eg. 1), a terminal pane id (eg. terminal_1) or a plugin pane id (eg. plugin_1)",
malformed_ids.join(", ")
)
)
} else {
Ok(vec![Action::StackPanes(pane_ids)])
}
},
}
}
pub fn launches_plugin(&self, plugin_url: &str) -> bool {

View file

@ -1301,6 +1301,7 @@ impl TryFrom<Action> for ProtobufAction {
| Action::DumpLayout
| Action::CliPipe { .. }
| Action::ListClients
| Action::StackPanes(..)
| Action::SkipConfirm(..) => Err("Unsupported action"),
}
}

View file

@ -129,6 +129,7 @@ enum CommandName {
ListClients = 113;
ChangeHostFolder = 114;
SetFloatingPanePinned = 115;
StackPanes = 116;
}
message PluginCommand {
@ -214,9 +215,14 @@ message PluginCommand {
RebindKeysPayload rebind_keys_payload = 88;
ChangeHostFolderPayload change_host_folder_payload = 89;
SetFloatingPanePinnedPayload set_floating_pane_pinned_payload = 90;
StackPanesPayload stack_panes_payload = 91;
}
}
message StackPanesPayload {
repeated PaneId pane_ids = 1;
}
message SetFloatingPanePinnedPayload {
PaneId pane_id = 1;
bool should_be_pinned = 2;

View file

@ -19,9 +19,10 @@ pub use super::generated_api::api::{
RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload,
RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetFloatingPanePinnedPayload,
SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload,
SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload,
UnsubscribePayload, WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
SetTimeoutPayload, ShowPaneWithIdPayload, StackPanesPayload, SubscribePayload,
SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
@ -1328,6 +1329,18 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for SetFloatingPanePinned"),
},
Some(CommandName::StackPanes) => match protobuf_plugin_command.payload {
Some(Payload::StackPanesPayload(stack_panes_payload)) => {
Ok(PluginCommand::StackPanes(
stack_panes_payload
.pane_ids
.into_iter()
.filter_map(|p_id| p_id.try_into().ok())
.collect(),
))
},
_ => Err("Mismatched payload for SetFloatingPanePinned"),
},
None => Err("Unrecognized plugin command"),
}
}
@ -2172,6 +2185,15 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
)),
})
},
PluginCommand::StackPanes(pane_ids) => Ok(ProtobufPluginCommand {
name: CommandName::StackPanes as i32,
payload: Some(Payload::StackPanesPayload(StackPanesPayload {
pane_ids: pane_ids
.into_iter()
.filter_map(|p_id| p_id.try_into().ok())
.collect(),
})),
}),
}
}
}