feat(plugins): add api to dump the current session layout to a plugin (#3227)

This commit is contained in:
Aram Drevekenin 2024-03-26 18:44:56 +01:00 committed by GitHub
parent b24dd87b80
commit 2eaa50cc44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 95 additions and 2 deletions

View file

@ -37,6 +37,7 @@ use zellij_utils::{
}, },
ipc::ClientAttributes, ipc::ClientAttributes,
pane_size::Size, pane_size::Size,
session_serialization,
}; };
pub type PluginId = u32; pub type PluginId = u32;
@ -104,6 +105,7 @@ pub enum PluginInstruction {
Option<PathBuf>, Option<PathBuf>,
), ),
DumpLayout(SessionLayoutMetadata, ClientId), DumpLayout(SessionLayoutMetadata, ClientId),
DumpLayoutToPlugin(SessionLayoutMetadata, PluginId),
LogLayoutToHd(SessionLayoutMetadata), LogLayoutToHd(SessionLayoutMetadata),
CliPipe { CliPipe {
pipe_id: String, pipe_id: String,
@ -178,6 +180,7 @@ impl From<&PluginInstruction> for PluginContext {
PluginInstruction::UnblockCliPipes { .. } => PluginContext::UnblockCliPipes, PluginInstruction::UnblockCliPipes { .. } => PluginContext::UnblockCliPipes,
PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem, PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem,
PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe, PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe,
PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin,
} }
} }
} }
@ -484,6 +487,32 @@ pub(crate) fn plugin_thread_main(
client_id, 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) => { PluginInstruction::LogLayoutToHd(mut session_layout_metadata) => {
populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge); populate_session_layout_metadata(&mut session_layout_metadata, &wasm_bridge);
drop( drop(

View file

@ -263,6 +263,7 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
scan_host_folder(env, folder_to_scan) scan_host_folder(env, folder_to_scan)
}, },
PluginCommand::WatchFilesystem => watch_filesystem(env), PluginCommand::WatchFilesystem => watch_filesystem(env),
PluginCommand::DumpSessionLayout => dump_session_layout(env),
}, },
(PermissionStatus::Denied, permission) => { (PermissionStatus::Denied, permission) => {
log::error!( log::error!(
@ -1340,6 +1341,14 @@ fn watch_filesystem(env: &ForeignFunctionEnv) {
.map(|sender| sender.send(PluginInstruction::WatchFilesystem)); .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) { fn scan_host_folder(env: &ForeignFunctionEnv, folder_to_scan: PathBuf) {
if !folder_to_scan.starts_with("/host") { if !folder_to_scan.starts_with("/host") {
log::error!( log::error!(
@ -1542,6 +1551,7 @@ fn check_command_permission(
| PluginCommand::BlockCliPipeInput(..) | PluginCommand::BlockCliPipeInput(..)
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes, | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins, PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
_ => return (PermissionStatus::Granted, None), _ => return (PermissionStatus::Granted, None),
}; };

View file

@ -2,7 +2,7 @@ use crate::background_jobs::BackgroundJob;
use crate::terminal_bytes::TerminalBytes; use crate::terminal_bytes::TerminalBytes;
use crate::{ use crate::{
panes::PaneId, panes::PaneId,
plugins::PluginInstruction, plugins::{PluginId, PluginInstruction},
screen::ScreenInstruction, screen::ScreenInstruction,
session_layout_metadata::SessionLayoutMetadata, session_layout_metadata::SessionLayoutMetadata,
thread_bus::{Bus, ThreadSenders}, thread_bus::{Bus, ThreadSenders},
@ -77,6 +77,7 @@ pub enum PtyInstruction {
ClientTabIndexOrPaneId, ClientTabIndexOrPaneId,
), // String is an optional pane name ), // String is an optional pane name
DumpLayout(SessionLayoutMetadata, ClientId), DumpLayout(SessionLayoutMetadata, ClientId),
DumpLayoutToPlugin(SessionLayoutMetadata, PluginId),
LogLayoutToHd(SessionLayoutMetadata), LogLayoutToHd(SessionLayoutMetadata),
FillPluginCwd( FillPluginCwd(
Option<bool>, // should float Option<bool>, // should float
@ -110,6 +111,7 @@ impl From<&PtyInstruction> for PtyContext {
PtyInstruction::DropToShellInPane { .. } => PtyContext::DropToShellInPane, PtyInstruction::DropToShellInPane { .. } => PtyContext::DropToShellInPane,
PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal,
PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout, PtyInstruction::DumpLayout(..) => PtyContext::DumpLayout,
PtyInstruction::DumpLayoutToPlugin(..) => PtyContext::DumpLayoutToPlugin,
PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd, PtyInstruction::LogLayoutToHd(..) => PtyContext::LogLayoutToHd,
PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd, PtyInstruction::FillPluginCwd(..) => PtyContext::FillPluginCwd,
PtyInstruction::Exit => PtyContext::Exit, PtyInstruction::Exit => PtyContext::Exit,
@ -630,6 +632,18 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> 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) => { PtyInstruction::LogLayoutToHd(mut session_layout_metadata) => {
let err_context = || format!("Failed to dump layout"); let err_context = || format!("Failed to dump layout");
pty.populate_session_layout_metadata(&mut session_layout_metadata); pty.populate_session_layout_metadata(&mut session_layout_metadata);

View file

@ -36,7 +36,7 @@ use crate::{
output::Output, output::Output,
panes::sixel::SixelImageStore, panes::sixel::SixelImageStore,
panes::PaneId, panes::PaneId,
plugins::{PluginInstruction, PluginRenderAsset}, plugins::{PluginId, PluginInstruction, PluginRenderAsset},
pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes},
tab::Tab, tab::Tab,
thread_bus::Bus, thread_bus::Bus,
@ -180,6 +180,7 @@ pub enum ScreenInstruction {
DumpScreen(String, ClientId, bool), DumpScreen(String, ClientId, bool),
DumpLayout(Option<PathBuf>, ClientId), // PathBuf is the default configured DumpLayout(Option<PathBuf>, ClientId), // PathBuf is the default configured
// shell // shell
DumpLayoutToPlugin(PluginId),
EditScrollback(ClientId), EditScrollback(ClientId),
ScrollUp(ClientId), ScrollUp(ClientId),
ScrollUpAt(Position, ClientId), ScrollUpAt(Position, ClientId),
@ -421,6 +422,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen, ScreenInstruction::ClearScreen(..) => ScreenContext::ClearScreen,
ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen, ScreenInstruction::DumpScreen(..) => ScreenContext::DumpScreen,
ScreenInstruction::DumpLayout(..) => ScreenContext::DumpLayout, ScreenInstruction::DumpLayout(..) => ScreenContext::DumpLayout,
ScreenInstruction::DumpLayoutToPlugin(..) => ScreenContext::DumpLayoutToPlugin,
ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback, ScreenInstruction::EditScrollback(..) => ScreenContext::EditScrollback,
ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp, ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown, ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown,
@ -2630,6 +2632,20 @@ pub(crate) fn screen_thread_main(
)) ))
.with_context(err_context)?; .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) => { ScreenInstruction::EditScrollback(client_id) => {
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,

View file

@ -784,6 +784,14 @@ pub fn watch_filesystem() {
unsafe { host_run_plugin_command() }; 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 // Utility Functions
#[allow(unused)] #[allow(unused)]

View file

@ -420,6 +420,7 @@ pub enum CommandName {
KillSessions = 81, KillSessions = 81,
ScanHostFolder = 82, ScanHostFolder = 82,
WatchFilesystem = 83, WatchFilesystem = 83,
DumpSessionLayout = 84,
} }
impl CommandName { impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -512,6 +513,7 @@ impl CommandName {
CommandName::KillSessions => "KillSessions", CommandName::KillSessions => "KillSessions",
CommandName::ScanHostFolder => "ScanHostFolder", CommandName::ScanHostFolder => "ScanHostFolder",
CommandName::WatchFilesystem => "WatchFilesystem", CommandName::WatchFilesystem => "WatchFilesystem",
CommandName::DumpSessionLayout => "DumpSessionLayout",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -601,6 +603,7 @@ impl CommandName {
"KillSessions" => Some(Self::KillSessions), "KillSessions" => Some(Self::KillSessions),
"ScanHostFolder" => Some(Self::ScanHostFolder), "ScanHostFolder" => Some(Self::ScanHostFolder),
"WatchFilesystem" => Some(Self::WatchFilesystem), "WatchFilesystem" => Some(Self::WatchFilesystem),
"DumpSessionLayout" => Some(Self::DumpSessionLayout),
_ => None, _ => None,
} }
} }

View file

@ -1378,4 +1378,5 @@ pub enum PluginCommand {
KillSessions(Vec<String>), // one or more session names KillSessions(Vec<String>), // one or more session names
ScanHostFolder(PathBuf), // TODO: rename to ScanHostFolder ScanHostFolder(PathBuf), // TODO: rename to ScanHostFolder
WatchFilesystem, WatchFilesystem,
DumpSessionLayout,
} }

View file

@ -351,6 +351,7 @@ pub enum ScreenContext {
NewInPlacePluginPane, NewInPlacePluginPane,
DumpLayoutToHd, DumpLayoutToHd,
RenameSession, RenameSession,
DumpLayoutToPlugin,
} }
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@ -371,6 +372,7 @@ pub enum PtyContext {
DumpLayout, DumpLayout,
LogLayoutToHd, LogLayoutToHd,
FillPluginCwd, FillPluginCwd,
DumpLayoutToPlugin,
Exit, Exit,
} }
@ -402,6 +404,7 @@ pub enum PluginContext {
UnblockCliPipes, UnblockCliPipes,
WatchFilesystem, WatchFilesystem,
KeybindPipe, KeybindPipe,
DumpLayoutToPlugin,
} }
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s. /// Stack call representations corresponding to the different types of [`ClientInstruction`]s.

View file

@ -95,6 +95,7 @@ enum CommandName {
KillSessions = 81; KillSessions = 81;
ScanHostFolder = 82; ScanHostFolder = 82;
WatchFilesystem = 83; WatchFilesystem = 83;
DumpSessionLayout = 84;
} }
message PluginCommand { message PluginCommand {

View file

@ -867,6 +867,10 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(_) => Err("WatchFilesystem should have no payload, found a payload"), Some(_) => Err("WatchFilesystem should have no payload, found a payload"),
None => Ok(PluginCommand::WatchFilesystem), 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"), None => Err("Unrecognized plugin command"),
} }
} }
@ -1381,6 +1385,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::WatchFilesystem as i32, name: CommandName::WatchFilesystem as i32,
payload: None, payload: None,
}), }),
PluginCommand::DumpSessionLayout => Ok(ProtobufPluginCommand {
name: CommandName::DumpSessionLayout as i32,
payload: None,
}),
} }
} }
} }