From 2eaa50cc44284bdfd47a98770625f380c15fd51d Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 26 Mar 2024 18:44:56 +0100 Subject: feat(plugins): add api to dump the current session layout to a plugin (#3227) --- zellij-server/src/plugins/mod.rs | 29 ++++++++++++++++++++++++ zellij-server/src/plugins/zellij_exports.rs | 10 ++++++++ zellij-server/src/pty.rs | 16 ++++++++++++- zellij-server/src/screen.rs | 18 ++++++++++++++- zellij-tile/src/shim.rs | 8 +++++++ zellij-utils/assets/prost/api.plugin_command.rs | 3 +++ zellij-utils/src/data.rs | 1 + zellij-utils/src/errors.rs | 3 +++ zellij-utils/src/plugin_api/plugin_command.proto | 1 + zellij-utils/src/plugin_api/plugin_command.rs | 8 +++++++ 10 files changed, 95 insertions(+), 2 deletions(-) diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 44e88ae0f..e55168939 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -37,6 +37,7 @@ use zellij_utils::{ }, ipc::ClientAttributes, pane_size::Size, + session_serialization, }; pub type PluginId = u32; @@ -104,6 +105,7 @@ pub enum PluginInstruction { Option, ), DumpLayout(SessionLayoutMetadata, ClientId), + DumpLayoutToPlugin(SessionLayoutMetadata, PluginId), LogLayoutToHd(SessionLayoutMetadata), CliPipe { pipe_id: String, @@ -178,6 +180,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::UnblockCliPipes { .. } => PluginContext::UnblockCliPipes, PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem, PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe, + PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin, } } } @@ -484,6 +487,32 @@ pub(crate) fn plugin_thread_main( client_id, ))); }, + PluginInstruction::DumpLayoutToPlugin(mut session_layout_metadata, plugin_id) => { + populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge); + match session_serialization::serialize_session_layout( + session_layout_metadata.into(), + ) { + Ok((layout, _pane_contents)) => { + let updates = vec![( + Some(plugin_id), + None, + Event::CustomMessage("session_layout".to_owned(), layout), + )]; + wasm_bridge.update_plugins(updates, shutdown_send.clone())?; + }, + Err(e) => { + let updates = vec![( + Some(plugin_id), + None, + Event::CustomMessage( + "session_layout_error".to_owned(), + format!("{}", e), + ), + )]; + wasm_bridge.update_plugins(updates, shutdown_send.clone())?; + }, + } + }, PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => { populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge); drop( diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 65c18e16b..1f65fad54 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -263,6 +263,7 @@ fn host_run_plugin_command(env: FunctionEnvMut) { scan_host_folder(env, folder_to_scan) }, PluginCommand::WatchFilesystem => watch_filesystem(env), + PluginCommand::DumpSessionLayout => dump_session_layout(env), }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1340,6 +1341,14 @@ fn watch_filesystem(env: &ForeignFunctionEnv) { .map(|sender| sender.send(PluginInstruction::WatchFilesystem)); } +fn dump_session_layout(env: &ForeignFunctionEnv) { + let _ = env.plugin_env.senders.to_screen.as_ref().map(|sender| { + sender.send(ScreenInstruction::DumpLayoutToPlugin( + env.plugin_env.plugin_id, + )) + }); +} + fn scan_host_folder(env: &ForeignFunctionEnv, folder_to_scan: PathBuf) { if !folder_to_scan.starts_with("/host") { log::error!( @@ -1542,6 +1551,7 @@ fn check_command_permission( | PluginCommand::BlockCliPipeInput(..) | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes, PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins, + PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index cc219f372..c4e810536 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -2,7 +2,7 @@ use crate::background_jobs::BackgroundJob; use crate::terminal_bytes::TerminalBytes; use crate::{ panes::PaneId, - plugins::PluginInstruction, + plugins::{PluginId, PluginInstruction}, screen::ScreenInstruction, session_layout_metadata::SessionLayoutMetadata, thread_bus::{Bus, ThreadSenders}, @@ -77,6 +77,7 @@ pub enum PtyInstruction { ClientTabIndexOrPaneId, ), // String is an optional pane name DumpLayout(SessionLayoutMetadata, ClientId), + DumpLayoutToPlugin(SessionLayoutMetadata, PluginId), LogLayoutToHd(SessionLayoutMetadata), FillPluginCwd( Option, // should float @@ -110,6 +111,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::DropToShellInPane { .. } => PtyContext::DropToShellInPane, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout, + PtyInstruction::DumpLayoutToPlugin(..) => PtyContext::DumpLayoutToPlugin, PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd, PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd, PtyInstruction::Exit => PtyContext::Exit, @@ -630,6 +632,18 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box) -> Result<()> { }, } }, + PtyInstruction::DumpLayoutToPlugin(mut session_layout_metadata, plugin_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::DumpLayoutToPlugin( + session_layout_metadata, + plugin_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 35ec8fee5..5384987de 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -36,7 +36,7 @@ use crate::{ output::Output, panes::sixel::SixelImageStore, panes::PaneId, - plugins::{PluginInstruction, PluginRenderAsset}, + plugins::{PluginId, PluginInstruction, PluginRenderAsset}, pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, tab::Tab, thread_bus::Bus, @@ -180,6 +180,7 @@ pub enum ScreenInstruction { DumpScreen(String, ClientId, bool), DumpLayout(Option, ClientId), // PathBuf is the default configured // shell + DumpLayoutToPlugin(PluginId), EditScrollback(ClientId), ScrollUp(ClientId), ScrollUpAt(Position, ClientId), @@ -421,6 +422,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen, ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen, ScreenInstruction::DumpLayout(..) => ScreenContext::DumpLayout, + ScreenInstruction::DumpLayoutToPlugin(..) => ScreenContext::DumpLayoutToPlugin, ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback, ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp, ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown, @@ -2630,6 +2632,20 @@ pub(crate) fn screen_thread_main( )) .with_context(err_context)?; }, + ScreenInstruction::DumpLayoutToPlugin(plugin_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::DumpLayoutToPlugin( + session_layout_metadata, + plugin_id, + )) + .with_context(err_context) + .non_fatal(); + }, ScreenInstruction::EditScrollback(client_id) => { active_tab_and_connected_client_id!( screen, diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 2d04a7b6a..532a5fafe 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -784,6 +784,14 @@ pub fn watch_filesystem() { unsafe { host_run_plugin_command() }; } +/// Get the serialized session layout in KDL format as a CustomMessage Event +pub fn dump_session_layout() { + let plugin_command = PluginCommand::DumpSessionLayout; + 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 5ec93e2e1..ca067fd97 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -420,6 +420,7 @@ pub enum CommandName { KillSessions = 81, ScanHostFolder = 82, WatchFilesystem = 83, + DumpSessionLayout = 84, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -512,6 +513,7 @@ impl CommandName { CommandName::KillSessions => "KillSessions", CommandName::ScanHostFolder => "ScanHostFolder", CommandName::WatchFilesystem => "WatchFilesystem", + CommandName::DumpSessionLayout => "DumpSessionLayout", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -601,6 +603,7 @@ impl CommandName { "KillSessions" => Some(Self::KillSessions), "ScanHostFolder" => Some(Self::ScanHostFolder), "WatchFilesystem" => Some(Self::WatchFilesystem), + "DumpSessionLayout" => Some(Self::DumpSessionLayout), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 379fb126f..78fbbd770 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1378,4 +1378,5 @@ pub enum PluginCommand { KillSessions(Vec), // one or more session names ScanHostFolder(PathBuf), // TODO: rename to ScanHostFolder WatchFilesystem, + DumpSessionLayout, } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index d05df3a25..51174b756 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -351,6 +351,7 @@ pub enum ScreenContext { NewInPlacePluginPane, DumpLayoutToHd, RenameSession, + DumpLayoutToPlugin, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. @@ -371,6 +372,7 @@ pub enum PtyContext { DumpLayout, LogLayoutToHd, FillPluginCwd, + DumpLayoutToPlugin, Exit, } @@ -402,6 +404,7 @@ pub enum PluginContext { UnblockCliPipes, WatchFilesystem, KeybindPipe, + DumpLayoutToPlugin, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index c95111e59..b05c8ac8a 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -95,6 +95,7 @@ enum CommandName { KillSessions = 81; ScanHostFolder = 82; WatchFilesystem = 83; + DumpSessionLayout = 84; } message PluginCommand { diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index e452e53cc..232a9f2dc 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -867,6 +867,10 @@ impl TryFrom for PluginCommand { Some(_) => Err("WatchFilesystem should have no payload, found a payload"), None => Ok(PluginCommand::WatchFilesystem), }, + Some(CommandName::DumpSessionLayout) => match protobuf_plugin_command.payload { + Some(_) => Err("DumpSessionLayout should have no payload, found a payload"), + None => Ok(PluginCommand::DumpSessionLayout), + }, None => Err("Unrecognized plugin command"), } } @@ -1381,6 +1385,10 @@ impl TryFrom for ProtobufPluginCommand { name: CommandName::WatchFilesystem as i32, payload: None, }), + PluginCommand::DumpSessionLayout => Ok(ProtobufPluginCommand { + name: CommandName::DumpSessionLayout as i32, + payload: None, + }), } } } -- cgit v1.2.3