diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index 6aca8718..81ccc235 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -525,6 +525,9 @@ impl ZellijPlugin for State { ]; rebind_keys(keys_to_unbind, keys_to_rebind, write_to_disk); }, + BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Alt]) => { + list_clients(); + }, _ => {}, }, Event::CustomMessage(message, payload) => { diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 3a8caec6..783f4a4f 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -26,7 +26,7 @@ use wasm_bridge::WasmBridge; use zellij_utils::{ async_std::{channel, future::timeout, task}, data::{ - Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType, + ClientInfo, Event, EventType, InputMode, MessageToPlugin, PermissionStatus, PermissionType, PipeMessage, PipeSource, PluginCapabilities, }, errors::{prelude::*, ContextType, PluginContext}, @@ -158,6 +158,7 @@ pub enum PluginInstruction { file_path: Option, }, WatchFilesystem, + ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId), Exit, } @@ -203,6 +204,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::FailedToWriteConfigToDisk { .. } => { PluginContext::FailedToWriteConfigToDisk }, + PluginInstruction::ListClientsToPlugin(..) => PluginContext::ListClientsToPlugin, } } } @@ -607,6 +609,35 @@ pub(crate) fn plugin_thread_main( }, } }, + PluginInstruction::ListClientsToPlugin( + mut session_layout_metadata, + plugin_id, + client_id, + ) => { + populate_session_layout_metadata( + &mut session_layout_metadata, + &wasm_bridge, + &plugin_aliases, + ); + let mut clients_metadata = session_layout_metadata.all_clients_metadata(); + let mut client_list_for_plugin = vec![]; + let default_editor = session_layout_metadata.default_editor.clone(); + for (client_metadata_id, client_metadata) in clients_metadata.iter_mut() { + let is_current_client = client_metadata_id == &client_id; + client_list_for_plugin.push(ClientInfo::new( + *client_metadata_id, + client_metadata.get_pane_id().into(), + client_metadata.stringify_command(&default_editor), + is_current_client, + )); + } + let updates = vec![( + Some(plugin_id), + Some(client_id), + Event::ListClients(client_list_for_plugin), + )]; + wasm_bridge.update_plugins(updates, shutdown_send.clone())?; + }, PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => { populate_session_layout_metadata( &mut session_layout_metadata, diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 0750c0dd..63d88115 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -8551,3 +8551,74 @@ pub fn rebind_keys_plugin_command() { .clone(); assert_snapshot!(format!("{:#?}", rebind_event)); } + +#[test] +#[ignore] +pub fn list_clients_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::ListClientsToPlugin, + 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, + Some(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('z')).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 list_clients_instruction = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::ListClientsToPlugin(..) = i { + Some(i.clone()) + } else { + None + } + }) + .unwrap(); + assert_snapshot!(format!("{:#?}", list_clients_instruction)); +} diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__list_clients_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__list_clients_plugin_command.snap new file mode 100644 index 00000000..80f38132 --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__list_clients_plugin_command.snap @@ -0,0 +1,9 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 8623 +expression: "format!(\"{:#?}\", list_clients_instruction)" +--- +ListClientsToPlugin( + 0, + 1, +) diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 5dd37485..d308689b 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -351,6 +351,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { keys_to_unbind, write_config_to_disk, } => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?, + PluginCommand::ListClients => list_clients(env), }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1474,6 +1475,15 @@ fn dump_session_layout(env: &PluginEnv) { .map(|sender| sender.send(ScreenInstruction::DumpLayoutToPlugin(env.plugin_id))); } +fn list_clients(env: &PluginEnv) { + let _ = env.senders.to_screen.as_ref().map(|sender| { + sender.send(ScreenInstruction::ListClientsToPlugin( + env.plugin_id, + env.client_id, + )) + }); +} + fn scan_host_folder(env: &PluginEnv, folder_to_scan: PathBuf) { if !folder_to_scan.starts_with("/host") { log::error!( @@ -1897,7 +1907,9 @@ fn check_command_permission( | PluginCommand::BlockCliPipeInput(..) | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes, PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins, - PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState, + PluginCommand::ListClients | PluginCommand::DumpSessionLayout => { + PermissionType::ReadApplicationState + }, PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => { PermissionType::Reconfigure }, diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 47542dc4..2ecc35fe 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -101,6 +101,7 @@ pub enum PtyInstruction { client_id: ClientId, default_editor: Option, }, + ListClientsToPlugin(SessionLayoutMetadata, PluginId, ClientId), Exit, } @@ -125,6 +126,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd, PtyInstruction::ListClientsMetadata(..) => PtyContext::ListClientsMetadata, PtyInstruction::Reconfigure { .. } => PtyContext::Reconfigure, + PtyInstruction::ListClientsToPlugin(..) => PtyContext::ListClientsToPlugin, PtyInstruction::Exit => PtyContext::Exit, } } @@ -724,6 +726,23 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { .with_context(err_context) .non_fatal(); }, + PtyInstruction::ListClientsToPlugin( + mut session_layout_metadata, + plugin_id, + client_id, + ) => { + let err_context = || format!("Failed to dump layout"); + pty.populate_session_layout_metadata(&mut session_layout_metadata); + pty.bus + .senders + .send_to_plugin(PluginInstruction::ListClientsToPlugin( + session_layout_metadata, + plugin_id, + client_id, + )) + .with_context(err_context) + .non_fatal(); + }, PtyInstruction::LogLayoutToHd(mut session_layout_metadata) => { let err_context = || format!("Failed to dump layout"); pty.populate_session_layout_metadata(&mut session_layout_metadata); diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 1d266859..eb5dca42 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -410,6 +410,7 @@ pub enum ScreenInstruction { should_change_focus_to_new_tab: bool, client_id: ClientId, }, + ListClientsToPlugin(PluginId, ClientId), } impl From<&ScreenInstruction> for ScreenContext { @@ -621,6 +622,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::BreakPanesToTabWithIndex { .. } => { ScreenContext::BreakPanesToTabWithIndex }, + ScreenInstruction::ListClientsToPlugin(..) => ScreenContext::ListClientsToPlugin, } } } @@ -2506,7 +2508,7 @@ impl Screen { let active_tab_index = first_client_id.and_then(|client_id| self.active_tab_indices.get(&client_id)); - for (tab_index, tab) in self.tabs.values().enumerate() { + for (tab_index, tab) in self.tabs.iter() { let tab_is_focused = active_tab_index == Some(&tab_index); let hide_floating_panes = !tab.are_floating_panes_visible(); let mut suppressed_panes = HashMap::new(); @@ -3110,6 +3112,21 @@ pub(crate) fn screen_thread_main( .with_context(err_context) .non_fatal(); }, + ScreenInstruction::ListClientsToPlugin(plugin_id, client_id) => { + let err_context = || format!("Failed to dump layout"); + let session_layout_metadata = + screen.get_layout_metadata(screen.default_shell.clone()); + screen + .bus + .senders + .send_to_pty(PtyInstruction::ListClientsToPlugin( + session_layout_metadata, + plugin_id, + client_id, + )) + .with_context(err_context) + .non_fatal(); + }, ScreenInstruction::EditScrollback(client_id) => { active_tab_and_connected_client_id!( screen, diff --git a/zellij-server/src/session_layout_metadata.rs b/zellij-server/src/session_layout_metadata.rs index 1faa9f6b..b67659ec 100644 --- a/zellij-server/src/session_layout_metadata.rs +++ b/zellij-server/src/session_layout_metadata.rs @@ -82,6 +82,28 @@ impl SessionLayoutMetadata { ClientMetadata::render_many(clients_metadata, &self.default_editor) } + pub fn all_clients_metadata(&self) -> BTreeMap { + let mut clients_metadata: BTreeMap = BTreeMap::new(); + for tab in &self.tabs { + let panes = if tab.hide_floating_panes { + &tab.tiled_panes + } else { + &tab.floating_panes + }; + for pane in panes { + for focused_client in &pane.focused_clients { + clients_metadata.insert( + *focused_client, + ClientMetadata { + pane_id: pane.id.clone(), + command: pane.run.clone(), + }, + ); + } + } + } + clients_metadata + } pub fn is_dirty(&self) -> bool { // here we check to see if the serialized layout would be different than the base one, and // thus is "dirty". A layout is considered dirty if one of the following is true: @@ -372,7 +394,7 @@ impl PaneLayoutMetadata { } } -struct ClientMetadata { +pub struct ClientMetadata { pane_id: PaneId, command: Option, } @@ -404,6 +426,9 @@ impl ClientMetadata { }; stringified.unwrap_or("N/A".to_owned()) } + pub fn get_pane_id(&self) -> PaneId { + self.pane_id + } pub fn render_many( clients_metadata: BTreeMap, default_editor: &Option, diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index c51a93d8..bc1b4844 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -854,6 +854,15 @@ pub fn dump_session_layout() { unsafe { host_run_plugin_command() }; } +/// Get a list of clients, their focused pane and running command or focused plugin back as an +/// Event::ListClients (note: this event must be subscribed to) +pub fn list_clients() { + let plugin_command = PluginCommand::ListClients; + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + /// Change configuration for the current user pub fn reconfigure(new_config: String, save_configuration_file: bool) { let plugin_command = PluginCommand::Reconfigure(new_config, save_configuration_file); diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs index 79910d9b..02d709e6 100644 --- a/zellij-utils/assets/prost/api.event.rs +++ b/zellij-utils/assets/prost/api.event.rs @@ -11,7 +11,7 @@ pub struct Event { pub name: i32, #[prost( oneof = "event::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23" )] pub payload: ::core::option::Option, } @@ -62,10 +62,30 @@ pub mod event { CommandPaneRerunPayload(super::CommandPaneReRunPayload), #[prost(message, tag = "22")] FailedToWriteConfigToDiskPayload(super::FailedToWriteConfigToDiskPayload), + #[prost(message, tag = "23")] + ListClientsPayload(super::ListClientsPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListClientsPayload { + #[prost(message, repeated, tag = "1")] + pub client_info: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClientInfo { + #[prost(uint32, tag = "1")] + pub client_id: u32, + #[prost(message, optional, tag = "2")] + pub pane_id: ::core::option::Option, + #[prost(string, tag = "3")] + pub running_command: ::prost::alloc::string::String, + #[prost(bool, tag = "4")] + pub is_current_client: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct FailedToWriteConfigToDiskPayload { #[prost(string, optional, tag = "1")] pub file_path: ::core::option::Option<::prost::alloc::string::String>, @@ -447,6 +467,7 @@ pub enum EventType { EditPaneExited = 23, CommandPaneReRun = 24, FailedToWriteConfigToDisk = 25, + ListClients = 26, } impl EventType { /// String value of the enum field names used in the ProtoBuf definition. @@ -481,6 +502,7 @@ impl EventType { EventType::EditPaneExited => "EditPaneExited", EventType::CommandPaneReRun => "CommandPaneReRun", EventType::FailedToWriteConfigToDisk => "FailedToWriteConfigToDisk", + EventType::ListClients => "ListClients", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -512,6 +534,7 @@ impl EventType { "EditPaneExited" => Some(Self::EditPaneExited), "CommandPaneReRun" => Some(Self::CommandPaneReRun), "FailedToWriteConfigToDisk" => Some(Self::FailedToWriteConfigToDisk), + "ListClients" => Some(Self::ListClients), _ => None, } } diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index f3bd625b..8b3b6752 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -715,6 +715,7 @@ pub enum CommandName { ReloadPlugin = 110, LoadNewPlugin = 111, RebindKeys = 112, + ListClients = 113, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -838,6 +839,7 @@ impl CommandName { CommandName::ReloadPlugin => "ReloadPlugin", CommandName::LoadNewPlugin => "LoadNewPlugin", CommandName::RebindKeys => "RebindKeys", + CommandName::ListClients => "ListClients", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -958,6 +960,7 @@ impl CommandName { "ReloadPlugin" => Some(Self::ReloadPlugin), "LoadNewPlugin" => Some(Self::LoadNewPlugin), "RebindKeys" => Some(Self::RebindKeys), + "ListClients" => Some(Self::ListClients), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 6220aa46..e35c9238 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -921,6 +921,7 @@ pub enum Event { EditPaneExited(u32, Option, Context), // u32 - terminal_pane_id, Option - exit code CommandPaneReRun(u32, Context), // u32 - terminal_pane_id, Option - FailedToWriteConfigToDisk(Option), // String -> the file path we failed to write + ListClients(Vec), } #[derive( @@ -1365,6 +1366,29 @@ pub struct PaneInfo { /// (eg. the default `status-bar` or `tab-bar`). pub is_selectable: bool, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct ClientInfo { + pub client_id: ClientId, + pub pane_id: PaneId, + pub running_command: String, + pub is_current_client: bool, +} + +impl ClientInfo { + pub fn new( + client_id: ClientId, + pane_id: PaneId, + running_command: String, + is_current_client: bool, + ) -> Self { + ClientInfo { + client_id, + pane_id, + running_command, + is_current_client, + } + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct PluginIds { @@ -1878,4 +1902,5 @@ pub enum PluginCommand { keys_to_unbind: Vec<(InputMode, KeyWithModifier)>, write_config_to_disk: bool, }, + ListClients, } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index c5f566e1..43deff3b 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -372,6 +372,7 @@ pub enum ScreenContext { CloseTabWithIndex, BreakPanesToNewTab, BreakPanesToTabWithIndex, + ListClientsToPlugin, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. @@ -395,6 +396,7 @@ pub enum PtyContext { DumpLayoutToPlugin, ListClientsMetadata, Reconfigure, + ListClientsToPlugin, Exit, } @@ -432,6 +434,7 @@ pub enum PluginContext { ListClientsMetadata, Reconfigure, FailedToWriteConfigToDisk, + ListClientsToPlugin, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index e3f3cacb..607ba448 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -49,6 +49,7 @@ enum EventType { EditPaneExited = 23; CommandPaneReRun = 24; FailedToWriteConfigToDisk = 25; + ListClients = 26; } message EventNameList { @@ -79,9 +80,21 @@ message Event { EditPaneExitedPayload edit_pane_exited_payload = 20; CommandPaneReRunPayload command_pane_rerun_payload = 21; FailedToWriteConfigToDiskPayload failed_to_write_config_to_disk_payload = 22; + ListClientsPayload list_clients_payload = 23; } } +message ListClientsPayload { + repeated ClientInfo client_info = 1; +} + +message ClientInfo { + uint32 client_id = 1; + PaneId pane_id = 2; + string running_command = 3; + bool is_current_client = 4; +} + message FailedToWriteConfigToDiskPayload { optional string file_path = 1; } diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index d1e2f50d..d78f5567 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -1,14 +1,15 @@ pub use super::generated_api::api::{ action::{Action as ProtobufAction, Position as ProtobufPosition}, event::{ - event::Payload as ProtobufEventPayload, CopyDestination as ProtobufCopyDestination, - Event as ProtobufEvent, EventNameList as ProtobufEventNameList, - EventType as ProtobufEventType, FileMetadata as ProtobufFileMetadata, - InputModeKeybinds as ProtobufInputModeKeybinds, KeyBind as ProtobufKeyBind, - LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload, - PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo, - PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType, - PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession, + event::Payload as ProtobufEventPayload, ClientInfo as ProtobufClientInfo, + CopyDestination as ProtobufCopyDestination, Event as ProtobufEvent, + EventNameList as ProtobufEventNameList, EventType as ProtobufEventType, + FileMetadata as ProtobufFileMetadata, InputModeKeybinds as ProtobufInputModeKeybinds, + KeyBind as ProtobufKeyBind, LayoutInfo as ProtobufLayoutInfo, + ModeUpdatePayload as ProtobufModeUpdatePayload, PaneId as ProtobufPaneId, + PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest, + PaneType as ProtobufPaneType, PluginInfo as ProtobufPluginInfo, + ResurrectableSession as ProtobufResurrectableSession, SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *, }, input_mode::InputMode as ProtobufInputMode, @@ -17,9 +18,9 @@ pub use super::generated_api::api::{ }; #[allow(hidden_glob_reexports)] use crate::data::{ - CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo, - ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities, - PluginInfo, SessionInfo, Style, TabInfo, + ClientInfo, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, + LayoutInfo, ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, + PluginCapabilities, PluginInfo, SessionInfo, Style, TabInfo, }; use crate::errors::prelude::*; @@ -320,11 +321,50 @@ impl TryFrom for Event { )), _ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"), }, + Some(ProtobufEventType::ListClients) => match protobuf_event.payload { + Some(ProtobufEventPayload::ListClientsPayload(mut list_clients_payload)) => { + Ok(Event::ListClients( + list_clients_payload + .client_info + .drain(..) + .filter_map(|c| c.try_into().ok()) + .collect(), + )) + }, + _ => Err("Malformed payload for the FailedToWriteConfigToDisk Event"), + }, None => Err("Unknown Protobuf Event"), } } } +impl TryFrom for ClientInfo { + type Error = &'static str; + fn try_from(protobuf_client_info: ProtobufClientInfo) -> Result { + Ok(ClientInfo::new( + protobuf_client_info.client_id as u16, + protobuf_client_info + .pane_id + .ok_or("No pane id found")? + .try_into()?, + protobuf_client_info.running_command, + protobuf_client_info.is_current_client, + )) + } +} + +impl TryFrom for ProtobufClientInfo { + type Error = &'static str; + fn try_from(client_info: ClientInfo) -> Result { + Ok(ProtobufClientInfo { + client_id: client_info.client_id as u32, + pane_id: Some(client_info.pane_id.try_into()?), + running_command: client_info.running_command, + is_current_client: client_info.is_current_client, + }) + } +} + impl TryFrom for ProtobufEvent { type Error = &'static str; fn try_from(event: Event) -> Result { @@ -634,6 +674,15 @@ impl TryFrom for ProtobufEvent { FailedToWriteConfigToDiskPayload { file_path }, )), }), + Event::ListClients(mut client_info_list) => Ok(ProtobufEvent { + name: ProtobufEventType::ListClients as i32, + payload: Some(event::Payload::ListClientsPayload(ListClientsPayload { + client_info: client_info_list + .drain(..) + .filter_map(|c| c.try_into().ok()) + .collect(), + })), + }), } } } @@ -1178,6 +1227,7 @@ impl TryFrom for EventType { ProtobufEventType::EditPaneExited => EventType::EditPaneExited, ProtobufEventType::CommandPaneReRun => EventType::CommandPaneReRun, ProtobufEventType::FailedToWriteConfigToDisk => EventType::FailedToWriteConfigToDisk, + ProtobufEventType::ListClients => EventType::ListClients, }) } } @@ -1212,6 +1262,7 @@ impl TryFrom for ProtobufEventType { EventType::EditPaneExited => ProtobufEventType::EditPaneExited, EventType::CommandPaneReRun => ProtobufEventType::CommandPaneReRun, EventType::FailedToWriteConfigToDisk => ProtobufEventType::FailedToWriteConfigToDisk, + EventType::ListClients => ProtobufEventType::ListClients, }) } } diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 29ebca67..5b58de14 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -126,6 +126,7 @@ enum CommandName { ReloadPlugin = 110; LoadNewPlugin = 111; RebindKeys = 112; + ListClients = 113; } message PluginCommand { diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 79b08794..06687971 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -1299,6 +1299,10 @@ impl TryFrom for PluginCommand { }, _ => Err("Mismatched payload for RebindKeys"), }, + Some(CommandName::ListClients) => match protobuf_plugin_command.payload { + Some(_) => Err("ListClients should have no payload, found a payload"), + None => Ok(PluginCommand::ListClients), + }, None => Err("Unrecognized plugin command"), } } @@ -2122,6 +2126,10 @@ impl TryFrom for ProtobufPluginCommand { write_config_to_disk, })), }), + PluginCommand::ListClients => Ok(ProtobufPluginCommand { + name: CommandName::ListClients as i32, + payload: None, + }), } } }