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:
parent
cd24da052e
commit
42adc8cd04
19 changed files with 373 additions and 10 deletions
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(..)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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): └──────────────┘└──────────────────────────────────────────────┘└──────────────┘
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
source: zellij-server/src/./unit/screen_tests.rs
|
||||
assertion_line: 3689
|
||||
expression: "format!(\"{}\", snapshot_count)"
|
||||
---
|
||||
2
|
||||
|
|
@ -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): └──────────────┘└──────────────┘└──────────────┘└──────────────┘└──────────────┘
|
||||
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1902,4 +1902,5 @@ pub enum PluginCommand {
|
|||
ListClients,
|
||||
ChangeHostFolder(PathBuf),
|
||||
SetFloatingPanePinned(PaneId, bool), // bool -> should be pinned
|
||||
StackPanes(Vec<PaneId>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ pub enum ScreenContext {
|
|||
ListClientsToPlugin,
|
||||
TogglePanePinned,
|
||||
SetFloatingPanePinned,
|
||||
StackPanes,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1301,6 +1301,7 @@ impl TryFrom<Action> for ProtobufAction {
|
|||
| Action::DumpLayout
|
||||
| Action::CliPipe { .. }
|
||||
| Action::ListClients
|
||||
| Action::StackPanes(..)
|
||||
| Action::SkipConfirm(..) => Err("Unsupported action"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue