From 1776d240dade5ef1d254651ba53d0ab6d99e10ff Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 29 Aug 2024 17:35:21 +0200 Subject: [PATCH] feat(plugins): add plugin APIs to affect other panes (#3576) * resize_pane_with_id and close_pane_with_id * focus_pane_with_id and edit_scrollback_for_pane_with_id * write_to_pane_id and write_chars_to_pane_id * lots more commands * style(fmt): rustfmt --- .../fixture-plugin-for-tests/src/main.rs | 51 + zellij-server/src/panes/floating_panes/mod.rs | 158 ++- zellij-server/src/panes/grid.rs | 2 +- zellij-server/src/panes/terminal_pane.rs | 2 +- zellij-server/src/panes/tiled_panes/mod.rs | 611 ++++----- .../src/plugins/unit/plugin_tests.rs | 1136 +++++++++++++++++ ...ear_screen_for_pane_id_plugin_command.snap | 12 + ...__close_tab_with_index_plugin_command.snap | 10 + ...lback_for_pane_with_id_plugin_command.snap | 12 + ...h_pane_id_in_direction_plugin_command.snap | 13 + ...move_pane_with_pane_id_plugin_command.snap | 12 + ...scroll_down_in_pane_id_plugin_command.snap | 12 + ...e_scroll_up_in_pane_id_plugin_command.snap | 12 + ...s__resize_pane_with_id_plugin_command.snap | 19 + ...scroll_down_in_pane_id_plugin_command.snap | 12 + ...l_to_bottom_in_pane_id_plugin_command.snap | 12 + ...roll_to_top_in_pane_id_plugin_command.snap | 12 + ...__scroll_up_in_pane_id_plugin_command.snap | 12 + ...d_or_eject_for_pane_id_plugin_command.snap | 12 + ...gle_pane_id_fullscreen_plugin_command.snap | 12 + ...write_chars_to_pane_id_plugin_command.snap | 18 + ...ests__write_to_pane_id_plugin_command.snap | 17 + zellij-server/src/plugins/zellij_exports.rs | 166 ++- zellij-server/src/pty.rs | 23 +- zellij-server/src/screen.rs | 274 +++- zellij-server/src/tab/mod.rs | 430 ++++++- zellij-server/src/unit/screen_tests.rs | 7 +- zellij-tile/src/shim.rs | 173 +++ .../assets/prost/api.plugin_command.rs | 194 ++- zellij-utils/src/data.rs | 16 + zellij-utils/src/errors.rs | 15 + .../src/plugin_api/plugin_command.proto | 100 ++ zellij-utils/src/plugin_api/plugin_command.rs | 333 ++++- 33 files changed, 3469 insertions(+), 431 deletions(-) create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__clear_screen_for_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_tab_with_index_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__edit_scrollback_for_pane_with_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_in_direction_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_down_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_up_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__resize_pane_with_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_down_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_bottom_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_top_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_up_in_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_embed_or_eject_for_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_id_fullscreen_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_to_pane_id_plugin_command.snap create mode 100644 zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_to_pane_id_plugin_command.snap diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index b0915f29..85300612 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -364,6 +364,57 @@ impl ZellijPlugin for State { BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Alt]) => { rerun_command_pane(1); }, + BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Alt]) => { + resize_pane_with_id( + ResizeStrategy::new(Resize::Increase, Some(Direction::Left)), + PaneId::Terminal(2), + ); + }, + BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Alt]) => { + edit_scrollback_for_pane_with_id(PaneId::Terminal(2)); + }, + BareKey::Char('g') if key.has_modifiers(&[KeyModifier::Alt]) => { + write_to_pane_id(vec![102, 111, 111], PaneId::Terminal(2)); + }, + BareKey::Char('h') if key.has_modifiers(&[KeyModifier::Alt]) => { + write_chars_to_pane_id("foo\n", PaneId::Terminal(2)); + }, + BareKey::Char('i') if key.has_modifiers(&[KeyModifier::Alt]) => { + move_pane_with_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('j') if key.has_modifiers(&[KeyModifier::Alt]) => { + move_pane_with_pane_id_in_direction(PaneId::Terminal(2), Direction::Left); + }, + BareKey::Char('k') if key.has_modifiers(&[KeyModifier::Alt]) => { + clear_screen_for_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Alt]) => { + scroll_up_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('m') if key.has_modifiers(&[KeyModifier::Alt]) => { + scroll_down_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('n') if key.has_modifiers(&[KeyModifier::Alt]) => { + scroll_to_top_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Alt]) => { + scroll_to_bottom_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('p') if key.has_modifiers(&[KeyModifier::Alt]) => { + page_scroll_up_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('q') if key.has_modifiers(&[KeyModifier::Alt]) => { + page_scroll_down_in_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Alt]) => { + toggle_pane_id_fullscreen(PaneId::Terminal(2)); + }, + BareKey::Char('s') if key.has_modifiers(&[KeyModifier::Alt]) => { + toggle_pane_embed_or_eject_for_pane_id(PaneId::Terminal(2)); + }, + BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Alt]) => { + close_tab_with_index(2); + }, _ => {}, }, Event::CustomMessage(message, payload) => { diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 2b586bbc..1d65e749 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -408,35 +408,41 @@ impl FloatingPanes { strategy: &ResizeStrategy, ) -> Result { // true => successfully resized - let err_context = - || format!("failed to {strategy} for active floating pane for client {client_id}"); - - let display_area = *self.display_area.borrow(); - let viewport = *self.viewport.borrow(); if let Some(active_floating_pane_id) = self.active_panes.get(&client_id) { - let mut floating_pane_grid = FloatingPaneGrid::new( - &mut self.panes, - &mut self.desired_pane_positions, - display_area, - viewport, - ); - floating_pane_grid - .change_pane_size( - active_floating_pane_id, - strategy, - (RESIZE_INCREMENT_WIDTH, RESIZE_INCREMENT_HEIGHT), - ) - .with_context(err_context)?; - - for pane in self.panes.values_mut() { - resize_pty!(pane, os_api, self.senders, self.character_cell_size) - .with_context(err_context)?; - } - self.set_force_render(); - return Ok(true); + return self.resize_pane_with_id(*strategy, *active_floating_pane_id); } Ok(false) } + pub fn resize_pane_with_id( + &mut self, + strategy: ResizeStrategy, + pane_id: PaneId, + ) -> Result { + // true => successfully resized + let err_context = || format!("Failed to resize pane with id: {:?}", pane_id); + let display_area = *self.display_area.borrow(); + let viewport = *self.viewport.borrow(); + let mut floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + floating_pane_grid + .change_pane_size( + &pane_id, + &strategy, + (RESIZE_INCREMENT_WIDTH, RESIZE_INCREMENT_HEIGHT), + ) + .with_context(err_context)?; + + for pane in self.panes.values_mut() { + resize_pty!(pane, os_api, self.senders, self.character_cell_size) + .with_context(err_context)?; + } + self.set_force_render(); + Ok(true) + } fn set_pane_active_at(&mut self, pane_id: PaneId) { if let Some(pane) = self.panes.get_mut(&pane_id) { @@ -545,61 +551,73 @@ impl FloatingPanes { } pub fn move_active_pane_down(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.active_panes.get(&client_id) { + self.move_pane_down(*active_pane_id); + } + } + pub fn move_pane_down(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); - if let Some(active_pane_id) = self.active_panes.get(&client_id) { - let mut floating_pane_grid = FloatingPaneGrid::new( - &mut self.panes, - &mut self.desired_pane_positions, - display_area, - viewport, - ); - floating_pane_grid.move_pane_down(active_pane_id).unwrap(); - self.set_force_render(); - } + let mut floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + floating_pane_grid.move_pane_down(&pane_id).non_fatal(); + self.set_force_render(); } pub fn move_active_pane_up(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.active_panes.get(&client_id) { + self.move_pane_up(*active_pane_id); + } + } + pub fn move_pane_up(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); - if let Some(active_pane_id) = self.active_panes.get(&client_id) { - let mut floating_pane_grid = FloatingPaneGrid::new( - &mut self.panes, - &mut self.desired_pane_positions, - display_area, - viewport, - ); - floating_pane_grid.move_pane_up(active_pane_id).unwrap(); - self.set_force_render(); - } + let mut floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + floating_pane_grid.move_pane_up(&pane_id).non_fatal(); + self.set_force_render(); } pub fn move_active_pane_left(&mut self, client_id: ClientId) { - let display_area = *self.display_area.borrow(); - let viewport = *self.viewport.borrow(); if let Some(active_pane_id) = self.active_panes.get(&client_id) { - let mut floating_pane_grid = FloatingPaneGrid::new( - &mut self.panes, - &mut self.desired_pane_positions, - display_area, - viewport, - ); - floating_pane_grid.move_pane_left(active_pane_id).unwrap(); - self.set_force_render(); + self.move_pane_left(*active_pane_id); } } - pub fn move_active_pane_right(&mut self, client_id: ClientId) { + pub fn move_pane_left(&mut self, pane_id: PaneId) { let display_area = *self.display_area.borrow(); let viewport = *self.viewport.borrow(); + let mut floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + floating_pane_grid.move_pane_left(&pane_id).unwrap(); + self.set_force_render(); + } + pub fn move_active_pane_right(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.active_panes.get(&client_id) { - let mut floating_pane_grid = FloatingPaneGrid::new( - &mut self.panes, - &mut self.desired_pane_positions, - display_area, - viewport, - ); - floating_pane_grid.move_pane_right(active_pane_id).unwrap(); - self.set_force_render(); + self.move_pane_right(*active_pane_id); } } + pub fn move_pane_right(&mut self, pane_id: PaneId) { + let display_area = *self.display_area.borrow(); + let viewport = *self.viewport.borrow(); + let mut floating_pane_grid = FloatingPaneGrid::new( + &mut self.panes, + &mut self.desired_pane_positions, + display_area, + viewport, + ); + floating_pane_grid.move_pane_right(&pane_id).unwrap(); + self.set_force_render(); + } pub fn move_active_pane( &mut self, search_backwards: bool, @@ -607,7 +625,9 @@ impl FloatingPanes { client_id: ClientId, ) { let active_pane_id = self.get_active_pane_id(client_id).unwrap(); - + self.move_pane(search_backwards, active_pane_id) + } + pub fn move_pane(&mut self, search_backwards: bool, pane_id: PaneId) { let new_position_id = { let pane_grid = FloatingPaneGrid::new( &mut self.panes, @@ -616,13 +636,13 @@ impl FloatingPanes { *self.viewport.borrow(), ); if search_backwards { - pane_grid.previous_selectable_pane_id(&active_pane_id) + pane_grid.previous_selectable_pane_id(&pane_id) } else { - pane_grid.next_selectable_pane_id(&active_pane_id) + pane_grid.next_selectable_pane_id(&pane_id) } }; if let Some(new_position_id) = new_position_id { - let current_position = self.panes.get(&active_pane_id).unwrap(); + let current_position = self.panes.get(&pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -635,7 +655,7 @@ impl FloatingPanes { } new_position.set_should_render(true); - let current_position = self.panes.get_mut(&active_pane_id).unwrap(); + let current_position = self.panes.get_mut(&pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.set_geom_override(geom); diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 05d127a6..8a603f4b 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1155,7 +1155,7 @@ impl Grid { self.mark_for_rerender(); } /// Dumps all lines above terminal vieport and the viewport itself to a string - pub fn dump_screen(&mut self, full: bool) -> String { + pub fn dump_screen(&self, full: bool) -> String { let viewport: String = dump_screen!(self.viewport); if !full { return viewport; diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 872928c3..c7e96eb6 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -515,7 +515,7 @@ impl Pane for TerminalPane { self.geom.y -= count; self.reflow_lines(); } - fn dump_screen(&mut self, _client_id: ClientId, full: bool) -> String { + fn dump_screen(&self, full: bool) -> String { self.grid.dump_screen(full) } fn clear_screen(&mut self) { diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 7a528f2b..931e2594 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -67,7 +67,7 @@ pub struct TiledPanes { active_panes: ActivePanes, draw_pane_frames: bool, panes_to_hide: HashSet, - fullscreen_is_active: bool, + fullscreen_is_active: Option, senders: ThreadSenders, window_title: Option, client_id_to_boundaries: HashMap, @@ -103,7 +103,7 @@ impl TiledPanes { active_panes: ActivePanes::new(&os_api), draw_pane_frames, panes_to_hide: HashSet::new(), - fullscreen_is_active: false, + fullscreen_is_active: None, senders, window_title: None, client_id_to_boundaries: HashMap::new(), @@ -833,58 +833,59 @@ impl TiledPanes { client_id: ClientId, strategy: &ResizeStrategy, ) -> Result<()> { - let err_context = - || format!("failed to {strategy} for active tiled pane for client {client_id}"); - if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - let mut pane_grid = TiledPaneGrid::new( - &mut self.panes, - &self.panes_to_hide, - *self.display_area.borrow(), - *self.viewport.borrow(), - ); - - match pane_grid - .change_pane_size(&active_pane_id, strategy, (RESIZE_PERCENT, RESIZE_PERCENT)) - .with_context(err_context) - { - Ok(_) => {}, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::PaneSizeUnchanged) => { - // try once more with double the resize percent, but let's keep it at that - match pane_grid - .change_pane_size( - &active_pane_id, - strategy, - (RESIZE_PERCENT * 2.0, RESIZE_PERCENT * 2.0), - ) - .with_context(err_context) - { - Ok(_) => {}, - Err(err) => match err.downcast_ref::() { - Some(ZellijError::PaneSizeUnchanged) => { - Err::<(), _>(err).non_fatal() - }, - _ => { - return Err(err); - }, - }, - } - }, - _ => { - return Err(err); - }, - }, - } - - for pane in self.panes.values_mut() { - resize_pty!(pane, self.os_api, self.senders, self.character_cell_size).unwrap(); - } - self.reset_boundaries(); + self.resize_pane_with_id(*strategy, active_pane_id)?; } Ok(()) } + pub fn resize_pane_with_id(&mut self, strategy: ResizeStrategy, pane_id: PaneId) -> Result<()> { + let err_context = || format!("failed to resize pand with id: {:?}", pane_id); + + let mut pane_grid = TiledPaneGrid::new( + &mut self.panes, + &self.panes_to_hide, + *self.display_area.borrow(), + *self.viewport.borrow(), + ); + + match pane_grid + .change_pane_size(&pane_id, &strategy, (RESIZE_PERCENT, RESIZE_PERCENT)) + .with_context(err_context) + { + Ok(_) => {}, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::PaneSizeUnchanged) => { + // try once more with double the resize percent, but let's keep it at that + match pane_grid + .change_pane_size( + &pane_id, + &strategy, + (RESIZE_PERCENT * 2.0, RESIZE_PERCENT * 2.0), + ) + .with_context(err_context) + { + Ok(_) => {}, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::PaneSizeUnchanged) => Err::<(), _>(err).non_fatal(), + _ => { + return Err(err); + }, + }, + } + }, + _ => { + return Err(err); + }, + }, + } + + for pane in self.panes.values_mut() { + resize_pty!(pane, self.os_api, self.senders, self.character_cell_size).unwrap(); + } + self.reset_boundaries(); + Ok(()) + } pub fn focus_next_pane(&mut self, client_id: ClientId) { let connected_clients: Vec = @@ -1225,7 +1226,9 @@ impl TiledPanes { } pub fn move_active_pane(&mut self, search_backwards: bool, client_id: ClientId) { let active_pane_id = self.get_active_pane_id(client_id).unwrap(); - + self.move_pane(search_backwards, active_pane_id) + } + pub fn move_pane(&mut self, search_backwards: bool, pane_id: PaneId) { let new_position_id = { let pane_grid = TiledPaneGrid::new( &mut self.panes, @@ -1234,9 +1237,9 @@ impl TiledPanes { *self.viewport.borrow(), ); if search_backwards { - pane_grid.previous_selectable_pane_id(&active_pane_id) + pane_grid.previous_selectable_pane_id(&pane_id) } else { - pane_grid.next_selectable_pane_id(&active_pane_id) + pane_grid.next_selectable_pane_id(&pane_id) } }; if self @@ -1250,7 +1253,7 @@ impl TiledPanes { self.reapply_pane_frames(); } - let current_position = self.panes.get(&active_pane_id).unwrap(); + let current_position = self.panes.get(&pane_id).unwrap(); let prev_geom = current_position.position_and_size(); let prev_geom_override = current_position.geom_override(); @@ -1270,7 +1273,7 @@ impl TiledPanes { .unwrap(); new_position.set_should_render(true); - let current_position = self.panes.get_mut(&active_pane_id).unwrap(); + let current_position = self.panes.get_mut(&pane_id).unwrap(); current_position.set_geom(next_geom); if let Some(geom) = next_geom_override { current_position.set_geom_override(geom); @@ -1283,202 +1286,215 @@ impl TiledPanes { ) .unwrap(); current_position.set_should_render(true); + self.reapply_pane_focus(); self.set_pane_frames(self.draw_pane_frames); } pub fn move_active_pane_down(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - let mut pane_grid = TiledPaneGrid::new( - &mut self.panes, - &self.panes_to_hide, - *self.display_area.borrow(), - *self.viewport.borrow(), - ); - let next_index = pane_grid - .next_selectable_pane_id_below(&active_pane_id) - .or_else(|| pane_grid.progress_stack_down_if_in_stack(&active_pane_id)); - if let Some(p) = next_index { - let active_pane_id = self.active_panes.get(&client_id).unwrap(); - let current_position = self.panes.get(active_pane_id).unwrap(); - let prev_geom = current_position.position_and_size(); - let prev_geom_override = current_position.geom_override(); + self.move_pane_down(active_pane_id); + } + } + pub fn move_pane_down(&mut self, pane_id: PaneId) { + let mut pane_grid = TiledPaneGrid::new( + &mut self.panes, + &self.panes_to_hide, + *self.display_area.borrow(), + *self.viewport.borrow(), + ); + let next_index = pane_grid + .next_selectable_pane_id_below(&pane_id) + .or_else(|| pane_grid.progress_stack_down_if_in_stack(&pane_id)); + if let Some(p) = next_index { + let current_position = self.panes.get(&pane_id).unwrap(); + let prev_geom = current_position.position_and_size(); + let prev_geom_override = current_position.geom_override(); - let new_position = self.panes.get_mut(&p).unwrap(); - let next_geom = new_position.position_and_size(); - let next_geom_override = new_position.geom_override(); - new_position.set_geom(prev_geom); - if let Some(geom) = prev_geom_override { - new_position.set_geom_override(geom); - } - resize_pty!( - new_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - new_position.set_should_render(true); - - let current_position = self.panes.get_mut(active_pane_id).unwrap(); - current_position.set_geom(next_geom); - if let Some(geom) = next_geom_override { - current_position.set_geom_override(geom); - } - resize_pty!( - current_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - current_position.set_should_render(true); - self.set_pane_frames(self.draw_pane_frames); + let new_position = self.panes.get_mut(&p).unwrap(); + let next_geom = new_position.position_and_size(); + let next_geom_override = new_position.geom_override(); + new_position.set_geom(prev_geom); + if let Some(geom) = prev_geom_override { + new_position.set_geom_override(geom); } + resize_pty!( + new_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + new_position.set_should_render(true); + + let current_position = self.panes.get_mut(&pane_id).unwrap(); + current_position.set_geom(next_geom); + if let Some(geom) = next_geom_override { + current_position.set_geom_override(geom); + } + resize_pty!( + current_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + current_position.set_should_render(true); + self.reapply_pane_focus(); + self.set_pane_frames(self.draw_pane_frames); } } pub fn move_active_pane_left(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - let pane_grid = TiledPaneGrid::new( - &mut self.panes, - &self.panes_to_hide, - *self.display_area.borrow(), - *self.viewport.borrow(), - ); - let next_index = pane_grid.next_selectable_pane_id_to_the_left(&active_pane_id); - if let Some(p) = next_index { - let active_pane_id = self.active_panes.get(&client_id).unwrap(); - let current_position = self.panes.get(active_pane_id).unwrap(); - let prev_geom = current_position.position_and_size(); - let prev_geom_override = current_position.geom_override(); + self.move_pane_left(active_pane_id); + } + } + pub fn move_pane_left(&mut self, pane_id: PaneId) { + let pane_grid = TiledPaneGrid::new( + &mut self.panes, + &self.panes_to_hide, + *self.display_area.borrow(), + *self.viewport.borrow(), + ); + let next_index = pane_grid.next_selectable_pane_id_to_the_left(&pane_id); + if let Some(p) = next_index { + let current_position = self.panes.get(&pane_id).unwrap(); + let prev_geom = current_position.position_and_size(); + let prev_geom_override = current_position.geom_override(); - let new_position = self.panes.get_mut(&p).unwrap(); - let next_geom = new_position.position_and_size(); - let next_geom_override = new_position.geom_override(); - new_position.set_geom(prev_geom); - if let Some(geom) = prev_geom_override { - new_position.set_geom_override(geom); - } - resize_pty!( - new_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - new_position.set_should_render(true); - - let current_position = self.panes.get_mut(active_pane_id).unwrap(); - current_position.set_geom(next_geom); - if let Some(geom) = next_geom_override { - current_position.set_geom_override(geom); - } - resize_pty!( - current_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - current_position.set_should_render(true); - self.set_pane_frames(self.draw_pane_frames); + let new_position = self.panes.get_mut(&p).unwrap(); + let next_geom = new_position.position_and_size(); + let next_geom_override = new_position.geom_override(); + new_position.set_geom(prev_geom); + if let Some(geom) = prev_geom_override { + new_position.set_geom_override(geom); } + resize_pty!( + new_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + new_position.set_should_render(true); + + let current_position = self.panes.get_mut(&pane_id).unwrap(); + current_position.set_geom(next_geom); + if let Some(geom) = next_geom_override { + current_position.set_geom_override(geom); + } + resize_pty!( + current_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + current_position.set_should_render(true); + self.reapply_pane_focus(); + self.set_pane_frames(self.draw_pane_frames); } } pub fn move_active_pane_right(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - let pane_grid = TiledPaneGrid::new( - &mut self.panes, - &self.panes_to_hide, - *self.display_area.borrow(), - *self.viewport.borrow(), - ); - let next_index = pane_grid.next_selectable_pane_id_to_the_right(&active_pane_id); - if let Some(p) = next_index { - let active_pane_id = self.active_panes.get(&client_id).unwrap(); - let current_position = self.panes.get(active_pane_id).unwrap(); - let prev_geom = current_position.position_and_size(); - let prev_geom_override = current_position.geom_override(); + self.move_pane_right(active_pane_id); + } + } + pub fn move_pane_right(&mut self, pane_id: PaneId) { + let pane_grid = TiledPaneGrid::new( + &mut self.panes, + &self.panes_to_hide, + *self.display_area.borrow(), + *self.viewport.borrow(), + ); + let next_index = pane_grid.next_selectable_pane_id_to_the_right(&pane_id); + if let Some(p) = next_index { + let current_position = self.panes.get(&pane_id).unwrap(); + let prev_geom = current_position.position_and_size(); + let prev_geom_override = current_position.geom_override(); - let new_position = self.panes.get_mut(&p).unwrap(); - let next_geom = new_position.position_and_size(); - let next_geom_override = new_position.geom_override(); - new_position.set_geom(prev_geom); - if let Some(geom) = prev_geom_override { - new_position.set_geom_override(geom); - } - resize_pty!( - new_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - new_position.set_should_render(true); - - let current_position = self.panes.get_mut(active_pane_id).unwrap(); - current_position.set_geom(next_geom); - if let Some(geom) = next_geom_override { - current_position.set_geom_override(geom); - } - resize_pty!( - current_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - current_position.set_should_render(true); - self.set_pane_frames(self.draw_pane_frames); + let new_position = self.panes.get_mut(&p).unwrap(); + let next_geom = new_position.position_and_size(); + let next_geom_override = new_position.geom_override(); + new_position.set_geom(prev_geom); + if let Some(geom) = prev_geom_override { + new_position.set_geom_override(geom); } + resize_pty!( + new_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + new_position.set_should_render(true); + + let current_position = self.panes.get_mut(&pane_id).unwrap(); + current_position.set_geom(next_geom); + if let Some(geom) = next_geom_override { + current_position.set_geom_override(geom); + } + resize_pty!( + current_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + current_position.set_should_render(true); + self.reapply_pane_focus(); + self.set_pane_frames(self.draw_pane_frames); } } pub fn move_active_pane_up(&mut self, client_id: ClientId) { if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - let mut pane_grid = TiledPaneGrid::new( - &mut self.panes, - &self.panes_to_hide, - *self.display_area.borrow(), - *self.viewport.borrow(), - ); - let next_index = pane_grid - .next_selectable_pane_id_above(&active_pane_id) - .or_else(|| pane_grid.progress_stack_up_if_in_stack(&active_pane_id)); - if let Some(p) = next_index { - let active_pane_id = self.active_panes.get(&client_id).unwrap(); - let current_position = self.panes.get(active_pane_id).unwrap(); - let prev_geom = current_position.position_and_size(); - let prev_geom_override = current_position.geom_override(); + self.move_pane_up(active_pane_id); + } + } + pub fn move_pane_up(&mut self, pane_id: PaneId) { + let mut pane_grid = TiledPaneGrid::new( + &mut self.panes, + &self.panes_to_hide, + *self.display_area.borrow(), + *self.viewport.borrow(), + ); + let next_index = pane_grid + .next_selectable_pane_id_above(&pane_id) + .or_else(|| pane_grid.progress_stack_up_if_in_stack(&pane_id)); + if let Some(p) = next_index { + let current_position = self.panes.get(&pane_id).unwrap(); + let prev_geom = current_position.position_and_size(); + let prev_geom_override = current_position.geom_override(); - let new_position = self.panes.get_mut(&p).unwrap(); - let next_geom = new_position.position_and_size(); - let next_geom_override = new_position.geom_override(); - new_position.set_geom(prev_geom); - if let Some(geom) = prev_geom_override { - new_position.set_geom_override(geom); - } - resize_pty!( - new_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - new_position.set_should_render(true); - - let current_position = self.panes.get_mut(active_pane_id).unwrap(); - current_position.set_geom(next_geom); - if let Some(geom) = next_geom_override { - current_position.set_geom_override(geom); - } - resize_pty!( - current_position, - self.os_api, - self.senders, - self.character_cell_size - ) - .unwrap(); - current_position.set_should_render(true); - self.set_pane_frames(self.draw_pane_frames); + let new_position = self.panes.get_mut(&p).unwrap(); + let next_geom = new_position.position_and_size(); + let next_geom_override = new_position.geom_override(); + new_position.set_geom(prev_geom); + if let Some(geom) = prev_geom_override { + new_position.set_geom_override(geom); } + resize_pty!( + new_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + new_position.set_should_render(true); + + let current_position = self.panes.get_mut(&pane_id).unwrap(); + current_position.set_geom(next_geom); + if let Some(geom) = next_geom_override { + current_position.set_geom_override(geom); + } + resize_pty!( + current_position, + self.os_api, + self.senders, + self.character_cell_size + ) + .unwrap(); + current_position.set_should_render(true); + self.reapply_pane_focus(); + self.set_pane_frames(self.draw_pane_frames); } } pub fn move_clients_out_of_pane(&mut self, pane_id: PaneId) { @@ -1565,23 +1581,65 @@ impl TiledPanes { self.panes_to_hide.contains(&pane_id) } pub fn fullscreen_is_active(&self) -> bool { - self.fullscreen_is_active + self.fullscreen_is_active.is_some() } pub fn unset_fullscreen(&mut self) { - if self.fullscreen_is_active { - let first_client_id = { - let connected_clients = self.connected_clients.borrow(); - connected_clients.iter().next().copied() - }; - if let Some(active_pane_id) = - first_client_id.and_then(|first_client_id| self.get_active_pane_id(first_client_id)) - { - let panes_to_hide: Vec<_> = self.panes_to_hide.iter().copied().collect(); - for pane_id in panes_to_hide { - let pane = self.get_pane_mut(pane_id).unwrap(); - pane.set_should_render(true); - pane.set_should_render_boundaries(true); + if let Some(fullscreen_pane_id) = self.fullscreen_is_active { + let panes_to_hide: Vec<_> = self.panes_to_hide.iter().copied().collect(); + for pane_id in panes_to_hide { + let pane = self.get_pane_mut(pane_id).unwrap(); + pane.set_should_render(true); + pane.set_should_render_boundaries(true); + } + let viewport_pane_ids: Vec<_> = self + .panes + .keys() + .copied() + .into_iter() + .filter(|id| { + !is_inside_viewport(&*self.viewport.borrow(), self.get_pane(*id).unwrap()) + }) + .collect(); + for pid in viewport_pane_ids { + let viewport_pane = self.get_pane_mut(pid).unwrap(); + viewport_pane.reset_size_and_position_override(); + } + self.panes_to_hide.clear(); + let fullscreen_pane = self.get_pane_mut(fullscreen_pane_id).unwrap(); + fullscreen_pane.reset_size_and_position_override(); + self.set_force_render(); + let display_area = *self.display_area.borrow(); + self.resize(display_area); + self.fullscreen_is_active = None; + } + } + pub fn toggle_active_pane_fullscreen(&mut self, client_id: ClientId) { + if let Some(active_pane_id) = self.get_active_pane_id(client_id) { + self.toggle_pane_fullscreen(active_pane_id); + } + } + + pub fn toggle_pane_fullscreen(&mut self, pane_id: PaneId) { + if self.fullscreen_is_active.is_some() { + self.unset_fullscreen(); + } else { + let pane_ids_to_hide = self.panes.iter().filter_map(|(&id, _pane)| { + if id != pane_id + && is_inside_viewport(&*self.viewport.borrow(), self.get_pane(id).unwrap()) + { + Some(id) + } else { + None } + }); + self.panes_to_hide = pane_ids_to_hide.collect(); + if self.panes_to_hide.is_empty() { + // nothing to do, pane is already as fullscreen as it can be, let's bail + return; + } else { + // For all of the panes outside of the viewport staying on the fullscreen + // screen, switch them to using override positions as well so that the resize + // system doesn't get confused by viewport and old panes that no longer line up let viewport_pane_ids: Vec<_> = self .panes .keys() @@ -1592,59 +1650,12 @@ impl TiledPanes { }) .collect(); for pid in viewport_pane_ids { - let viewport_pane = self.get_pane_mut(pid).unwrap(); - viewport_pane.reset_size_and_position_override(); - } - self.panes_to_hide.clear(); - let active_terminal = self.get_pane_mut(active_pane_id).unwrap(); - active_terminal.reset_size_and_position_override(); - self.set_force_render(); - let display_area = *self.display_area.borrow(); - self.resize(display_area); - self.fullscreen_is_active = false; - } - } - } - pub fn toggle_active_pane_fullscreen(&mut self, client_id: ClientId) { - if let Some(active_pane_id) = self.get_active_pane_id(client_id) { - if self.fullscreen_is_active { - self.unset_fullscreen(); - } else { - let pane_ids_to_hide = self.panes.iter().filter_map(|(&id, _pane)| { - if id != active_pane_id - && is_inside_viewport(&*self.viewport.borrow(), self.get_pane(id).unwrap()) - { - Some(id) - } else { - None - } - }); - self.panes_to_hide = pane_ids_to_hide.collect(); - if self.panes_to_hide.is_empty() { - // nothing to do, pane is already as fullscreen as it can be, let's bail - return; - } else { - // For all of the panes outside of the viewport staying on the fullscreen - // screen, switch them to using override positions as well so that the resize - // system doesn't get confused by viewport and old panes that no longer line up - let viewport_pane_ids: Vec<_> = self - .panes - .keys() - .copied() - .into_iter() - .filter(|id| { - !is_inside_viewport( - &*self.viewport.borrow(), - self.get_pane(*id).unwrap(), - ) - }) - .collect(); - for pid in viewport_pane_ids { - let viewport_pane = self.get_pane_mut(pid).unwrap(); + if let Some(viewport_pane) = self.get_pane_mut(pid) { viewport_pane.set_geom_override(viewport_pane.position_and_size()); } - let viewport = { *self.viewport.borrow() }; - let active_pane = self.get_pane_mut(active_pane_id).unwrap(); + } + let viewport = { *self.viewport.borrow() }; + if let Some(active_pane) = self.get_pane_mut(pane_id) { let full_screen_geom = PaneGeom { x: viewport.x, y: viewport.y, @@ -1652,16 +1663,16 @@ impl TiledPanes { }; active_pane.set_geom_override(full_screen_geom); } - let connected_client_list: Vec = - { self.connected_clients.borrow().iter().copied().collect() }; - for client_id in connected_client_list { - self.focus_pane(active_pane_id, client_id); - } - self.set_force_render(); - let display_area = *self.display_area.borrow(); - self.resize(display_area); - self.fullscreen_is_active = true; } + let connected_client_list: Vec = + { self.connected_clients.borrow().iter().copied().collect() }; + for client_id in connected_client_list { + self.focus_pane(pane_id, client_id); + } + self.set_force_render(); + let display_area = *self.display_area.borrow(); + self.resize(display_area); + self.fullscreen_is_active = Some(pane_id); } } diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 18df6159..392985a1 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -6984,3 +6984,1139 @@ pub fn rerun_command_pane_plugin_command() { .clone(); assert_snapshot!(format!("{:#?}", rerun_command_pane_event)); } + +#[test] +#[ignore] +pub fn resize_pane_with_id_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!( + received_screen_instructions, + ScreenInstruction::ResizePaneWithId, + 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('e')).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 rerun_command_pane_event = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::ResizePaneWithId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", rerun_command_pane_event)); +} + +#[test] +#[ignore] +pub fn edit_scrollback_for_pane_with_id_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!( + received_screen_instructions, + ScreenInstruction::EditScrollbackForPaneWithId, + 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('f')).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 rerun_command_pane_event = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::EditScrollbackForPaneWithId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", rerun_command_pane_event)); +} + +#[test] +#[ignore] +pub fn write_to_pane_id_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!( + received_screen_instructions, + ScreenInstruction::WriteToPaneId, + 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('g')).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 rerun_command_pane_event = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::WriteToPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", rerun_command_pane_event)); +} + +#[test] +#[ignore] +pub fn write_chars_to_pane_id_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!( + received_screen_instructions, + ScreenInstruction::WriteToPaneId, + 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('h')).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 rerun_command_pane_event = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::WriteToPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", rerun_command_pane_event)); +} + +#[test] +#[ignore] +pub fn move_pane_with_pane_id_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!( + received_screen_instructions, + ScreenInstruction::MovePaneWithPaneId, + 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('i')).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::MovePaneWithPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn move_pane_with_pane_id_in_direction_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!( + received_screen_instructions, + ScreenInstruction::MovePaneWithPaneIdInDirection, + 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('j')).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::MovePaneWithPaneIdInDirection(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn clear_screen_for_pane_id_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!( + received_screen_instructions, + ScreenInstruction::ClearScreenForPaneId, + 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('k')).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::ClearScreenForPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn scroll_up_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::ScrollUpInPaneId, + 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('l')).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::ScrollUpInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn scroll_down_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::ScrollDownInPaneId, + 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('m')).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::ScrollDownInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn scroll_to_top_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::ScrollToTopInPaneId, + 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('n')).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::ScrollToTopInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn scroll_to_bottom_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::ScrollToBottomInPaneId, + 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('o')).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::ScrollToBottomInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn page_scroll_up_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::PageScrollUpInPaneId, + 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('p')).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::PageScrollUpInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn page_scroll_down_in_pane_id_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!( + received_screen_instructions, + ScreenInstruction::PageScrollDownInPaneId, + 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('q')).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::PageScrollDownInPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn toggle_pane_id_fullscreen_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!( + received_screen_instructions, + ScreenInstruction::TogglePaneIdFullscreen, + 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('r')).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::TogglePaneIdFullscreen(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn toggle_pane_embed_or_eject_for_pane_id_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!( + received_screen_instructions, + ScreenInstruction::TogglePaneEmbedOrEjectForPaneId, + 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('s')).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::TogglePaneEmbedOrEjectForPaneId(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} + +#[test] +#[ignore] +pub fn close_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!( + received_screen_instructions, + ScreenInstruction::CloseTabWithIndex, + 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('t')).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::CloseTabWithIndex(..) = i { + Some(i.clone()) + } else { + None + } + }) + .clone(); + assert_snapshot!(format!("{:#?}", screen_instruction)); +} diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__clear_screen_for_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__clear_screen_for_pane_id_plugin_command.snap new file mode 100644 index 00000000..bd9a6796 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__clear_screen_for_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7482 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + ClearScreenForPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_tab_with_index_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_tab_with_index_plugin_command.snap new file mode 100644 index 00000000..32b2ac8a --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__close_tab_with_index_plugin_command.snap @@ -0,0 +1,10 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 8121 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + CloseTabWithIndex( + 2, + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__edit_scrollback_for_pane_with_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__edit_scrollback_for_pane_with_id_plugin_command.snap new file mode 100644 index 00000000..47285b57 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__edit_scrollback_for_pane_with_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7127 +expression: "format!(\"{:#?}\", rerun_command_pane_event)" +--- +Some( + EditScrollbackForPaneWithId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_in_direction_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_in_direction_plugin_command.snap new file mode 100644 index 00000000..5fedc503 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_in_direction_plugin_command.snap @@ -0,0 +1,13 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7411 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + MovePaneWithPaneIdInDirection( + Terminal( + 2, + ), + Left, + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_plugin_command.snap new file mode 100644 index 00000000..ed8d393b --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__move_pane_with_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7340 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + MovePaneWithPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_down_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_down_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..e436c892 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_down_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7908 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + PageScrollDownInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_up_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_up_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..31c9d2f9 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__page_scroll_up_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7837 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + PageScrollUpInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__resize_pane_with_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__resize_pane_with_id_plugin_command.snap new file mode 100644 index 00000000..69c25e26 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__resize_pane_with_id_plugin_command.snap @@ -0,0 +1,19 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7056 +expression: "format!(\"{:#?}\", rerun_command_pane_event)" +--- +Some( + ResizePaneWithId( + ResizeStrategy { + resize: Increase, + direction: Some( + Left, + ), + invert_on_boundaries: false, + }, + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_down_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_down_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..df8f5b6f --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_down_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7624 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + ScrollDownInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_bottom_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_bottom_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..3a72d183 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_bottom_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7766 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + ScrollToBottomInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_top_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_top_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..bb111aea --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_to_top_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7695 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + ScrollToTopInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_up_in_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_up_in_pane_id_plugin_command.snap new file mode 100644 index 00000000..f6de5eb8 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__scroll_up_in_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7553 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + ScrollUpInPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_embed_or_eject_for_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_embed_or_eject_for_pane_id_plugin_command.snap new file mode 100644 index 00000000..816c3576 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_embed_or_eject_for_pane_id_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 8050 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + TogglePaneEmbedOrEjectForPaneId( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_id_fullscreen_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_id_fullscreen_plugin_command.snap new file mode 100644 index 00000000..1d64a184 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_pane_id_fullscreen_plugin_command.snap @@ -0,0 +1,12 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7979 +expression: "format!(\"{:#?}\", screen_instruction)" +--- +Some( + TogglePaneIdFullscreen( + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_to_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_to_pane_id_plugin_command.snap new file mode 100644 index 00000000..60904fab --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_to_pane_id_plugin_command.snap @@ -0,0 +1,18 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7269 +expression: "format!(\"{:#?}\", rerun_command_pane_event)" +--- +Some( + WriteToPaneId( + [ + 102, + 111, + 111, + 10, + ], + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_to_pane_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_to_pane_id_plugin_command.snap new file mode 100644 index 00000000..6d9b051e --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_to_pane_id_plugin_command.snap @@ -0,0 +1,17 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 7198 +expression: "format!(\"{:#?}\", rerun_command_pane_event)" +--- +Some( + WriteToPaneId( + [ + 102, + 111, + 111, + ], + Terminal( + 2, + ), + ), +) diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 26b6388a..89093c50 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -272,6 +272,54 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { PluginCommand::RerunCommandPane(terminal_pane_id) => { rerun_command_pane(env, terminal_pane_id) }, + PluginCommand::ResizePaneIdWithDirection(resize, pane_id) => { + resize_pane_with_id(env, resize, pane_id.into()) + }, + PluginCommand::EditScrollbackForPaneWithId(pane_id) => { + edit_scrollback_for_pane_with_id(env, pane_id.into()) + }, + PluginCommand::WriteToPaneId(bytes, pane_id) => { + write_to_pane_id(env, bytes, pane_id.into()) + }, + PluginCommand::WriteCharsToPaneId(chars, pane_id) => { + write_chars_to_pane_id(env, chars, pane_id.into()) + }, + PluginCommand::MovePaneWithPaneId(pane_id) => { + move_pane_with_pane_id(env, pane_id.into()) + }, + PluginCommand::MovePaneWithPaneIdInDirection(pane_id, direction) => { + move_pane_with_pane_id_in_direction(env, pane_id.into(), direction) + }, + PluginCommand::ClearScreenForPaneId(pane_id) => { + clear_screen_for_pane_id(env, pane_id.into()) + }, + PluginCommand::ScrollUpInPaneId(pane_id) => { + scroll_up_in_pane_id(env, pane_id.into()) + }, + PluginCommand::ScrollDownInPaneId(pane_id) => { + scroll_down_in_pane_id(env, pane_id.into()) + }, + PluginCommand::ScrollToTopInPaneId(pane_id) => { + scroll_to_top_in_pane_id(env, pane_id.into()) + }, + PluginCommand::ScrollToBottomInPaneId(pane_id) => { + scroll_to_bottom_in_pane_id(env, pane_id.into()) + }, + PluginCommand::PageScrollUpInPaneId(pane_id) => { + page_scroll_up_in_pane_id(env, pane_id.into()) + }, + PluginCommand::PageScrollDownInPaneId(pane_id) => { + page_scroll_down_in_pane_id(env, pane_id.into()) + }, + PluginCommand::TogglePaneIdFullscreen(pane_id) => { + toggle_pane_id_fullscreen(env, pane_id.into()) + }, + PluginCommand::TogglePaneEmbedOrEjectForPaneId(pane_id) => { + toggle_pane_embed_or_eject_for_pane_id(env, pane_id.into()) + }, + PluginCommand::CloseTabWithIndex(tab_index) => { + close_tab_with_index(env, tab_index) + }, }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1442,6 +1490,105 @@ fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) { } } +fn resize_pane_with_id(env: &PluginEnv, resize: ResizeStrategy, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ResizePaneWithId(resize, pane_id)); +} + +fn edit_scrollback_for_pane_with_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::EditScrollbackForPaneWithId(pane_id)); +} + +fn write_to_pane_id(env: &PluginEnv, bytes: Vec, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::WriteToPaneId(bytes, pane_id)); +} + +fn write_chars_to_pane_id(env: &PluginEnv, chars: String, pane_id: PaneId) { + let bytes = chars.into_bytes(); + let _ = env + .senders + .send_to_screen(ScreenInstruction::WriteToPaneId(bytes, pane_id)); +} + +fn move_pane_with_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::MovePaneWithPaneId(pane_id)); +} + +fn move_pane_with_pane_id_in_direction(env: &PluginEnv, pane_id: PaneId, direction: Direction) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::MovePaneWithPaneIdInDirection( + pane_id, direction, + )); +} + +fn clear_screen_for_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ClearScreenForPaneId(pane_id)); +} + +fn scroll_up_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ScrollUpInPaneId(pane_id)); +} + +fn scroll_down_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ScrollDownInPaneId(pane_id)); +} + +fn scroll_to_top_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ScrollToTopInPaneId(pane_id)); +} + +fn scroll_to_bottom_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::ScrollToBottomInPaneId(pane_id)); +} + +fn page_scroll_up_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::PageScrollUpInPaneId(pane_id)); +} + +fn page_scroll_down_in_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::PageScrollDownInPaneId(pane_id)); +} + +fn toggle_pane_id_fullscreen(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::TogglePaneIdFullscreen(pane_id)); +} + +fn toggle_pane_embed_or_eject_for_pane_id(env: &PluginEnv, pane_id: PaneId) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::TogglePaneEmbedOrEjectForPaneId(pane_id)); +} + +fn close_tab_with_index(env: &PluginEnv, tab_index: usize) { + let _ = env + .senders + .send_to_screen(ScreenInstruction::CloseTabWithIndex(tab_index)); +} + // Custom panic handler for plugins. // // This is called when a panic occurs in a plugin. Since most panics will likely originate in the @@ -1516,7 +1663,10 @@ fn check_command_permission( | PluginCommand::RunCommand(..) | PluginCommand::ExecCmd(..) => PermissionType::RunCommands, PluginCommand::WebRequest(..) => PermissionType::WebAccess, - PluginCommand::Write(..) | PluginCommand::WriteChars(..) => PermissionType::WriteToStdin, + PluginCommand::Write(..) + | PluginCommand::WriteChars(..) + | PluginCommand::WriteToPaneId(..) + | PluginCommand::WriteCharsToPaneId(..) => PermissionType::WriteToStdin, PluginCommand::SwitchTabTo(..) | PluginCommand::SwitchToMode(..) | PluginCommand::NewTabsWithLayout(..) @@ -1531,19 +1681,31 @@ fn check_command_permission( | PluginCommand::MoveFocusOrTab(..) | PluginCommand::Detach | PluginCommand::EditScrollback + | PluginCommand::EditScrollbackForPaneWithId(..) | PluginCommand::ToggleTab | PluginCommand::MovePane | PluginCommand::MovePaneWithDirection(..) + | PluginCommand::MovePaneWithPaneId(..) + | PluginCommand::MovePaneWithPaneIdInDirection(..) | PluginCommand::ClearScreen + | PluginCommand::ClearScreenForPaneId(..) | PluginCommand::ScrollUp + | PluginCommand::ScrollUpInPaneId(..) | PluginCommand::ScrollDown + | PluginCommand::ScrollDownInPaneId(..) | PluginCommand::ScrollToTop + | PluginCommand::ScrollToTopInPaneId(..) | PluginCommand::ScrollToBottom + | PluginCommand::ScrollToBottomInPaneId(..) | PluginCommand::PageScrollUp + | PluginCommand::PageScrollUpInPaneId(..) | PluginCommand::PageScrollDown + | PluginCommand::PageScrollDownInPaneId(..) | PluginCommand::ToggleFocusFullscreen + | PluginCommand::TogglePaneIdFullscreen(..) | PluginCommand::TogglePaneFrames | PluginCommand::TogglePaneEmbedOrEject + | PluginCommand::TogglePaneEmbedOrEjectForPaneId(..) | PluginCommand::UndoRenamePane | PluginCommand::CloseFocus | PluginCommand::ToggleActiveTabSync @@ -1570,6 +1732,8 @@ fn check_command_permission( | PluginCommand::ShowPaneWithId(..) | PluginCommand::HidePaneWithId(..) | PluginCommand::RerunCommandPane(..) + | PluginCommand::ResizePaneIdWithDirection(..) + | PluginCommand::CloseTabWithIndex(..) | PluginCommand::KillSessions(..) => PermissionType::ChangeApplicationState, PluginCommand::UnblockCliPipeInput(..) | PluginCommand::BlockCliPipeInput(..) diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 1ad549cd..a03464fa 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -10,11 +10,7 @@ use crate::{ }; use async_std::task::{self, JoinHandle}; use std::sync::Arc; -use std::{ - collections::{BTreeMap, HashMap}, - os::unix::io::RawFd, - path::PathBuf, -}; +use std::{collections::HashMap, os::unix::io::RawFd, path::PathBuf}; use zellij_utils::nix::unistd::Pid; use zellij_utils::{ async_std, @@ -32,7 +28,7 @@ use zellij_utils::{ pub type VteBytes = Vec; pub type TabIndex = u32; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ClientTabIndexOrPaneId { ClientId(ClientId), TabIndex(usize), @@ -51,7 +47,7 @@ pub enum PtyInstruction { ClientTabIndexOrPaneId, ), // bool (if Some) is // should_float, String is an optional pane name - OpenInPlaceEditor(PathBuf, Option, ClientId), // Option is the optional line number + OpenInPlaceEditor(PathBuf, Option, ClientTabIndexOrPaneId), // Option is the optional line number SpawnTerminalVertically(Option, Option, ClientId), // String is an // optional pane // name @@ -357,9 +353,12 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { }, } }, - PtyInstruction::OpenInPlaceEditor(temp_file, line_number, client_id) => { - let err_context = - || format!("failed to open in-place editor for client {}", client_id); + PtyInstruction::OpenInPlaceEditor( + temp_file, + line_number, + client_tab_index_or_pane_id, + ) => { + let err_context = || format!("failed to open in-place editor for client"); match pty.spawn_terminal( Some(TerminalAction::OpenFile(OpenFilePayload::new( @@ -367,14 +366,14 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { line_number, None, ))), - ClientTabIndexOrPaneId::ClientId(client_id), + client_tab_index_or_pane_id, ) { Ok((pid, _starts_held)) => { pty.bus .senders .send_to_screen(ScreenInstruction::OpenInPlaceEditor( PaneId::Terminal(pid), - client_id, + client_tab_index_or_pane_id, )) .with_context(err_context)?; }, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 9c88396a..1bbec5cf 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -156,7 +156,7 @@ pub enum ScreenInstruction { bool, // start suppressed ClientTabIndexOrPaneId, ), - OpenInPlaceEditor(PaneId, ClientId), + OpenInPlaceEditor(PaneId, ClientTabIndexOrPaneId), TogglePaneEmbedOrFloating(ClientId), ToggleFloatingPanes(ClientId, Option), HorizontalSplit(PaneId, Option, HoldForCommand, ClientId), @@ -380,6 +380,21 @@ pub enum ScreenInstruction { hide_session_name: bool, }, RerunCommandPane(u32), // u32 - terminal pane id + ResizePaneWithId(ResizeStrategy, PaneId), + EditScrollbackForPaneWithId(PaneId), + WriteToPaneId(Vec, PaneId), + MovePaneWithPaneId(PaneId), + MovePaneWithPaneIdInDirection(PaneId, Direction), + ClearScreenForPaneId(PaneId), + ScrollUpInPaneId(PaneId), + ScrollDownInPaneId(PaneId), + ScrollToTopInPaneId(PaneId), + ScrollToBottomInPaneId(PaneId), + PageScrollUpInPaneId(PaneId), + PageScrollDownInPaneId(PaneId), + TogglePaneIdFullscreen(PaneId), + TogglePaneEmbedOrEjectForPaneId(PaneId), + CloseTabWithIndex(usize), } impl From<&ScreenInstruction> for ScreenContext { @@ -566,6 +581,27 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata, ScreenInstruction::Reconfigure { .. } => ScreenContext::Reconfigure, ScreenInstruction::RerunCommandPane { .. } => ScreenContext::RerunCommandPane, + ScreenInstruction::ResizePaneWithId(..) => ScreenContext::ResizePaneWithId, + ScreenInstruction::EditScrollbackForPaneWithId(..) => { + ScreenContext::EditScrollbackForPaneWithId + }, + ScreenInstruction::WriteToPaneId(..) => ScreenContext::WriteToPaneId, + ScreenInstruction::MovePaneWithPaneId(..) => ScreenContext::MovePaneWithPaneId, + ScreenInstruction::MovePaneWithPaneIdInDirection(..) => { + ScreenContext::MovePaneWithPaneIdInDirection + }, + ScreenInstruction::ClearScreenForPaneId(..) => ScreenContext::ClearScreenForPaneId, + ScreenInstruction::ScrollUpInPaneId(..) => ScreenContext::ScrollUpInPaneId, + ScreenInstruction::ScrollDownInPaneId(..) => ScreenContext::ScrollDownInPaneId, + ScreenInstruction::ScrollToTopInPaneId(..) => ScreenContext::ScrollToTopInPaneId, + ScreenInstruction::ScrollToBottomInPaneId(..) => ScreenContext::ScrollToBottomInPaneId, + ScreenInstruction::PageScrollUpInPaneId(..) => ScreenContext::PageScrollUpInPaneId, + ScreenInstruction::PageScrollDownInPaneId(..) => ScreenContext::PageScrollDownInPaneId, + ScreenInstruction::TogglePaneIdFullscreen(..) => ScreenContext::TogglePaneIdFullscreen, + ScreenInstruction::TogglePaneEmbedOrEjectForPaneId(..) => { + ScreenContext::TogglePaneEmbedOrEjectForPaneId + }, + ScreenInstruction::CloseTabWithIndex(..) => ScreenContext::CloseTabWithIndex, } } } @@ -2010,6 +2046,19 @@ impl Screen { ); } } + pub fn resize_pane_with_id(&mut self, resize: ResizeStrategy, pane_id: PaneId) { + let mut found = false; + for tab in self.tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.resize_pane_with_id(resize, pane_id); + found = true; + break; + } + } + if !found { + log::error!("Failed to find pane with id: {:?} to resize", pane_id); + } + } pub fn break_pane( &mut self, default_shell: Option, @@ -2586,11 +2635,35 @@ pub(crate) fn screen_thread_main( screen.render(None)?; }, - ScreenInstruction::OpenInPlaceEditor(pid, client_id) => { - active_tab!(screen, client_id, |tab: &mut Tab| tab - .replace_active_pane_with_editor_pane(pid, client_id), ?); - screen.unblock_input()?; - screen.log_and_report_session_state()?; + ScreenInstruction::OpenInPlaceEditor(pid, client_tab_index_or_pane_id) => { + match client_tab_index_or_pane_id { + ClientTabIndexOrPaneId::ClientId(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .replace_active_pane_with_editor_pane(pid, client_id), ?); + screen.unblock_input()?; + screen.log_and_report_session_state()?; + }, + ClientTabIndexOrPaneId::TabIndex(tab_index) => { + log::error!("Cannot OpenInPlaceEditor with a TabIndex"); + }, + ClientTabIndexOrPaneId::PaneId(pane_id_to_replace) => { + let mut found = false; + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id_to_replace) { + tab.replace_pane_with_editor_pane(pid, pane_id_to_replace); + found = true; + break; + } + } + if !found { + log::error!( + "Could not find pane with id {:?} to replace", + pane_id_to_replace + ); + } + }, + } screen.render(None)?; }, @@ -4165,6 +4238,195 @@ pub(crate) fn screen_thread_main( ScreenInstruction::RerunCommandPane(terminal_pane_id) => { screen.rerun_command_pane_with_id(terminal_pane_id) }, + ScreenInstruction::ResizePaneWithId(resize, pane_id) => { + screen.resize_pane_with_id(resize, pane_id) + }, + ScreenInstruction::EditScrollbackForPaneWithId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.edit_scrollback_for_pane_with_id(pane_id).non_fatal(); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::WriteToPaneId(bytes, pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.write_to_pane_id(&None, bytes, false, pane_id, None) + .non_fatal(); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::MovePaneWithPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.move_pane(pane_id); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::MovePaneWithPaneIdInDirection(pane_id, direction) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + match direction { + Direction::Down => tab.move_pane_down(pane_id), + Direction::Up => tab.move_pane_up(pane_id), + Direction::Left => tab.move_pane_left(pane_id), + Direction::Right => tab.move_pane_right(pane_id), + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::ClearScreenForPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.clear_screen_for_pane_id(pane_id); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::ScrollUpInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_up(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!( + "Currently only terminal panes are supported for scrolling up" + ); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::ScrollDownInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_down(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!( + "Currently only terminal panes are supported for scrolling down" + ); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::ScrollToTopInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_to_top(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!( + "Currently only terminal panes are supported for scrolling to top" + ); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::ScrollToBottomInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_to_bottom(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!("Currently only terminal panes are supported for scrolling to bottom"); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::PageScrollUpInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_page_up(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!( + "Currently only terminal panes are supported for scrolling" + ); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::PageScrollDownInPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + if let PaneId::Terminal(terminal_pane_id) = pane_id { + tab.scroll_terminal_page_down(terminal_pane_id); + } else { + // this is because to do this with plugins, we need the client_id - + // which we do not have (yet?) in this context... + log::error!( + "Currently only terminal panes are supported for scrolling" + ); + } + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::TogglePaneIdFullscreen(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.toggle_pane_fullscreen(pane_id); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::TogglePaneEmbedOrEjectForPaneId(pane_id) => { + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_pane_with_pid(&pane_id) { + tab.toggle_pane_embed_or_floating_for_pane_id(pane_id) + .non_fatal(); + break; + } + } + screen.render(None)?; + }, + ScreenInstruction::CloseTabWithIndex(tab_index) => { + screen.close_tab_at_index(tab_index).non_fatal() + }, } } Ok(()) diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 4d507d46..7b8e01e4 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -265,7 +265,7 @@ pub trait Pane { fn pull_left(&mut self, count: usize); fn pull_up(&mut self, count: usize); fn clear_screen(&mut self); - fn dump_screen(&mut self, _client_id: ClientId, _full: bool) -> String { + fn dump_screen(&self, _full: bool) -> String { "".to_owned() } fn scroll_up(&mut self, count: usize, client_id: ClientId); @@ -1024,6 +1024,38 @@ impl Tab { } Ok(()) } + pub fn toggle_pane_embed_or_floating_for_pane_id(&mut self, pane_id: PaneId) -> Result<()> { + let err_context = || { + format!( + "failed to toggle embedded/floating pane for pane_id {:?}", + pane_id + ) + }; + if self.tiled_panes.fullscreen_is_active() { + self.tiled_panes.unset_fullscreen(); + } + if self.floating_panes.panes_contain(&pane_id) { + if self.tiled_panes.has_room_for_new_pane() { + let floating_pane_to_embed = self + .extract_pane(pane_id, true, None) + .with_context(|| { + format!("failed to find floating pane (ID: {pane_id:?}) to embed",) + }) + .with_context(err_context)?; + self.add_tiled_pane(floating_pane_to_embed, pane_id, None)?; + } + } else if self.tiled_panes.panes_contain(&pane_id) { + if self.get_selectable_tiled_panes().count() <= 1 { + log::error!("Cannot float the last tiled pane..."); + // don't close the only pane on screen... + return Ok(()); + } + if let Some(embedded_pane_to_float) = self.extract_pane(pane_id, true, None) { + self.add_floating_pane(embedded_pane_to_float, pane_id, None, None)?; + } + } + Ok(()) + } pub fn toggle_floating_panes( &mut self, client_id: Option, @@ -1181,28 +1213,7 @@ impl Tab { match pid { PaneId::Terminal(pid) => { - let next_terminal_position = self.get_next_terminal_position(); // TODO: this is not accurate in this case - let mut new_pane = TerminalPane::new( - pid, - PaneGeom::default(), // the initial size will be set later - self.style, - next_terminal_position, - String::new(), - self.link_handler.clone(), - self.character_cell_size.clone(), - self.sixel_image_store.clone(), - self.terminal_emulator_colors.clone(), - self.terminal_emulator_color_codes.clone(), - None, - None, - self.debug, - self.arrow_fonts, - self.styled_underlines, - self.explicitly_disable_kitty_keyboard_protocol, - ); - new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the - // constructor so it won't be overrided - // by the editor + let new_pane = self.new_scrollback_editor_pane(pid); let replaced_pane = if self.floating_panes.panes_are_visible() { self.floating_panes .replace_active_pane(Box::new(new_pane), client_id) @@ -1213,9 +1224,7 @@ impl Tab { }; match replaced_pane { Some(replaced_pane) => { - let is_scrollback_editor = true; - self.suppressed_panes - .insert(PaneId::Terminal(pid), (is_scrollback_editor, replaced_pane)); + self.insert_scrollback_editor_replaced_pane(replaced_pane, pid); self.get_active_pane(client_id) .with_context(|| format!("no active pane found for client {client_id}")) .and_then(|current_active_pane| { @@ -1243,6 +1252,60 @@ impl Tab { } Ok(()) } + pub fn replace_pane_with_editor_pane( + &mut self, + pid: PaneId, + pane_id_to_replace: PaneId, + ) -> Result<()> { + // this method creates a new pane from pid and replaces it with the pane iwth the given pane_id_to_replace + // the pane with the given pane_id_to_replace is then suppressed (hidden and not rendered) until the current + // created pane is closed, in which case it will be replaced back by it + let err_context = || format!("failed to suppress pane"); + + match pid { + PaneId::Terminal(pid) => { + let new_pane = self.new_scrollback_editor_pane(pid); + let replaced_pane = if self.floating_panes.panes_contain(&pane_id_to_replace) { + self.floating_panes + .replace_pane(pane_id_to_replace, Box::new(new_pane)) + .ok() + } else if self.tiled_panes.panes_contain(&pane_id_to_replace) { + self.tiled_panes + .replace_pane(pane_id_to_replace, Box::new(new_pane)) + } else if self + .suppressed_panes + .values() + .any(|s_p| s_p.1.pid() == pane_id_to_replace) + { + log::error!("Cannot replace suppressed pane"); + None + } else { + // not a thing + None + }; + match replaced_pane { + Some(replaced_pane) => { + resize_pty!( + replaced_pane, + self.os_api, + self.senders, + self.character_cell_size + ); + self.insert_scrollback_editor_replaced_pane(replaced_pane, pid); + }, + None => { + Err::<(), _>(anyhow!("Could not find editor pane to replace")) + .with_context(err_context) + .non_fatal(); + }, + } + }, + PaneId::Plugin(_pid) => { + // TBD, currently unsupported + }, + } + Ok(()) + } pub fn suppress_pane_and_replace_with_pid( &mut self, old_pane_id: PaneId, @@ -1493,6 +1556,23 @@ impl Tab { } }) } + pub fn get_pane_with_id(&self, pane_id: PaneId) -> Option<&dyn Pane> { + self.floating_panes + .get_pane(pane_id) + .map(Box::as_ref) + .or_else(|| self.tiled_panes.get_pane(pane_id).map(Box::as_ref)) + .or_else(|| self.suppressed_panes.get(&pane_id).map(|p| p.1.as_ref())) + } + pub fn get_pane_with_id_mut(&mut self, pane_id: PaneId) -> Option<&mut Box> { + self.floating_panes + .get_pane_mut(pane_id) + .or_else(|| self.tiled_panes.get_pane_mut(pane_id)) + .or_else(|| { + self.suppressed_panes + .get_mut(&pane_id) + .map(|(_, pane)| pane) + }) + } pub fn get_active_pane_mut(&mut self, client_id: ClientId) -> Option<&mut Box> { self.get_active_pane_id(client_id).and_then(|ap| { if self.floating_panes.panes_are_visible() { @@ -1948,6 +2028,13 @@ impl Tab { } self.tiled_panes.toggle_active_pane_fullscreen(client_id); } + pub fn toggle_pane_fullscreen(&mut self, pane_id: PaneId) { + if self.tiled_panes.panes_contain(&pane_id) { + self.tiled_panes.toggle_pane_fullscreen(pane_id); + } else { + log::error!("No tiled pane with id: {:?} found", pane_id); + } + } pub fn is_fullscreen_active(&self) -> bool { self.tiled_panes.fullscreen_is_active() } @@ -2391,6 +2478,20 @@ impl Tab { .move_active_pane(search_backwards, client_id); } } + pub fn move_pane(&mut self, pane_id: PaneId) { + if !self.has_selectable_panes() { + return; + } + if self.tiled_panes.fullscreen_is_active() { + return; + } + let search_backwards = false; + if self.floating_panes.panes_are_visible() { + self.floating_panes.move_pane(search_backwards, pane_id); + } else { + self.tiled_panes.move_pane(search_backwards, pane_id); + } + } pub fn move_active_pane_backwards(&mut self, client_id: ClientId) { if !self.has_selectable_panes() { return; @@ -2422,6 +2523,21 @@ impl Tab { self.tiled_panes.move_active_pane_down(client_id); } } + pub fn move_pane_down(&mut self, pane_id: PaneId) { + if self.floating_panes.panes_are_visible() { + self.floating_panes.move_pane_down(pane_id); + self.swap_layouts.set_is_floating_damaged(); + self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind + } else { + if !self.has_selectable_panes() { + return; + } + if self.tiled_panes.fullscreen_is_active() { + return; + } + self.tiled_panes.move_pane_down(pane_id); + } + } pub fn move_active_pane_up(&mut self, client_id: ClientId) { if self.floating_panes.panes_are_visible() { self.floating_panes.move_active_pane_up(client_id); @@ -2437,6 +2553,21 @@ impl Tab { self.tiled_panes.move_active_pane_up(client_id); } } + pub fn move_pane_up(&mut self, pane_id: PaneId) { + if self.floating_panes.panes_are_visible() { + self.floating_panes.move_pane_up(pane_id); + self.swap_layouts.set_is_floating_damaged(); + self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind + } else { + if !self.has_selectable_panes() { + return; + } + if self.tiled_panes.fullscreen_is_active() { + return; + } + self.tiled_panes.move_pane_up(pane_id); + } + } pub fn move_active_pane_right(&mut self, client_id: ClientId) { if self.floating_panes.panes_are_visible() { self.floating_panes.move_active_pane_right(client_id); @@ -2452,6 +2583,21 @@ impl Tab { self.tiled_panes.move_active_pane_right(client_id); } } + pub fn move_pane_right(&mut self, pane_id: PaneId) { + if self.floating_panes.panes_are_visible() { + self.floating_panes.move_pane_right(pane_id); + self.swap_layouts.set_is_floating_damaged(); + self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind + } else { + if !self.has_selectable_panes() { + return; + } + if self.tiled_panes.fullscreen_is_active() { + return; + } + self.tiled_panes.move_pane_right(pane_id); + } + } pub fn move_active_pane_left(&mut self, client_id: ClientId) { if self.floating_panes.panes_are_visible() { self.floating_panes.move_active_pane_left(client_id); @@ -2467,6 +2613,21 @@ impl Tab { self.tiled_panes.move_active_pane_left(client_id); } } + pub fn move_pane_left(&mut self, pane_id: PaneId) { + if self.floating_panes.panes_are_visible() { + self.floating_panes.move_pane_left(pane_id); + self.swap_layouts.set_is_floating_damaged(); + self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" behind + } else { + if !self.has_selectable_panes() { + return; + } + if self.tiled_panes.fullscreen_is_active() { + return; + } + self.tiled_panes.move_pane_left(pane_id); + } + } fn close_down_to_max_terminals(&mut self) -> Result<()> { if let Some(max_panes) = self.max_panes { let terminals = self.get_tiled_pane_ids(); @@ -2744,6 +2905,11 @@ impl Tab { } Ok(()) } + pub fn clear_screen_for_pane_id(&mut self, pane_id: PaneId) { + if let Some(pane) = self.get_pane_with_id_mut(pane_id) { + pane.clear_screen(); + } + } pub fn dump_active_terminal_screen( &mut self, file: Option, @@ -2754,13 +2920,25 @@ impl Tab { || format!("failed to dump active terminal screen for client {client_id}"); if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { - let dump = active_pane.dump_screen(client_id, full); + let dump = active_pane.dump_screen(full); self.os_api .write_to_file(dump, file) .with_context(err_context)?; } Ok(()) } + pub fn dump_terminal_screen( + &mut self, + file: Option, + pane_id: PaneId, + full: bool, + ) -> Result<()> { + if let Some(pane) = self.get_pane_with_id(pane_id) { + let dump = pane.dump_screen(full); + self.os_api.write_to_file(dump, file).non_fatal() + } + Ok(()) + } pub fn edit_scrollback(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to edit scrollback for client {client_id}"); @@ -2779,16 +2957,44 @@ impl Tab { .send_to_pty(PtyInstruction::OpenInPlaceEditor( file, line_number, - client_id, + ClientTabIndexOrPaneId::ClientId(client_id), )) .with_context(err_context) } + pub fn edit_scrollback_for_pane_with_id(&mut self, pane_id: PaneId) -> Result<()> { + if let PaneId::Terminal(_terminal_pane_id) = pane_id { + let mut file = temp_dir(); + file.push(format!("{}.dump", Uuid::new_v4())); + self.dump_terminal_screen(Some(String::from(file.to_string_lossy())), pane_id, true) + .non_fatal(); + let line_number = self + .get_pane_with_id(pane_id) + .and_then(|a_t| a_t.get_line_number()); + self.senders.send_to_pty(PtyInstruction::OpenInPlaceEditor( + file, + line_number, + ClientTabIndexOrPaneId::PaneId(pane_id), + )) + } else { + log::error!("Editing plugin pane scrollback is currently unsupported."); + Ok(()) + } + } pub fn scroll_active_terminal_up(&mut self, client_id: ClientId) { if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { active_pane.scroll_up(1, client_id); } } + pub fn scroll_terminal_up(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + terminal_pane.scroll_up(1, fictitious_client_id); + } + } + pub fn scroll_active_terminal_down(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to scroll down active pane for client {client_id}"); @@ -2804,14 +3010,34 @@ impl Tab { Ok(()) } + pub fn scroll_terminal_down(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + terminal_pane.scroll_down(1, fictitious_client_id); + } + } + pub fn scroll_active_terminal_up_page(&mut self, client_id: ClientId) { if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { // prevent overflow when row == 0 - let scroll_rows = active_pane.rows().max(1) - 1; + let scroll_rows = active_pane.rows().max(1).saturating_sub(1); active_pane.scroll_up(scroll_rows, client_id); } } + pub fn scroll_terminal_page_up(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + // prevent overflow when row == 0 + let scroll_rows = terminal_pane.rows().max(1).saturating_sub(1); + terminal_pane.scroll_up(scroll_rows, fictitious_client_id); + } + } + pub fn scroll_active_terminal_down_page(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to scroll down one page in active pane for client {client_id}"); @@ -2829,14 +3055,40 @@ impl Tab { Ok(()) } + pub fn scroll_terminal_page_down(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + let scroll_rows = terminal_pane.get_content_rows(); + terminal_pane.scroll_down(scroll_rows, fictitious_client_id); + if !terminal_pane.is_scrolled() { + if let PaneId::Terminal(raw_fd) = terminal_pane.pid() { + self.process_pending_vte_events(raw_fd).non_fatal() + } + } + } + } + pub fn scroll_active_terminal_up_half_page(&mut self, client_id: ClientId) { if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { // prevent overflow when row == 0 - let scroll_rows = (active_pane.rows().max(1) - 1) / 2; + let scroll_rows = (active_pane.rows().max(1).saturating_sub(1)) / 2; active_pane.scroll_up(scroll_rows, client_id); } } + pub fn scroll_terminal_half_page_up(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + // prevent overflow when row == 0 + let scroll_rows = (terminal_pane.rows().max(1).saturating_sub(1)) / 2; + terminal_pane.scroll_down(scroll_rows, fictitious_client_id); + } + } + pub fn scroll_active_terminal_down_half_page(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to scroll down half a page in active pane for client {client_id}"); @@ -2854,6 +3106,21 @@ impl Tab { Ok(()) } + pub fn scroll_terminal_half_page_down(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + let scroll_rows = (terminal_pane.rows().max(1) - 1) / 2; + terminal_pane.scroll_down(scroll_rows, fictitious_client_id); + if !terminal_pane.is_scrolled() { + if let PaneId::Terminal(raw_fd) = terminal_pane.pid() { + self.process_pending_vte_events(raw_fd).non_fatal(); + } + } + } + } + pub fn scroll_active_terminal_to_bottom(&mut self, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to scroll to bottom in active pane for client {client_id}"); @@ -2870,6 +3137,17 @@ impl Tab { Ok(()) } + pub fn scroll_terminal_to_bottom(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + terminal_pane.clear_scroll(); + if !terminal_pane.is_scrolled() { + if let PaneId::Terminal(raw_fd) = terminal_pane.pid() { + self.process_pending_vte_events(raw_fd).non_fatal(); + } + } + } + } + pub fn scroll_active_terminal_to_top(&mut self, client_id: ClientId) -> Result<()> { if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { active_pane.clear_scroll(); @@ -2880,6 +3158,18 @@ impl Tab { Ok(()) } + pub fn scroll_terminal_to_top(&mut self, terminal_pane_id: u32) { + if let Some(terminal_pane) = self.get_pane_with_id_mut(PaneId::Terminal(terminal_pane_id)) { + terminal_pane.clear_scroll(); + if let Some(size) = terminal_pane.get_line_number() { + let fictitious_client_id = 1; // this is not checked for terminal panes and we + // don't have an actual client id here + // TODO: traits were a mistake + terminal_pane.scroll_up(size, fictitious_client_id); + } + } + } + pub fn clear_active_terminal_scroll(&mut self, client_id: ClientId) -> Result<()> { // TODO: is this a thing? let err_context = @@ -3954,6 +4244,50 @@ impl Tab { }, } } + pub fn resize_pane_with_id(&mut self, strategy: ResizeStrategy, pane_id: PaneId) -> Result<()> { + let err_context = || format!("unable to resize pane"); + if self.floating_panes.panes_contain(&pane_id) { + let successfully_resized = self + .floating_panes + .resize_pane_with_id(strategy, pane_id) + .with_context(err_context)?; + if successfully_resized { + self.swap_layouts.set_is_floating_damaged(); + self.swap_layouts.set_is_tiled_damaged(); + self.set_force_render(); // we force render here to make sure the panes under the floating pane render and don't leave "garbage" in case of a decrease + } + } else if self.tiled_panes.panes_contain(&pane_id) { + match self.tiled_panes.resize_pane_with_id(strategy, pane_id) { + Ok(_) => {}, + Err(err) => match err.downcast_ref::() { + Some(ZellijError::CantResizeFixedPanes { pane_ids }) => { + let mut pane_ids_to_error = vec![]; + for (id, is_terminal) in pane_ids { + if *is_terminal { + pane_ids_to_error.push(PaneId::Terminal(*id)); + } else { + pane_ids_to_error.push(PaneId::Plugin(*id)); + }; + } + self.senders + .send_to_background_jobs(BackgroundJob::DisplayPaneError( + pane_ids_to_error, + "FIXED!".into(), + )) + .with_context(err_context)?; + }, + _ => Err::<(), _>(err).fatal(), + }, + } + } else if self + .suppressed_panes + .values() + .any(|s_p| s_p.1.pid() == pane_id) + { + log::error!("Cannot resize suppressed panes"); + } + Ok(()) + } pub fn update_theme(&mut self, theme: Palette) { self.style.colors = theme; self.floating_panes.update_pane_themes(theme); @@ -3995,6 +4329,42 @@ impl Tab { pub fn update_auto_layout(&mut self, auto_layout: bool) { self.auto_layout = auto_layout; } + fn new_scrollback_editor_pane(&self, pid: u32) -> TerminalPane { + let next_terminal_position = self.get_next_terminal_position(); + let mut new_pane = TerminalPane::new( + pid, + PaneGeom::default(), // the initial size will be set later + self.style, + next_terminal_position, + String::new(), + self.link_handler.clone(), + self.character_cell_size.clone(), + self.sixel_image_store.clone(), + self.terminal_emulator_colors.clone(), + self.terminal_emulator_color_codes.clone(), + None, + None, + self.debug, + self.arrow_fonts, + self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, + ); + new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the + // constructor so it won't be overrided + // by the editor + new_pane + } + fn insert_scrollback_editor_replaced_pane( + &mut self, + replaced_pane: Box, + terminal_pane_id: u32, + ) { + let is_scrollback_editor = true; + self.suppressed_panes.insert( + PaneId::Terminal(terminal_pane_id), + (is_scrollback_editor, replaced_pane), + ); + } } pub fn pane_info_for_pane(pane_id: &PaneId, pane: &Box) -> PaneInfo { diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 1d8a7c81..f3554286 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -29,7 +29,10 @@ use std::env::set_var; use std::os::unix::io::RawFd; use std::sync::{Arc, Mutex}; -use crate::{plugins::PluginInstruction, pty::PtyInstruction}; +use crate::{ + plugins::PluginInstruction, + pty::{ClientTabIndexOrPaneId, PtyInstruction}, +}; use zellij_utils::ipc::PixelDimensions; use zellij_utils::{ @@ -1699,7 +1702,7 @@ pub fn send_cli_edit_scrollback_action() { { assert_eq!(scrollback_contents_file, &PathBuf::from(&dumped_file_name)); assert_eq!(terminal_id, &Some(1)); - assert_eq!(client_id, &1); + assert_eq!(client_id, &ClientTabIndexOrPaneId::ClientId(1)); found_instruction = true; } } diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 2c5e8580..9b57d1a9 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -857,6 +857,179 @@ pub fn rerun_command_pane(terminal_pane_id: u32) { unsafe { host_run_plugin_command() }; } +/// Sugar for close_terminal_pane and close_plugin_pane +pub fn close_pane_with_id(pane_id: PaneId) { + let plugin_command = match pane_id { + PaneId::Terminal(terminal_pane_id) => PluginCommand::CloseTerminalPane(terminal_pane_id), + PaneId::Plugin(plugin_pane_id) => PluginCommand::ClosePluginPane(plugin_pane_id), + }; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Resize the specified pane (increase/decrease) with an optional direction (left/right/up/down) +pub fn resize_pane_with_id(resize_strategy: ResizeStrategy, pane_id: PaneId) { + let plugin_command = PluginCommand::ResizePaneIdWithDirection(resize_strategy, pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Changes the focus to the pane with the specified id, unsuppressing it if it was suppressed and switching to its tab and layer (eg. floating/tiled). +pub fn focus_pane_with_id(pane_id: PaneId, should_float_if_hidden: bool) { + let plugin_command = match pane_id { + PaneId::Terminal(terminal_pane_id) => { + PluginCommand::FocusTerminalPane(terminal_pane_id, should_float_if_hidden) + }, + PaneId::Plugin(plugin_pane_id) => { + PluginCommand::FocusPluginPane(plugin_pane_id, should_float_if_hidden) + }, + }; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Edit the scrollback of the specified pane in the user's default `$EDITOR` (currently only works +/// for terminal panes) +pub fn edit_scrollback_for_pane_with_id(pane_id: PaneId) { + let plugin_command = PluginCommand::EditScrollbackForPaneWithId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Write bytes to the `STDIN` of the specified pane +pub fn write_to_pane_id(bytes: Vec, pane_id: PaneId) { + let plugin_command = PluginCommand::WriteToPaneId(bytes, pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Write characters to the `STDIN` of the specified pane +pub fn write_chars_to_pane_id(chars: &str, pane_id: PaneId) { + let plugin_command = PluginCommand::WriteCharsToPaneId(chars.to_owned(), pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Switch the position of the pane with this id with a different pane +pub fn move_pane_with_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::MovePaneWithPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Switch the position of the pane with this id with a different pane in the specified direction (eg. `Down`, `Up`, `Left`, `Right`). +pub fn move_pane_with_pane_id_in_direction(pane_id: PaneId, direction: Direction) { + let plugin_command = PluginCommand::MovePaneWithPaneIdInDirection(pane_id, direction); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Clear the scroll buffer of the specified pane +pub fn clear_screen_for_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::ClearScreenForPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane up 1 line +pub fn scroll_up_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::ScrollUpInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane down 1 line +pub fn scroll_down_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::ScrollDownInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane all the way to the top of the scrollbuffer +pub fn scroll_to_top_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::ScrollToTopInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane all the way to the bottom of the scrollbuffer +pub fn scroll_to_bottom_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::ScrollToBottomInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane up one page +pub fn page_scroll_up_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::PageScrollUpInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Scroll the specified pane down one page +pub fn page_scroll_down_in_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::PageScrollDownInPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Toggle the specified pane to be fullscreen or normal sized +pub fn toggle_pane_id_fullscreen(pane_id: PaneId) { + let plugin_command = PluginCommand::TogglePaneIdFullscreen(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Embed the specified pane (make it stop floating) or turn it to a float pane if it is not +pub fn toggle_pane_embed_or_eject_for_pane_id(pane_id: PaneId) { + let plugin_command = PluginCommand::TogglePaneEmbedOrEjectForPaneId(pane_id); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Close the focused tab +pub fn close_tab_with_index(tab_index: usize) { + let plugin_command = PluginCommand::CloseTabWithIndex(tab_index); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + +/// Rename the specified pane +pub fn rename_pane_with_id>(pane_id: PaneId, new_name: S) +where + S: ToString, +{ + let plugin_command = match pane_id { + PaneId::Terminal(terminal_pane_id) => { + PluginCommand::RenameTerminalPane(terminal_pane_id, new_name.to_string()) + }, + PaneId::Plugin(plugin_pane_id) => { + PluginCommand::RenamePluginPane(plugin_pane_id, new_name.to_string()) + }, + }; + 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)] diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 2290aab4..7ebbf11d 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -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" + 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" )] pub payload: ::core::option::Option, } @@ -128,10 +128,150 @@ pub mod plugin_command { OpenCommandPaneBackgroundPayload(super::OpenCommandPanePayload), #[prost(message, tag = "67")] RerunCommandPanePayload(super::RerunCommandPanePayload), + #[prost(message, tag = "68")] + ResizePaneIdWithDirectionPayload(super::ResizePaneIdWithDirectionPayload), + #[prost(message, tag = "69")] + EditScrollbackForPaneWithIdPayload(super::EditScrollbackForPaneWithIdPayload), + #[prost(message, tag = "70")] + WriteToPaneIdPayload(super::WriteToPaneIdPayload), + #[prost(message, tag = "71")] + WriteCharsToPaneIdPayload(super::WriteCharsToPaneIdPayload), + #[prost(message, tag = "72")] + MovePaneWithPaneIdPayload(super::MovePaneWithPaneIdPayload), + #[prost(message, tag = "73")] + MovePaneWithPaneIdInDirectionPayload( + super::MovePaneWithPaneIdInDirectionPayload, + ), + #[prost(message, tag = "74")] + ClearScreenForPaneIdPayload(super::ClearScreenForPaneIdPayload), + #[prost(message, tag = "75")] + ScrollUpInPaneIdPayload(super::ScrollUpInPaneIdPayload), + #[prost(message, tag = "76")] + ScrollDownInPaneIdPayload(super::ScrollDownInPaneIdPayload), + #[prost(message, tag = "77")] + ScrollToTopInPaneIdPayload(super::ScrollToTopInPaneIdPayload), + #[prost(message, tag = "78")] + ScrollToBottomInPaneIdPayload(super::ScrollToBottomInPaneIdPayload), + #[prost(message, tag = "79")] + PageScrollUpInPaneIdPayload(super::PageScrollUpInPaneIdPayload), + #[prost(message, tag = "80")] + PageScrollDownInPaneIdPayload(super::PageScrollDownInPaneIdPayload), + #[prost(message, tag = "81")] + TogglePaneIdFullscreenPayload(super::TogglePaneIdFullscreenPayload), + #[prost(message, tag = "82")] + TogglePaneEmbedOrEjectForPaneIdPayload( + super::TogglePaneEmbedOrEjectForPaneIdPayload, + ), + #[prost(message, tag = "83")] + CloseTabWithIndexPayload(super::CloseTabWithIndexPayload), } } #[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, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MovePaneWithPaneIdInDirectionPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub direction: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClearScreenForPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ScrollUpInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ScrollDownInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ScrollToTopInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ScrollToBottomInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PageScrollUpInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PageScrollDownInPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TogglePaneIdFullscreenPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TogglePaneEmbedOrEjectForPaneIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CloseTabWithIndexPayload { + #[prost(uint32, tag = "1")] + pub tab_index: u32, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WriteCharsToPaneIdPayload { + #[prost(string, tag = "1")] + pub chars_to_write: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WriteToPaneIdPayload { + #[prost(bytes = "vec", tag = "1")] + pub bytes_to_write: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "2")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EditScrollbackForPaneWithIdPayload { + #[prost(message, optional, tag = "1")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResizePaneIdWithDirectionPayload { + #[prost(message, optional, tag = "1")] + pub resize: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub pane_id: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct ReconfigurePayload { #[prost(string, tag = "1")] pub config: ::prost::alloc::string::String, @@ -478,6 +618,22 @@ pub enum CommandName { ShowPaneWithId = 89, OpenCommandPaneBackground = 90, RerunCommandPane = 91, + ResizePaneIdWithDirection = 92, + EditScrollbackForPaneWithId = 93, + WriteToPaneId = 94, + WriteCharsToPaneId = 95, + MovePaneWithPaneId = 96, + MovePaneWithPaneIdInDirection = 97, + ClearScreenForPaneId = 98, + ScrollUpInPaneId = 99, + ScrollDownInPaneId = 100, + ScrollToTopInPaneId = 101, + ScrollToBottomInPaneId = 102, + PageScrollUpInPaneId = 103, + PageScrollDownInPaneId = 104, + TogglePaneIdFullscreen = 105, + TogglePaneEmbedOrEjectForPaneId = 106, + CloseTabWithIndex = 107, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -578,6 +734,24 @@ impl CommandName { CommandName::ShowPaneWithId => "ShowPaneWithId", CommandName::OpenCommandPaneBackground => "OpenCommandPaneBackground", CommandName::RerunCommandPane => "RerunCommandPane", + CommandName::ResizePaneIdWithDirection => "ResizePaneIdWithDirection", + CommandName::EditScrollbackForPaneWithId => "EditScrollbackForPaneWithId", + CommandName::WriteToPaneId => "WriteToPaneId", + CommandName::WriteCharsToPaneId => "WriteCharsToPaneId", + CommandName::MovePaneWithPaneId => "MovePaneWithPaneId", + CommandName::MovePaneWithPaneIdInDirection => "MovePaneWithPaneIdInDirection", + CommandName::ClearScreenForPaneId => "ClearScreenForPaneId", + CommandName::ScrollUpInPaneId => "ScrollUpInPaneId", + CommandName::ScrollDownInPaneId => "ScrollDownInPaneId", + CommandName::ScrollToTopInPaneId => "ScrollToTopInPaneId", + CommandName::ScrollToBottomInPaneId => "ScrollToBottomInPaneId", + CommandName::PageScrollUpInPaneId => "PageScrollUpInPaneId", + CommandName::PageScrollDownInPaneId => "PageScrollDownInPaneId", + CommandName::TogglePaneIdFullscreen => "TogglePaneIdFullscreen", + CommandName::TogglePaneEmbedOrEjectForPaneId => { + "TogglePaneEmbedOrEjectForPaneId" + } + CommandName::CloseTabWithIndex => "CloseTabWithIndex", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -675,6 +849,24 @@ impl CommandName { "ShowPaneWithId" => Some(Self::ShowPaneWithId), "OpenCommandPaneBackground" => Some(Self::OpenCommandPaneBackground), "RerunCommandPane" => Some(Self::RerunCommandPane), + "ResizePaneIdWithDirection" => Some(Self::ResizePaneIdWithDirection), + "EditScrollbackForPaneWithId" => Some(Self::EditScrollbackForPaneWithId), + "WriteToPaneId" => Some(Self::WriteToPaneId), + "WriteCharsToPaneId" => Some(Self::WriteCharsToPaneId), + "MovePaneWithPaneId" => Some(Self::MovePaneWithPaneId), + "MovePaneWithPaneIdInDirection" => Some(Self::MovePaneWithPaneIdInDirection), + "ClearScreenForPaneId" => Some(Self::ClearScreenForPaneId), + "ScrollUpInPaneId" => Some(Self::ScrollUpInPaneId), + "ScrollDownInPaneId" => Some(Self::ScrollDownInPaneId), + "ScrollToTopInPaneId" => Some(Self::ScrollToTopInPaneId), + "ScrollToBottomInPaneId" => Some(Self::ScrollToBottomInPaneId), + "PageScrollUpInPaneId" => Some(Self::PageScrollUpInPaneId), + "PageScrollDownInPaneId" => Some(Self::PageScrollDownInPaneId), + "TogglePaneIdFullscreen" => Some(Self::TogglePaneIdFullscreen), + "TogglePaneEmbedOrEjectForPaneId" => { + Some(Self::TogglePaneEmbedOrEjectForPaneId) + } + "CloseTabWithIndex" => Some(Self::CloseTabWithIndex), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 85897839..2087977b 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1820,4 +1820,20 @@ pub enum PluginCommand { ShowPaneWithId(PaneId, bool), // bool -> should_float_if_hidden OpenCommandPaneBackground(CommandToRun, Context), RerunCommandPane(u32), // u32 - terminal pane id + ResizePaneIdWithDirection(ResizeStrategy, PaneId), + EditScrollbackForPaneWithId(PaneId), + WriteToPaneId(Vec, PaneId), + WriteCharsToPaneId(String, PaneId), + MovePaneWithPaneId(PaneId), + MovePaneWithPaneIdInDirection(PaneId, Direction), + ClearScreenForPaneId(PaneId), + ScrollUpInPaneId(PaneId), + ScrollDownInPaneId(PaneId), + ScrollToTopInPaneId(PaneId), + ScrollToBottomInPaneId(PaneId), + PageScrollUpInPaneId(PaneId), + PageScrollDownInPaneId(PaneId), + TogglePaneIdFullscreen(PaneId), + TogglePaneEmbedOrEjectForPaneId(PaneId), + CloseTabWithIndex(usize), // usize - tab_index } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 44dd206d..f86595a3 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -355,6 +355,21 @@ pub enum ScreenContext { ListClientsMetadata, Reconfigure, RerunCommandPane, + ResizePaneWithId, + EditScrollbackForPaneWithId, + WriteToPaneId, + MovePaneWithPaneId, + MovePaneWithPaneIdInDirection, + ClearScreenForPaneId, + ScrollUpInPaneId, + ScrollDownInPaneId, + ScrollToTopInPaneId, + ScrollToBottomInPaneId, + PageScrollUpInPaneId, + PageScrollDownInPaneId, + TogglePaneIdFullscreen, + TogglePaneEmbedOrEjectForPaneId, + CloseTabWithIndex, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index be47f567..6e77643b 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -103,6 +103,22 @@ enum CommandName { ShowPaneWithId = 89; OpenCommandPaneBackground = 90; RerunCommandPane = 91; + ResizePaneIdWithDirection = 92; + EditScrollbackForPaneWithId = 93; + WriteToPaneId = 94; + WriteCharsToPaneId = 95; + MovePaneWithPaneId = 96; + MovePaneWithPaneIdInDirection = 97; + ClearScreenForPaneId = 98; + ScrollUpInPaneId = 99; + ScrollDownInPaneId = 100; + ScrollToTopInPaneId = 101; + ScrollToBottomInPaneId = 102; + PageScrollUpInPaneId = 103; + PageScrollDownInPaneId = 104; + TogglePaneIdFullscreen = 105; + TogglePaneEmbedOrEjectForPaneId = 106; + CloseTabWithIndex = 107; } message PluginCommand { @@ -165,9 +181,93 @@ message PluginCommand { ShowPaneWithIdPayload show_pane_with_id_payload = 65; OpenCommandPanePayload open_command_pane_background_payload = 66; RerunCommandPanePayload rerun_command_pane_payload = 67; + ResizePaneIdWithDirectionPayload resize_pane_id_with_direction_payload = 68; + EditScrollbackForPaneWithIdPayload edit_scrollback_for_pane_with_id_payload = 69; + WriteToPaneIdPayload write_to_pane_id_payload = 70; + WriteCharsToPaneIdPayload write_chars_to_pane_id_payload = 71; + MovePaneWithPaneIdPayload move_pane_with_pane_id_payload = 72; + MovePaneWithPaneIdInDirectionPayload move_pane_with_pane_id_in_direction_payload = 73; + ClearScreenForPaneIdPayload clear_screen_for_pane_id_payload = 74; + ScrollUpInPaneIdPayload scroll_up_in_pane_id_payload = 75; + ScrollDownInPaneIdPayload scroll_down_in_pane_id_payload = 76; + ScrollToTopInPaneIdPayload scroll_to_top_in_pane_id_payload = 77; + ScrollToBottomInPaneIdPayload scroll_to_bottom_in_pane_id_payload = 78; + PageScrollUpInPaneIdPayload page_scroll_up_in_pane_id_payload = 79; + PageScrollDownInPaneIdPayload page_scroll_down_in_pane_id_payload = 80; + 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; } } +message MovePaneWithPaneIdPayload { + PaneId pane_id = 1; +} + +message MovePaneWithPaneIdInDirectionPayload { + PaneId pane_id = 1; + resize.MoveDirection direction = 2; +} + +message ClearScreenForPaneIdPayload { + PaneId pane_id = 1; +} + +message ScrollUpInPaneIdPayload { + PaneId pane_id = 1; +} + +message ScrollDownInPaneIdPayload { + PaneId pane_id = 1; +} + +message ScrollToTopInPaneIdPayload { + PaneId pane_id = 1; +} + +message ScrollToBottomInPaneIdPayload { + PaneId pane_id = 1; +} + +message PageScrollUpInPaneIdPayload { + PaneId pane_id = 1; +} + +message PageScrollDownInPaneIdPayload { + PaneId pane_id = 1; +} + +message TogglePaneIdFullscreenPayload { + PaneId pane_id = 1; +} + +message TogglePaneEmbedOrEjectForPaneIdPayload { + PaneId pane_id = 1; +} + +message CloseTabWithIndexPayload { + uint32 tab_index = 1; +} + +message WriteCharsToPaneIdPayload { + string chars_to_write = 1; + PaneId pane_id = 2; +} + +message WriteToPaneIdPayload { + bytes bytes_to_write = 1; + PaneId pane_id = 2; +} + +message EditScrollbackForPaneWithIdPayload { + PaneId pane_id = 1; +} + +message ResizePaneIdWithDirectionPayload { + resize.Resize resize = 1; + PaneId pane_id = 2; +} + message ReconfigurePayload { string config = 1; bool write_to_disk = 2; diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 4ffabbe4..155907d3 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -3,17 +3,23 @@ pub use super::generated_api::api::{ event::{EventNameList as ProtobufEventNameList, Header}, input_mode::InputMode as ProtobufInputMode, plugin_command::{ - plugin_command::Payload, CliPipeOutputPayload, CommandName, ContextItem, EnvVariable, - ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, + plugin_command::Payload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, + CloseTabWithIndexPayload, CommandName, ContextItem, EditScrollbackForPaneWithIdPayload, + EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload, - MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload, - OpenCommandPanePayload, OpenFilePayload, PaneId as ProtobufPaneId, - PaneType as ProtobufPaneType, PluginCommand as ProtobufPluginCommand, PluginMessagePayload, - ReconfigurePayload, RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePayload, - RunCommandPayload, SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload, - SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload, WebRequestPayload, + MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, MovePayload, + NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload, + OpenCommandPanePayload, OpenFilePayload, PageScrollDownInPaneIdPayload, + PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType, + PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ReconfigurePayload, + RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, + ResizePayload, RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, + ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetTimeoutPayload, + ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, + TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload, + WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload, }, plugin_permission::PermissionType as ProtobufPermissionType, resize::ResizeAction as ProtobufResizeAction, @@ -991,6 +997,183 @@ impl TryFrom for PluginCommand { ), _ => Err("Mismatched payload for RerunCommandPane"), }, + Some(CommandName::ResizePaneIdWithDirection) => match protobuf_plugin_command.payload { + Some(Payload::ResizePaneIdWithDirectionPayload(resize_with_direction_payload)) => { + match ( + resize_with_direction_payload.resize, + resize_with_direction_payload.pane_id, + ) { + (Some(resize), Some(pane_id)) => { + Ok(PluginCommand::ResizePaneIdWithDirection( + resize.try_into()?, + pane_id.try_into()?, + )) + }, + _ => Err("Malformed resize_pane_with_id payload"), + } + }, + _ => Err("Mismatched payload for Resize"), + }, + Some(CommandName::EditScrollbackForPaneWithId) => match protobuf_plugin_command.payload + { + Some(Payload::EditScrollbackForPaneWithIdPayload( + edit_scrollback_for_pane_with_id_payload, + )) => match edit_scrollback_for_pane_with_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::EditScrollbackForPaneWithId( + pane_id.try_into()?, + )), + _ => Err("Malformed edit_scrollback_for_pane_with_id payload"), + }, + _ => Err("Mismatched payload for EditScrollback"), + }, + Some(CommandName::WriteToPaneId) => match protobuf_plugin_command.payload { + Some(Payload::WriteToPaneIdPayload(write_to_pane_id_payload)) => { + match write_to_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::WriteToPaneId( + write_to_pane_id_payload.bytes_to_write, + pane_id.try_into()?, + )), + _ => Err("Malformed write_to_pane_id payload"), + } + }, + _ => Err("Mismatched payload for WriteToPaneId"), + }, + Some(CommandName::WriteCharsToPaneId) => match protobuf_plugin_command.payload { + Some(Payload::WriteCharsToPaneIdPayload(write_chars_to_pane_id_payload)) => { + match write_chars_to_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::WriteCharsToPaneId( + write_chars_to_pane_id_payload.chars_to_write, + pane_id.try_into()?, + )), + _ => Err("Malformed write_chars_to_pane_id payload"), + } + }, + _ => Err("Mismatched payload for WriteCharsCharsToPaneId"), + }, + Some(CommandName::MovePaneWithPaneId) => match protobuf_plugin_command.payload { + Some(Payload::MovePaneWithPaneIdPayload(move_pane_with_pane_id_payload)) => { + match move_pane_with_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::MovePaneWithPaneId(pane_id.try_into()?)), + _ => Err("Malformed move_pane_with_pane_id payload"), + } + }, + _ => Err("Mismatched payload for MovePaneWithPaneId"), + }, + Some(CommandName::MovePaneWithPaneIdInDirection) => { + match protobuf_plugin_command.payload { + Some(Payload::MovePaneWithPaneIdInDirectionPayload(move_payload)) => { + match (move_payload.direction, move_payload.pane_id) { + (Some(direction), Some(pane_id)) => { + Ok(PluginCommand::MovePaneWithPaneIdInDirection( + pane_id.try_into()?, + direction.try_into()?, + )) + }, + _ => Err("Malformed MovePaneWithPaneIdInDirection payload"), + } + }, + _ => Err("Mismatched payload for MovePaneWithDirection"), + } + }, + Some(CommandName::ClearScreenForPaneId) => match protobuf_plugin_command.payload { + Some(Payload::ClearScreenForPaneIdPayload(clear_screen_for_pane_id_payload)) => { + match clear_screen_for_pane_id_payload.pane_id { + Some(pane_id) => { + Ok(PluginCommand::ClearScreenForPaneId(pane_id.try_into()?)) + }, + _ => Err("Malformed clear_screen_for_pane_id_payload payload"), + } + }, + _ => Err("Mismatched payload for ClearScreenForPaneId"), + }, + Some(CommandName::ScrollUpInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::ScrollUpInPaneIdPayload(scroll_up_in_pane_id_payload)) => { + match scroll_up_in_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::ScrollUpInPaneId(pane_id.try_into()?)), + _ => Err("Malformed scroll_up_in_pane_id_payload payload"), + } + }, + _ => Err("Mismatched payload for ScrollUpInPaneId"), + }, + Some(CommandName::ScrollDownInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::ScrollDownInPaneIdPayload(scroll_down_in_pane_id_payload)) => { + match scroll_down_in_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::ScrollDownInPaneId(pane_id.try_into()?)), + _ => Err("Malformed scroll_down_in_pane_id_payload payload"), + } + }, + _ => Err("Mismatched payload for ScrollDownInPaneId"), + }, + Some(CommandName::ScrollToTopInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::ScrollToTopInPaneIdPayload(scroll_to_top_in_pane_id_payload)) => { + match scroll_to_top_in_pane_id_payload.pane_id { + Some(pane_id) => { + Ok(PluginCommand::ScrollToTopInPaneId(pane_id.try_into()?)) + }, + _ => Err("Malformed scroll_to_top_in_pane_id_payload payload"), + } + }, + _ => Err("Mismatched payload for ScrollToTopInPaneId"), + }, + Some(CommandName::ScrollToBottomInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::ScrollToBottomInPaneIdPayload( + scroll_to_bottom_in_pane_id_payload, + )) => match scroll_to_bottom_in_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::ScrollToBottomInPaneId(pane_id.try_into()?)), + _ => Err("Malformed scroll_to_bottom_in_pane_id_payload payload"), + }, + _ => Err("Mismatched payload for ScrollToBottomInPaneId"), + }, + Some(CommandName::PageScrollUpInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::PageScrollUpInPaneIdPayload(page_scroll_up_in_pane_id_payload)) => { + match page_scroll_up_in_pane_id_payload.pane_id { + Some(pane_id) => { + Ok(PluginCommand::PageScrollUpInPaneId(pane_id.try_into()?)) + }, + _ => Err("Malformed page_scroll_up_in_pane_id_payload payload"), + } + }, + _ => Err("Mismatched payload for PageScrollUpInPaneId"), + }, + Some(CommandName::PageScrollDownInPaneId) => match protobuf_plugin_command.payload { + Some(Payload::PageScrollDownInPaneIdPayload( + page_scroll_down_in_pane_id_payload, + )) => match page_scroll_down_in_pane_id_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::PageScrollDownInPaneId(pane_id.try_into()?)), + _ => Err("Malformed page_scroll_down_in_pane_id_payload payload"), + }, + _ => Err("Mismatched payload for PageScrollDownInPaneId"), + }, + Some(CommandName::TogglePaneIdFullscreen) => match protobuf_plugin_command.payload { + Some(Payload::TogglePaneIdFullscreenPayload(toggle_pane_id_fullscreen_payload)) => { + match toggle_pane_id_fullscreen_payload.pane_id { + Some(pane_id) => { + Ok(PluginCommand::TogglePaneIdFullscreen(pane_id.try_into()?)) + }, + _ => Err("Malformed toggle_pane_id_fullscreen_payload payload"), + } + }, + _ => Err("Mismatched payload for TogglePaneIdFullscreen"), + }, + Some(CommandName::TogglePaneEmbedOrEjectForPaneId) => { + match protobuf_plugin_command.payload { + Some(Payload::TogglePaneEmbedOrEjectForPaneIdPayload( + toggle_pane_embed_or_eject_payload, + )) => match toggle_pane_embed_or_eject_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::TogglePaneEmbedOrEjectForPaneId( + pane_id.try_into()?, + )), + _ => Err("Malformed toggle_pane_embed_or_eject_payload payload"), + }, + _ => Err("Mismatched payload for TogglePaneEmbedOrEjectForPaneId"), + } + }, + Some(CommandName::CloseTabWithIndex) => match protobuf_plugin_command.payload { + Some(Payload::CloseTabWithIndexPayload(close_tab_index_payload)) => Ok( + PluginCommand::CloseTabWithIndex(close_tab_index_payload.tab_index as usize), + ), + _ => Err("Mismatched payload for CloseTabWithIndex"), + }, None => Err("Unrecognized plugin command"), } } @@ -1605,6 +1788,140 @@ impl TryFrom for ProtobufPluginCommand { terminal_pane_id, })), }), + PluginCommand::ResizePaneIdWithDirection(resize, pane_id) => { + Ok(ProtobufPluginCommand { + name: CommandName::ResizePaneIdWithDirection as i32, + payload: Some(Payload::ResizePaneIdWithDirectionPayload( + ResizePaneIdWithDirectionPayload { + resize: Some(resize.try_into()?), + pane_id: Some(pane_id.try_into()?), + }, + )), + }) + }, + PluginCommand::EditScrollbackForPaneWithId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::EditScrollbackForPaneWithId as i32, + payload: Some(Payload::EditScrollbackForPaneWithIdPayload( + EditScrollbackForPaneWithIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::WriteToPaneId(bytes_to_write, pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::WriteToPaneId as i32, + payload: Some(Payload::WriteToPaneIdPayload(WriteToPaneIdPayload { + bytes_to_write, + pane_id: Some(pane_id.try_into()?), + })), + }), + PluginCommand::WriteCharsToPaneId(chars_to_write, pane_id) => { + Ok(ProtobufPluginCommand { + name: CommandName::WriteCharsToPaneId as i32, + payload: Some(Payload::WriteCharsToPaneIdPayload( + WriteCharsToPaneIdPayload { + chars_to_write, + pane_id: Some(pane_id.try_into()?), + }, + )), + }) + }, + PluginCommand::MovePaneWithPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::MovePaneWithPaneId as i32, + payload: Some(Payload::MovePaneWithPaneIdPayload( + MovePaneWithPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::MovePaneWithPaneIdInDirection(pane_id, direction) => { + Ok(ProtobufPluginCommand { + name: CommandName::MovePaneWithPaneIdInDirection as i32, + payload: Some(Payload::MovePaneWithPaneIdInDirectionPayload( + MovePaneWithPaneIdInDirectionPayload { + pane_id: Some(pane_id.try_into()?), + direction: Some(direction.try_into()?), + }, + )), + }) + }, + PluginCommand::ClearScreenForPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ClearScreenForPaneId as i32, + payload: Some(Payload::ClearScreenForPaneIdPayload( + ClearScreenForPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::ScrollUpInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ScrollUpInPaneId as i32, + payload: Some(Payload::ScrollUpInPaneIdPayload(ScrollUpInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + })), + }), + PluginCommand::ScrollDownInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ScrollDownInPaneId as i32, + payload: Some(Payload::ScrollDownInPaneIdPayload( + ScrollDownInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::ScrollToTopInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ScrollToTopInPaneId as i32, + payload: Some(Payload::ScrollToTopInPaneIdPayload( + ScrollToTopInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::ScrollToBottomInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ScrollToBottomInPaneId as i32, + payload: Some(Payload::ScrollToBottomInPaneIdPayload( + ScrollToBottomInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::PageScrollUpInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::PageScrollUpInPaneId as i32, + payload: Some(Payload::PageScrollUpInPaneIdPayload( + PageScrollUpInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::PageScrollDownInPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::PageScrollDownInPaneId as i32, + payload: Some(Payload::PageScrollDownInPaneIdPayload( + PageScrollDownInPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::TogglePaneIdFullscreen(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::TogglePaneIdFullscreen as i32, + payload: Some(Payload::TogglePaneIdFullscreenPayload( + TogglePaneIdFullscreenPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::TogglePaneEmbedOrEjectForPaneId(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::TogglePaneEmbedOrEjectForPaneId as i32, + payload: Some(Payload::TogglePaneEmbedOrEjectForPaneIdPayload( + TogglePaneEmbedOrEjectForPaneIdPayload { + pane_id: Some(pane_id.try_into()?), + }, + )), + }), + PluginCommand::CloseTabWithIndex(tab_index) => Ok(ProtobufPluginCommand { + name: CommandName::CloseTabWithIndex as i32, + payload: Some(Payload::CloseTabWithIndexPayload( + CloseTabWithIndexPayload { + tab_index: tab_index as u32, + }, + )), + }), } } }