From bcbd940bf94cd76751af9b5fad64b716ce2cd3e6 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 15 Jun 2023 17:07:40 +0200 Subject: [PATCH] feat(plugins): plugin pane state events (#2545) * feat(plugins): report pane state to plugins * refactor(plugins): rename some stuff * tests(plugins): adjust for new behavior * style(fmt): rustfmt --- zellij-server/src/panes/active_panes.rs | 6 + zellij-server/src/panes/floating_panes/mod.rs | 17 +- zellij-server/src/panes/plugin_pane.rs | 7 + zellij-server/src/panes/terminal_pane.rs | 25 + zellij-server/src/panes/tiled_panes/mod.rs | 17 +- zellij-server/src/screen.rs | 144 ++++-- zellij-server/src/tab/mod.rs | 67 ++- zellij-server/src/unit/screen_tests.rs | 42 +- ...en__screen_tests__send_cli_rename_tab.snap | 426 +--------------- ...creen_tests__send_cli_undo_rename_tab.snap | 479 +----------------- zellij-tile/src/lib.rs | 2 +- zellij-utils/src/data.rs | 33 ++ 12 files changed, 313 insertions(+), 952 deletions(-) diff --git a/zellij-server/src/panes/active_panes.rs b/zellij-server/src/panes/active_panes.rs index c726bafe..039c71c6 100644 --- a/zellij-server/src/panes/active_panes.rs +++ b/zellij-server/src/panes/active_panes.rs @@ -98,4 +98,10 @@ impl ActivePanes { } } } + pub fn pane_id_is_focused(&self, pane_id: &PaneId) -> bool { + self.active_panes + .values() + .find(|p_id| **p_id == *pane_id) + .is_some() + } } diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 071f6e7c..98c6046b 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -1,11 +1,11 @@ mod floating_pane_grid; use zellij_utils::{ - data::{Direction, ResizeStrategy}, + data::{Direction, PaneInfo, ResizeStrategy}, position::Position, }; use crate::resize_pty; -use crate::tab::Pane; +use crate::tab::{pane_info_for_pane, Pane}; use floating_pane_grid::FloatingPaneGrid; use crate::{ @@ -885,4 +885,17 @@ impl FloatingPanes { Err(anyhow!("Pane not found")) } } + pub fn pane_info(&self) -> Vec { + let mut pane_infos = vec![]; + for (pane_id, pane) in self.panes.iter() { + let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane); + let is_focused = self.active_panes.pane_id_is_focused(pane_id); + pane_info_for_pane.is_floating = true; + pane_info_for_pane.is_suppressed = false; + pane_info_for_pane.is_focused = is_focused; + pane_info_for_pane.is_fullscreen = false; + pane_infos.push(pane_info_for_pane); + } + pane_infos + } } diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index d1d595f2..557769f0 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -564,6 +564,13 @@ impl Pane for PluginPane { self.loading_indication.to_string().as_bytes().to_vec(), ); } + fn current_title(&self) -> String { + if self.pane_name.is_empty() { + self.pane_title.to_owned() + } else { + self.pane_name.to_owned() + } + } } impl PluginPane { diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index c9e125b8..df74afd6 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -720,6 +720,31 @@ impl Pane for TerminalPane { fn set_title(&mut self, title: String) { self.pane_title = title; } + fn current_title(&self) -> String { + if self.pane_name.is_empty() { + self.grid + .title + .as_deref() + .unwrap_or(&self.pane_title) + .into() + } else { + self.pane_name.to_owned() + } + } + fn exit_status(&self) -> Option { + self.is_held + .as_ref() + .and_then(|(exit_status, _, _)| *exit_status) + } + fn is_held(&self) -> bool { + self.is_held.is_some() + } + fn exited(&self) -> bool { + match self.is_held { + Some((_, is_first_run, _)) => !is_first_run, + None => false, + } + } } impl TerminalPane { diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 3734214a..42405340 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -10,7 +10,7 @@ use crate::{ output::Output, panes::{ActivePanes, PaneId}, plugins::PluginInstruction, - tab::{Pane, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH}, + tab::{pane_info_for_pane, Pane, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH}, thread_bus::ThreadSenders, ui::boundaries::Boundaries, ui::pane_contents_and_ui::PaneContentsAndUi, @@ -18,7 +18,7 @@ use crate::{ }; use stacked_panes::StackedPanes; use zellij_utils::{ - data::{Direction, ModeInfo, ResizeStrategy, Style}, + data::{Direction, ModeInfo, PaneInfo, ResizeStrategy, Style}, errors::prelude::*, input::{ command::RunCommand, @@ -1709,6 +1709,19 @@ impl TiledPanes { .find(|(_id, s_p)| s_p.invoked_with() == &run) .map(|(id, _)| *id) } + pub fn pane_info(&self) -> Vec { + let mut pane_infos = vec![]; + for (pane_id, pane) in self.panes.iter() { + let mut pane_info_for_pane = pane_info_for_pane(pane_id, pane); + let is_focused = self.active_panes.pane_id_is_focused(pane_id); + pane_info_for_pane.is_floating = false; + pane_info_for_pane.is_suppressed = false; + pane_info_for_pane.is_focused = is_focused; + pane_info_for_pane.is_fullscreen = is_focused && self.fullscreen_is_active(); + pane_infos.push(pane_info_for_pane); + } + pane_infos + } } #[allow(clippy::borrowed_box)] diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 81177748..ae62be37 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use std::rc::Rc; use std::str; -use zellij_utils::data::{Direction, Resize, ResizeStrategy}; +use zellij_utils::data::{Direction, PaneManifest, Resize, ResizeStrategy}; use zellij_utils::errors::prelude::*; use zellij_utils::input::command::RunCommand; use zellij_utils::input::options::Clipboard; @@ -714,7 +714,8 @@ impl Screen { .non_fatal(); } - self.update_tabs().with_context(err_context)?; + self.report_tab_state().with_context(err_context)?; + self.report_pane_state().with_context(err_context)?; return self.render().with_context(err_context); }, Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(), @@ -843,7 +844,8 @@ impl Screen { t.position -= 1; } } - self.update_tabs().with_context(err_context)?; + self.report_tab_state().with_context(err_context)?; + self.report_pane_state().with_context(err_context)?; self.render().with_context(err_context) } } @@ -880,6 +882,7 @@ impl Screen { .with_context(err_context)?; tab.set_force_render(); } + self.report_pane_state().with_context(err_context)?; self.render().with_context(err_context) } @@ -1145,9 +1148,10 @@ impl Screen { self.add_client(client_id).with_context(err_context)?; } - self.update_tabs() + self.report_tab_state() .and_then(|_| self.render()) - .with_context(err_context) + .with_context(err_context)?; + self.report_pane_state().with_context(err_context) } pub fn add_client(&mut self, client_id: ClientId) -> Result<()> { @@ -1198,10 +1202,10 @@ impl Screen { self.tab_history.remove(&client_id); } self.connected_clients.borrow_mut().remove(&client_id); - self.update_tabs().with_context(err_context) + self.report_tab_state().with_context(err_context) } - pub fn update_tabs(&self) -> Result<()> { + pub fn report_tab_state(&self) -> Result<()> { let mut plugin_updates = vec![]; for (client_id, active_tab_index) in self.active_tab_indices.iter() { let mut tab_data = vec![]; @@ -1240,6 +1244,21 @@ impl Screen { .context("failed to update tabs")?; Ok(()) } + fn report_pane_state(&self) -> Result<()> { + let mut pane_manifest = PaneManifest::default(); + for tab in self.tabs.values() { + pane_manifest.panes.insert(tab.position, tab.pane_infos()); + } + self.bus + .senders + .send_to_plugin(PluginInstruction::Update(vec![( + None, + None, + Event::PaneUpdate(pane_manifest), + )])) + .context("failed to update tabs")?; + Ok(()) + } pub fn update_active_tab_name(&mut self, buf: Vec, client_id: ClientId) -> Result<()> { let err_context = @@ -1273,7 +1292,7 @@ impl Screen { } }, } - self.update_tabs().with_context(err_context) + self.report_tab_state().with_context(err_context) }, Err(err) => { Err::<(), _>(err).with_context(err_context).non_fatal(); @@ -1298,7 +1317,7 @@ impl Screen { Ok(active_tab) => { if active_tab.name != active_tab.prev_name { active_tab.name = active_tab.prev_name.clone(); - self.update_tabs() + self.report_tab_state() .context("failed to undo renaming of active tab")?; } }, @@ -1418,6 +1437,7 @@ impl Screen { Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(), }; } + self.report_pane_state().with_context(err_context)?; Ok(()) } pub fn move_focus_right_or_next_tab(&mut self, client_id: ClientId) -> Result<()> { @@ -1452,6 +1472,7 @@ impl Screen { Err(err) => Err::<(), _>(err).with_context(err_context).non_fatal(), }; } + self.report_pane_state().with_context(err_context)?; Ok(()) } pub fn toggle_tab(&mut self, client_id: ClientId) -> Result<()> { @@ -1464,7 +1485,8 @@ impl Screen { .context("failed to toggle tabs")?; }; - self.update_tabs().context("failed to toggle tabs")?; + self.report_tab_state().context("failed to toggle tabs")?; + self.report_pane_state().context("failed to toggle tabs")?; self.render() } @@ -1492,6 +1514,7 @@ impl Screen { .with_context(err_context)? .focus_pane_with_id(plugin_pane_id, should_float, client_id) .context("failed to focus plugin pane")?; + self.report_pane_state().with_context(err_context)?; Ok(true) }, None => Ok(false), @@ -1631,7 +1654,8 @@ pub(crate) fn screen_thread_main( }, }; screen.unblock_input()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, @@ -1639,7 +1663,8 @@ pub(crate) fn screen_thread_main( active_tab!(screen, client_id, |tab: &mut Tab| tab .suppress_active_pane(pid, client_id), ?); screen.unblock_input()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, @@ -1647,7 +1672,8 @@ pub(crate) fn screen_thread_main( active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab .toggle_pane_embed_or_floating(client_id), ?); screen.unblock_input()?; - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins + screen.report_pane_state()?; screen.render()?; }, @@ -1655,7 +1681,8 @@ pub(crate) fn screen_thread_main( active_tab_and_connected_client_id!(screen, client_id, |tab: &mut Tab, client_id: ClientId| tab .toggle_floating_panes(Some(client_id), default_shell), ?); screen.unblock_input()?; - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins + screen.report_pane_state()?; screen.render()?; }, @@ -1685,7 +1712,8 @@ pub(crate) fn screen_thread_main( ); } screen.unblock_input()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, ScreenInstruction::VerticalSplit( @@ -1714,11 +1742,12 @@ pub(crate) fn screen_thread_main( ); } screen.unblock_input()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, ScreenInstruction::WriteCharacter(bytes, client_id) => { - let mut should_update_tabs = false; + let mut state_changed = false; active_tab_and_connected_client_id!( screen, client_id, @@ -1728,14 +1757,15 @@ pub(crate) fn screen_thread_main( false => tab.write_to_active_terminal(bytes, client_id), }; if let Ok(true) = write_result { - should_update_tabs = true; + state_changed = true; } write_result }, ? ); - if should_update_tabs { - screen.update_tabs()?; + if state_changed { + screen.report_tab_state()?; + screen.report_pane_state()?; } }, ScreenInstruction::Resize(client_id, strategy) => { @@ -1747,7 +1777,8 @@ pub(crate) fn screen_thread_main( ); screen.unblock_input()?; screen.render()?; - screen.update_tabs()?; // TODO: no every time + screen.report_tab_state()?; + screen.report_pane_state()?; }, ScreenInstruction::SwitchFocus(client_id) => { active_tab_and_connected_client_id!( @@ -1757,6 +1788,7 @@ pub(crate) fn screen_thread_main( ); screen.unblock_input()?; screen.render()?; + screen.report_pane_state()?; }, ScreenInstruction::FocusNextPane(client_id) => { active_tab_and_connected_client_id!( @@ -1775,6 +1807,7 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusLeft(client_id) => { active_tab_and_connected_client_id!( @@ -1785,11 +1818,13 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => { screen.move_focus_left_or_previous_tab(client_id)?; screen.unblock_input()?; screen.render()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusDown(client_id) => { active_tab_and_connected_client_id!( @@ -1800,6 +1835,7 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusRight(client_id) => { active_tab_and_connected_client_id!( @@ -1810,11 +1846,13 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusRightOrNextTab(client_id) => { screen.move_focus_right_or_next_tab(client_id)?; screen.unblock_input()?; screen.render()?; + screen.report_pane_state()?; }, ScreenInstruction::MoveFocusUp(client_id) => { active_tab_and_connected_client_id!( @@ -1825,6 +1863,7 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::ClearScreen(client_id) => { active_tab_and_connected_client_id!( @@ -1860,6 +1899,7 @@ pub(crate) fn screen_thread_main( ? ); screen.render()?; + screen.report_pane_state()?; }, ScreenInstruction::ScrollUp(client_id) => { active_tab_and_connected_client_id!( @@ -1876,9 +1916,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MovePaneBackwards(client_id) => { active_tab_and_connected_client_id!( @@ -1886,9 +1927,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_backwards(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MovePaneDown(client_id) => { active_tab_and_connected_client_id!( @@ -1896,9 +1938,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_down(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MovePaneUp(client_id) => { active_tab_and_connected_client_id!( @@ -1906,9 +1949,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_up(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MovePaneRight(client_id) => { active_tab_and_connected_client_id!( @@ -1916,9 +1960,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_right(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::MovePaneLeft(client_id) => { active_tab_and_connected_client_id!( @@ -1926,9 +1971,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.move_active_pane_left(client_id) ); - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::ScrollUpAt(point, client_id) => { active_tab_and_connected_client_id!( @@ -2035,9 +2081,10 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, client_id: ClientId| tab.close_focused_pane(client_id), ? ); - screen.update_tabs()?; + screen.report_tab_state()?; screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::SetSelectable(id, selectable, tab_index) => { screen.get_indexed_tab_mut(tab_index).map_or_else( @@ -2052,6 +2099,7 @@ pub(crate) fn screen_thread_main( ); screen.render()?; + screen.report_pane_state()?; }, ScreenInstruction::ClosePane(id, client_id) => { match client_id { @@ -2071,8 +2119,9 @@ pub(crate) fn screen_thread_main( } }, } - screen.update_tabs()?; + screen.report_tab_state()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::HoldPane(id, exit_status, run_command, tab_index, client_id) => { let is_first_run = false; @@ -2101,8 +2150,9 @@ pub(crate) fn screen_thread_main( } }, } - screen.update_tabs()?; + screen.report_tab_state()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::UpdatePaneName(c, client_id) => { active_tab_and_connected_client_id!( @@ -2112,6 +2162,7 @@ pub(crate) fn screen_thread_main( ); screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::UndoRenamePane(client_id) => { active_tab_and_connected_client_id!( @@ -2129,9 +2180,10 @@ pub(crate) fn screen_thread_main( |tab: &mut Tab, client_id: ClientId| tab .toggle_active_pane_fullscreen(client_id) ); - screen.update_tabs()?; + screen.report_tab_state()?; screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::TogglePaneFrames => { screen.draw_pane_frames = !screen.draw_pane_frames; @@ -2140,6 +2192,7 @@ pub(crate) fn screen_thread_main( } screen.render()?; screen.unblock_input()?; + screen.report_pane_state()?; }, ScreenInstruction::SwitchTabNext(client_id) => { screen.switch_tab_next(None, client_id)?; @@ -2287,7 +2340,7 @@ pub(crate) fn screen_thread_main( }, ScreenInstruction::TerminalResize(new_size) => { screen.resize_to_screen(new_size)?; - screen.update_tabs()?; // update tabs so that the ui indication will be send to the plugins + screen.report_tab_state()?; // update tabs so that the ui indication will be send to the plugins screen.render()?; }, ScreenInstruction::TerminalPixelDimensions(pixel_dimensions) => { @@ -2318,28 +2371,31 @@ pub(crate) fn screen_thread_main( client_id, |tab: &mut Tab, _client_id: ClientId| tab.toggle_sync_panes_is_active() ); - screen.update_tabs()?; + screen.report_tab_state()?; screen.render()?; screen.unblock_input()?; }, ScreenInstruction::LeftClick(point, client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab .handle_left_click(&point, client_id), ?); - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; screen.unblock_input()?; }, ScreenInstruction::RightClick(point, client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab .handle_right_click(&point, client_id), ?); - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; screen.unblock_input()?; }, ScreenInstruction::MiddleClick(point, client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab .handle_middle_click(&point, client_id), ?); - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; screen.unblock_input()?; }, @@ -2389,11 +2445,13 @@ pub(crate) fn screen_thread_main( }, ScreenInstruction::AddClient(client_id) => { screen.add_client(client_id)?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, ScreenInstruction::RemoveClient(client_id) => { screen.remove_client(client_id)?; + screen.report_pane_state()?; screen.render()?; }, ScreenInstruction::AddOverlay(overlay, _client_id) => { @@ -2508,7 +2566,8 @@ pub(crate) fn screen_thread_main( ? ); screen.render()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.unblock_input()?; }, ScreenInstruction::NextSwapLayout(client_id) => { @@ -2519,7 +2578,8 @@ pub(crate) fn screen_thread_main( ? ); screen.render()?; - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.unblock_input()?; }, ScreenInstruction::QueryTabNames(client_id) => { @@ -2609,6 +2669,7 @@ pub(crate) fn screen_thread_main( } else { log::error!("Tab index not found: {:?}", tab_index); } + screen.report_pane_state()?; screen.unblock_input()?; }, ScreenInstruction::UpdatePluginLoadingStage(pid, loading_indication) => { @@ -2646,7 +2707,8 @@ pub(crate) fn screen_thread_main( for tab in all_tabs.values_mut() { tab.update_input_modes()?; } - screen.update_tabs()?; + screen.report_tab_state()?; + screen.report_pane_state()?; screen.render()?; }, ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => { @@ -2665,6 +2727,7 @@ pub(crate) fn screen_thread_main( Some((tab_index, client_id)) => { if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? { screen.render()?; + screen.report_pane_state()?; } else { screen.bus.senders.send_to_plugin(PluginInstruction::Load( Some(should_float), @@ -2688,6 +2751,7 @@ pub(crate) fn screen_thread_main( break; } } + screen.report_pane_state()?; }, } } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 5235a8a6..cf326b86 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -9,7 +9,7 @@ mod swap_layouts; use copy_command::CopyCommand; use std::env::temp_dir; use uuid::Uuid; -use zellij_utils::data::{Direction, ResizeStrategy}; +use zellij_utils::data::{Direction, PaneInfo, ResizeStrategy}; use zellij_utils::errors::prelude::*; use zellij_utils::input::command::RunCommand; use zellij_utils::position::{Column, Line}; @@ -446,6 +446,16 @@ pub trait Pane { fn update_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins fn start_loading_indication(&mut self, _loading_indication: LoadingIndication) {} // only relevant for plugins fn progress_animation_offset(&mut self) {} // only relevant for plugins + fn current_title(&self) -> String; + fn is_held(&self) -> bool { + false + } + fn exited(&self) -> bool { + false + } + fn exit_status(&self) -> Option { + None + } } #[derive(Clone, Debug)] @@ -1532,6 +1542,7 @@ impl Tab { command, )) .with_context(err_context)?; + should_update_ui = true; }, Some(AdjustedInput::CloseThisPane) => { self.close_pane(PaneId::Terminal(active_terminal_id), false, None); @@ -3299,6 +3310,22 @@ impl Tab { self.suppressed_panes.insert(pane_id, pane); } } + pub fn pane_infos(&self) -> Vec { + let mut pane_info = vec![]; + let mut tiled_pane_info = self.tiled_panes.pane_info(); + let mut floating_pane_info = self.floating_panes.pane_info(); + pane_info.append(&mut tiled_pane_info); + pane_info.append(&mut floating_pane_info); + for (pane_id, pane) in self.suppressed_panes.iter() { + let mut pane_info_for_suppressed_pane = pane_info_for_pane(pane_id, pane); + pane_info_for_suppressed_pane.is_floating = false; + pane_info_for_suppressed_pane.is_suppressed = true; + pane_info_for_suppressed_pane.is_focused = false; + pane_info_for_suppressed_pane.is_fullscreen = false; + pane_info.push(pane_info_for_suppressed_pane); + } + pane_info + } fn add_floating_pane( &mut self, mut pane: Box, @@ -3359,6 +3386,44 @@ impl Tab { } } +pub fn pane_info_for_pane(pane_id: &PaneId, pane: &Box) -> PaneInfo { + let mut pane_info = PaneInfo::default(); + pane_info.pane_x = pane.x(); + pane_info.pane_content_x = pane.get_content_x(); + pane_info.pane_y = pane.y(); + pane_info.pane_content_y = pane.get_content_y(); + pane_info.pane_rows = pane.rows(); + pane_info.pane_content_rows = pane.get_content_rows(); + pane_info.pane_columns = pane.cols(); + pane_info.pane_content_columns = pane.get_content_columns(); + pane_info.cursor_coordinates_in_pane = pane.cursor_coordinates(); + pane_info.is_selectable = pane.selectable(); + pane_info.title = pane.current_title(); + pane_info.exited = pane.exited(); + pane_info.exit_status = pane.exit_status(); + pane_info.is_held = pane.is_held(); + + match pane_id { + PaneId::Terminal(terminal_id) => { + pane_info.id = *terminal_id; + pane_info.is_plugin = false; + pane_info.terminal_command = pane.invoked_with().as_ref().and_then(|c| match c { + Run::Command(run_command) => Some(run_command.to_string()), + _ => None, + }); + }, + PaneId::Plugin(plugin_id) => { + pane_info.id = *plugin_id; + pane_info.is_plugin = true; + pane_info.plugin_url = pane.invoked_with().as_ref().and_then(|c| match c { + Run::Plugin(run_plugin) => Some(run_plugin.location.to_string()), + _ => None, + }); + }, + } + pane_info +} + #[cfg(test)] #[path = "./unit/tab_tests.rs"] mod tab_tests; diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 51dfbfd3..1040062b 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -10,7 +10,7 @@ use crate::{ use insta::assert_snapshot; use std::path::PathBuf; use zellij_utils::cli::CliAction; -use zellij_utils::data::Resize; +use zellij_utils::data::{Event, Resize}; use zellij_utils::errors::{prelude::*, ErrorContext}; use zellij_utils::input::actions::Action; use zellij_utils::input::command::{RunCommand, TerminalAction}; @@ -2431,10 +2431,22 @@ pub fn send_cli_rename_tab() { send_cli_action_to_server(&session_metadata, rename_tab, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); mock_screen.teardown(vec![plugin_thread, screen_thread]); - assert_snapshot!(format!( - "{:#?}", - *received_plugin_instructions.lock().unwrap() - )) + let plugin_rename_tab_instruction = received_plugin_instructions + .lock() + .unwrap() + .iter() + .find(|instruction| match instruction { + PluginInstruction::Update(updates) => updates + .iter() + .find(|u| match u { + (_, _, Event::TabUpdate(..)) => true, + _ => false, + }) + .is_some(), + _ => false, + }) + .cloned(); + assert_snapshot!(format!("{:#?}", plugin_rename_tab_instruction)) } #[test] @@ -2469,10 +2481,22 @@ pub fn send_cli_undo_rename_tab() { send_cli_action_to_server(&session_metadata, undo_rename_tab, client_id); std::thread::sleep(std::time::Duration::from_millis(100)); mock_screen.teardown(vec![plugin_thread, screen_thread]); - assert_snapshot!(format!( - "{:#?}", - *received_plugin_instructions.lock().unwrap() - )) + let plugin_undo_rename_tab_instruction = received_plugin_instructions + .lock() + .unwrap() + .iter() + .find(|instruction| match instruction { + PluginInstruction::Update(updates) => updates + .iter() + .find(|u| match u { + (_, _, Event::TabUpdate(..)) => true, + _ => false, + }) + .is_some(), + _ => false, + }) + .cloned(); + assert_snapshot!(format!("{:#?}", plugin_undo_rename_tab_instruction)) } #[test] diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap index f5e86b28..0538dbcd 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_rename_tab.snap @@ -1,164 +1,9 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2632 -expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" +assertion_line: 2448 +expression: "format!(\"{:#?}\", plugin_rename_tab_instruction)" --- -[ - Update( - [ - ( - None, - Some( - 10, - ), - InputReceived, - ), - ], - ), - Update( - [ - ( - None, - Some( - 10, - ), - InputReceived, - ), - ], - ), - NewTab( - None, - None, - Some( - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [ - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ), - [], - 0, - 1, - ), - Update( - [ - ( - None, - Some( - 1, - ), - ModeUpdate( - ModeInfo { - mode: Normal, - keybinds: [], - style: Style { - colors: Palette { - source: Default, - theme_hue: Dark, - fg: EightBit( - 0, - ), - bg: EightBit( - 0, - ), - black: EightBit( - 0, - ), - red: EightBit( - 0, - ), - green: EightBit( - 0, - ), - yellow: EightBit( - 0, - ), - blue: EightBit( - 0, - ), - magenta: EightBit( - 0, - ), - cyan: EightBit( - 0, - ), - white: EightBit( - 0, - ), - orange: EightBit( - 0, - ), - gray: EightBit( - 0, - ), - purple: EightBit( - 0, - ), - gold: EightBit( - 0, - ), - silver: EightBit( - 0, - ), - pink: EightBit( - 0, - ), - brown: EightBit( - 0, - ), - }, - rounded_corners: false, - hide_session_name: false, - }, - capabilities: PluginCapabilities { - arrow_fonts: false, - }, - session_name: Some( - "zellij-test", - ), - }, - ), - ), - ], - ), - Update( - [], - ), +Some( Update( [ ( @@ -187,267 +32,4 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" ), ], ), - NewTab( - None, - None, - Some( - TiledPaneLayout { - children_split_direction: Vertical, - name: None, - children: [ - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ), - [], - 1, - 1, - ), - Update( - [], - ), - Update( - [ - ( - None, - Some( - 1, - ), - ModeUpdate( - ModeInfo { - mode: Normal, - keybinds: [], - style: Style { - colors: Palette { - source: Default, - theme_hue: Dark, - fg: EightBit( - 0, - ), - bg: EightBit( - 0, - ), - black: EightBit( - 0, - ), - red: EightBit( - 0, - ), - green: EightBit( - 0, - ), - yellow: EightBit( - 0, - ), - blue: EightBit( - 0, - ), - magenta: EightBit( - 0, - ), - cyan: EightBit( - 0, - ), - white: EightBit( - 0, - ), - orange: EightBit( - 0, - ), - gray: EightBit( - 0, - ), - purple: EightBit( - 0, - ), - gold: EightBit( - 0, - ), - silver: EightBit( - 0, - ), - pink: EightBit( - 0, - ), - brown: EightBit( - 0, - ), - }, - rounded_corners: false, - hide_session_name: false, - }, - capabilities: PluginCapabilities { - arrow_fonts: false, - }, - session_name: Some( - "zellij-test", - ), - }, - ), - ), - ], - ), - Update( - [], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "Tab #2", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "new-tab-name", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Exit, -] +) diff --git a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap index bae48188..d19474f2 100644 --- a/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap +++ b/zellij-server/src/unit/snapshots/zellij_server__screen__screen_tests__send_cli_undo_rename_tab.snap @@ -1,164 +1,9 @@ --- source: zellij-server/src/./unit/screen_tests.rs -assertion_line: 2675 -expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" +assertion_line: 2486 +expression: "format!(\"{:#?}\", plugin_undo_rename_tab_instruction)" --- -[ - Update( - [ - ( - None, - Some( - 10, - ), - InputReceived, - ), - ], - ), - Update( - [ - ( - None, - Some( - 10, - ), - InputReceived, - ), - ], - ), - NewTab( - None, - None, - Some( - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [ - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ), - [], - 0, - 1, - ), - Update( - [ - ( - None, - Some( - 1, - ), - ModeUpdate( - ModeInfo { - mode: Normal, - keybinds: [], - style: Style { - colors: Palette { - source: Default, - theme_hue: Dark, - fg: EightBit( - 0, - ), - bg: EightBit( - 0, - ), - black: EightBit( - 0, - ), - red: EightBit( - 0, - ), - green: EightBit( - 0, - ), - yellow: EightBit( - 0, - ), - blue: EightBit( - 0, - ), - magenta: EightBit( - 0, - ), - cyan: EightBit( - 0, - ), - white: EightBit( - 0, - ), - orange: EightBit( - 0, - ), - gray: EightBit( - 0, - ), - purple: EightBit( - 0, - ), - gold: EightBit( - 0, - ), - silver: EightBit( - 0, - ), - pink: EightBit( - 0, - ), - brown: EightBit( - 0, - ), - }, - rounded_corners: false, - hide_session_name: false, - }, - capabilities: PluginCapabilities { - arrow_fonts: false, - }, - session_name: Some( - "zellij-test", - ), - }, - ), - ), - ], - ), - Update( - [], - ), +Some( Update( [ ( @@ -187,320 +32,4 @@ expression: "format!(\"{:#?}\", * received_plugin_instructions.lock().unwrap())" ), ], ), - NewTab( - None, - None, - Some( - TiledPaneLayout { - children_split_direction: Vertical, - name: None, - children: [ - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - TiledPaneLayout { - children_split_direction: Horizontal, - name: None, - children: [], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ], - split_size: None, - run: None, - borderless: false, - focus: None, - external_children_index: None, - children_are_stacked: false, - is_expanded_in_stack: false, - exclude_from_sync: None, - }, - ), - [], - 1, - 1, - ), - Update( - [], - ), - Update( - [ - ( - None, - Some( - 1, - ), - ModeUpdate( - ModeInfo { - mode: Normal, - keybinds: [], - style: Style { - colors: Palette { - source: Default, - theme_hue: Dark, - fg: EightBit( - 0, - ), - bg: EightBit( - 0, - ), - black: EightBit( - 0, - ), - red: EightBit( - 0, - ), - green: EightBit( - 0, - ), - yellow: EightBit( - 0, - ), - blue: EightBit( - 0, - ), - magenta: EightBit( - 0, - ), - cyan: EightBit( - 0, - ), - white: EightBit( - 0, - ), - orange: EightBit( - 0, - ), - gray: EightBit( - 0, - ), - purple: EightBit( - 0, - ), - gold: EightBit( - 0, - ), - silver: EightBit( - 0, - ), - pink: EightBit( - 0, - ), - brown: EightBit( - 0, - ), - }, - rounded_corners: false, - hide_session_name: false, - }, - capabilities: PluginCapabilities { - arrow_fonts: false, - }, - session_name: Some( - "zellij-test", - ), - }, - ), - ), - ], - ), - Update( - [], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "Tab #2", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "new-tab-name", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Update( - [ - ( - None, - Some( - 10, - ), - InputReceived, - ), - ], - ), - Update( - [ - ( - None, - Some( - 1, - ), - TabUpdate( - [ - TabInfo { - position: 0, - name: "Tab #1", - active: false, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - TabInfo { - position: 1, - name: "Tab #2", - active: true, - panes_to_hide: 0, - is_fullscreen_active: false, - is_sync_panes_active: false, - are_floating_panes_visible: false, - other_focused_clients: [], - active_swap_layout_name: Some( - "BASE", - ), - is_swap_layout_dirty: false, - }, - ], - ), - ), - ], - ), - Exit, -] +) diff --git a/zellij-tile/src/lib.rs b/zellij-tile/src/lib.rs index 7968ced3..cca2a876 100644 --- a/zellij-tile/src/lib.rs +++ b/zellij-tile/src/lib.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use zellij_utils::data::Event; #[allow(unused_variables)] -pub trait ZellijPlugin { +pub trait ZellijPlugin: Default { fn load(&mut self) {} fn update(&mut self, event: Event) -> bool { false diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index d470c248..9a228c72 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -2,6 +2,7 @@ use crate::input::actions::Action; use crate::input::config::ConversionError; use clap::ArgEnum; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fmt; use std::path::PathBuf; use std::str::FromStr; @@ -463,6 +464,7 @@ pub enum Mouse { pub enum Event { ModeUpdate(ModeInfo), TabUpdate(Vec), + PaneUpdate(PaneManifest), Key(Key), Mouse(Mouse), Timer(f64), @@ -700,6 +702,37 @@ pub struct TabInfo { pub is_swap_layout_dirty: bool, } +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct PaneManifest { + pub panes: HashMap>, // usize is the tab position +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct PaneInfo { + pub id: u32, + pub is_plugin: bool, + pub is_focused: bool, + pub is_fullscreen: bool, + pub is_floating: bool, + pub is_suppressed: bool, + pub title: String, + pub exited: bool, + pub exit_status: Option, + pub is_held: bool, + pub pane_x: usize, + pub pane_content_x: usize, + pub pane_y: usize, + pub pane_content_y: usize, + pub pane_rows: usize, + pub pane_content_rows: usize, + pub pane_columns: usize, + pub pane_content_columns: usize, + pub cursor_coordinates_in_pane: Option<(usize, usize)>, // x, y if cursor is visible + pub terminal_command: Option, + pub plugin_url: Option, + pub is_selectable: bool, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct PluginIds { pub plugin_id: u32,