diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 55c98e58dc..eb3890b26a 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -978,12 +978,14 @@ fn init_session( let client_attributes = client_attributes.clone(); let default_shell = default_shell.clone(); let capabilities = capabilities.clone(); + let layout_dir = config_options.layout_dir.clone(); move || { plugin_thread_main( plugin_bus, store, data_dir, layout, + layout_dir, path_to_default_shell, zellij_cwd, capabilities, diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index e55168939b..f6862ffd55 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -190,6 +190,7 @@ pub(crate) fn plugin_thread_main( store: Store, data_dir: PathBuf, mut layout: Box, + layout_dir: Option, path_to_default_shell: PathBuf, zellij_cwd: PathBuf, capabilities: PluginCapabilities, @@ -217,6 +218,7 @@ pub(crate) fn plugin_thread_main( client_attributes, default_shell, layout.clone(), + layout_dir, ); loop { diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index b74c80b18c..fafeeb22cc 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -66,6 +66,7 @@ pub struct PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, } impl<'a> PluginLoader<'a> { @@ -84,6 +85,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result<()> { let err_context = || format!("failed to reload plugin {plugin_id} from memory"); let mut connected_clients: Vec = @@ -108,6 +110,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, )?; plugin_loader .load_module_from_memory() @@ -149,6 +152,7 @@ impl<'a> PluginLoader<'a> { default_shell: Option, default_layout: Box, skip_cache: bool, + layout_dir: Option, ) -> Result<()> { let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}"); let mut plugin_loader = PluginLoader::new( @@ -168,6 +172,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, )?; if skip_cache { plugin_loader @@ -231,6 +236,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result<()> { let mut new_plugins = HashSet::new(); for plugin_id in plugin_map.lock().unwrap().plugin_ids() { @@ -252,6 +258,7 @@ impl<'a> PluginLoader<'a> { client_attributes.clone(), default_shell.clone(), default_layout.clone(), + layout_dir.clone(), )?; plugin_loader .load_module_from_memory() @@ -285,6 +292,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result<()> { let err_context = || format!("failed to reload plugin id {plugin_id}"); @@ -310,6 +318,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, )?; plugin_loader .compile_module() @@ -347,6 +356,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result { let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR .join(Url::from(&plugin.location).to_string()) @@ -373,6 +383,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, }) } pub fn new_from_existing_plugin_attributes( @@ -390,6 +401,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result { let err_context = || "Failed to find existing plugin"; let (running_plugin, _subscriptions, _workers) = { @@ -423,6 +435,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, ) } pub fn new_from_different_client_id( @@ -440,6 +453,7 @@ impl<'a> PluginLoader<'a> { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Result { let err_context = || "Failed to find existing plugin"; let running_plugin = { @@ -474,6 +488,7 @@ impl<'a> PluginLoader<'a> { client_attributes, default_shell, default_layout, + layout_dir, ) } pub fn load_module_from_memory(&mut self) -> Result { @@ -737,6 +752,7 @@ impl<'a> PluginLoader<'a> { self.client_attributes.clone(), self.default_shell.clone(), self.default_layout.clone(), + self.layout_dir.clone(), )?; plugin_loader_for_client .load_module_from_memory() @@ -845,6 +861,7 @@ impl<'a> PluginLoader<'a> { plugin_cwd: self.zellij_cwd.clone(), input_pipes_to_unblock: Arc::new(Mutex::new(HashSet::new())), input_pipes_to_block: Arc::new(Mutex::new(HashSet::new())), + layout_dir: self.layout_dir.clone(), }; let subscriptions = Arc::new(Mutex::new(HashSet::new())); diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index 1d30596ec8..1d400288a2 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -278,6 +278,7 @@ pub struct PluginEnv { pub client_attributes: ClientAttributes, pub default_shell: Option, pub default_layout: Box, + pub layout_dir: Option, pub plugin_cwd: PathBuf, pub input_pipes_to_unblock: Arc>>, pub input_pipes_to_block: Arc>>, diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 65a3799ea6..1f502fff70 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -271,6 +271,7 @@ fn create_plugin_thread( store, data_dir, Box::new(Layout::default()), + None, default_shell, zellij_cwd, plugin_capabilities, @@ -349,6 +350,7 @@ fn create_plugin_thread_with_server_receiver( store, data_dir, Box::new(Layout::default()), + None, default_shell, zellij_cwd, plugin_capabilities, @@ -433,6 +435,7 @@ fn create_plugin_thread_with_pty_receiver( store, data_dir, Box::new(Layout::default()), + None, default_shell, zellij_cwd, plugin_capabilities, @@ -512,6 +515,7 @@ fn create_plugin_thread_with_background_jobs_receiver( store, data_dir, Box::new(Layout::default()), + None, default_shell, zellij_cwd, plugin_capabilities, diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 7f39c9b1e8..5320fb0321 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -101,6 +101,7 @@ pub struct WasmBridge { cached_plugin_map: HashMap>>, pending_pipes: PendingPipes, + layout_dir: Option, } impl WasmBridge { @@ -114,6 +115,7 @@ impl WasmBridge { client_attributes: ClientAttributes, default_shell: Option, default_layout: Box, + layout_dir: Option, ) -> Self { let plugin_map = Arc::new(Mutex::new(PluginMap::default())); let connected_clients: Arc>> = Arc::new(Mutex::new(vec![])); @@ -143,6 +145,7 @@ impl WasmBridge { default_layout, cached_plugin_map: HashMap::new(), pending_pipes: Default::default(), + layout_dir, } } pub fn load_plugin( @@ -198,6 +201,7 @@ impl WasmBridge { let client_attributes = self.client_attributes.clone(); let default_shell = self.default_shell.clone(); let default_layout = self.default_layout.clone(); + let layout_dir = self.layout_dir.clone(); async move { let _ = senders.send_to_background_jobs( BackgroundJob::AnimatePluginLoading(plugin_id), @@ -244,6 +248,7 @@ impl WasmBridge { default_shell, default_layout, skip_cache, + layout_dir, ) { Ok(_) => handle_plugin_successful_loading(&senders, plugin_id), Err(e) => handle_plugin_loading_failure( @@ -334,6 +339,7 @@ impl WasmBridge { let client_attributes = self.client_attributes.clone(); let default_shell = self.default_shell.clone(); let default_layout = self.default_layout.clone(); + let layout_dir = self.layout_dir.clone(); async move { match PluginLoader::reload_plugin( first_plugin_id, @@ -350,6 +356,7 @@ impl WasmBridge { client_attributes.clone(), default_shell.clone(), default_layout.clone(), + layout_dir.clone(), ) { Ok(_) => { handle_plugin_successful_loading(&senders, first_plugin_id); @@ -374,6 +381,7 @@ impl WasmBridge { client_attributes.clone(), default_shell.clone(), default_layout.clone(), + layout_dir.clone(), ) { Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id), Err(e) => handle_plugin_loading_failure( @@ -425,6 +433,7 @@ impl WasmBridge { self.client_attributes.clone(), self.default_shell.clone(), self.default_layout.clone(), + self.layout_dir.clone(), ) { Ok(_) => { let _ = self diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index b3dcbc233e..1e169f840b 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -154,6 +154,9 @@ fn host_run_plugin_command(env: FunctionEnvMut) { PluginCommand::NewTabsWithLayout(raw_layout) => { new_tabs_with_layout(env, &raw_layout)? }, + PluginCommand::NewTabsWithLayoutInfo(layout_info) => { + new_tabs_with_layout_info(env, layout_info)? + }, PluginCommand::NewTab => new_tab(env), PluginCommand::GoToNextTab => go_to_next_tab(env), PluginCommand::GoToPreviousTab => go_to_previous_tab(env), @@ -859,6 +862,19 @@ fn new_tabs_with_layout(env: &ForeignFunctionEnv, raw_layout: &str) -> Result<() None, ) .map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?; + apply_layout(env, layout); + Ok(()) +} + +fn new_tabs_with_layout_info(env: &ForeignFunctionEnv, layout_info: LayoutInfo) -> Result<()> { + // TODO: cwd + let layout = Layout::from_layout_info(&env.plugin_env.layout_dir, layout_info) + .map_err(|e| anyhow!("Failed to parse layout: {:?}", e))?; + apply_layout(env, layout); + Ok(()) +} + +fn apply_layout(env: &ForeignFunctionEnv, layout: Layout) { let mut tabs_to_open = vec![]; let tabs = layout.tabs(); if tabs.is_empty() { @@ -890,7 +906,6 @@ fn new_tabs_with_layout(env: &ForeignFunctionEnv, raw_layout: &str) -> Result<() let error_msg = || format!("Failed to create layout tab"); apply_action!(action, error_msg, env); } - Ok(()) } fn new_tab(env: &ForeignFunctionEnv) { @@ -1526,6 +1541,7 @@ fn check_command_permission( PluginCommand::SwitchTabTo(..) | PluginCommand::SwitchToMode(..) | PluginCommand::NewTabsWithLayout(..) + | PluginCommand::NewTabsWithLayoutInfo(..) | PluginCommand::NewTab | PluginCommand::GoToNextTab | PluginCommand::GoToPreviousTab diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 2f00a8a2b1..ead2133202 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -273,6 +273,14 @@ pub fn new_tabs_with_layout(layout: &str) { unsafe { host_run_plugin_command() }; } +/// Provide a LayoutInfo to be applied to the current session in a new tab. If the layout has multiple tabs, they will all be opened. +pub fn new_tabs_with_layout_info(layout_info: LayoutInfo) { + let plugin_command = PluginCommand::NewTabsWithLayoutInfo(layout_info); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + /// Open a new tab with the default layout pub fn new_tab() { let plugin_command = PluginCommand::NewTab; diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 3bf49d36d0..98883353bb 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -5,7 +5,7 @@ pub struct PluginCommand { pub name: i32, #[prost( oneof = "plugin_command::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62" )] pub payload: ::core::option::Option, } @@ -116,10 +116,18 @@ pub mod plugin_command { KillSessionsPayload(super::KillSessionsPayload), #[prost(string, tag = "61")] ScanHostFolderPayload(::prost::alloc::string::String), + #[prost(message, tag = "62")] + NewTabsWithLayoutInfoPayload(super::NewTabsWithLayoutInfoPayload), } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewTabsWithLayoutInfoPayload { + #[prost(message, optional, tag = "1")] + pub layout_info: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct KillSessionsPayload { #[prost(string, repeated, tag = "1")] pub session_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, @@ -422,6 +430,7 @@ pub enum CommandName { WatchFilesystem = 83, DumpSessionLayout = 84, CloseSelf = 85, + NewTabsWithLayoutInfo = 86, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -516,6 +525,7 @@ impl CommandName { CommandName::WatchFilesystem => "WatchFilesystem", CommandName::DumpSessionLayout => "DumpSessionLayout", CommandName::CloseSelf => "CloseSelf", + CommandName::NewTabsWithLayoutInfo => "NewTabsWithLayoutInfo", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -607,6 +617,7 @@ impl CommandName { "WatchFilesystem" => Some(Self::WatchFilesystem), "DumpSessionLayout" => Some(Self::DumpSessionLayout), "CloseSelf" => Some(Self::CloseSelf), + "NewTabsWithLayoutInfo" => Some(Self::NewTabsWithLayoutInfo), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index b0e0a8861c..9914a79784 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1380,4 +1380,5 @@ pub enum PluginCommand { WatchFilesystem, DumpSessionLayout, CloseSelf, + NewTabsWithLayoutInfo(LayoutInfo), } diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs index f51ddd92c1..055410262f 100644 --- a/zellij-utils/src/input/layout.rs +++ b/zellij-utils/src/input/layout.rs @@ -1075,7 +1075,6 @@ impl Default for LayoutParts { } impl Layout { - // the first layout will either be the default one pub fn list_available_layouts( layout_dir: Option, default_layout_name: &Option, @@ -1131,6 +1130,31 @@ impl Layout { }); available_layouts } + pub fn from_layout_info( + layout_dir: &Option, + layout_info: LayoutInfo, + ) -> Result { + let (path_to_raw_layout, raw_layout, raw_swap_layouts) = match layout_info { + LayoutInfo::File(layout_name_without_extension) => { + let layout_dir = layout_dir.clone().or_else(|| default_layout_dir()); + Self::stringified_from_dir( + &PathBuf::from(layout_name_without_extension), + layout_dir.as_ref(), + )? + }, + LayoutInfo::BuiltIn(layout_name) => { + Self::stringified_from_default_assets(&PathBuf::from(layout_name))? + }, + }; + Layout::from_kdl( + &raw_layout, + path_to_raw_layout, + raw_swap_layouts + .as_ref() + .map(|(r, f)| (r.as_str(), f.as_str())), + None, + ) + } pub fn stringified_from_path_or_default( layout_path: Option<&PathBuf>, layout_dir: Option, diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index b961593e7b..718e8b4ca8 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -97,6 +97,7 @@ enum CommandName { WatchFilesystem = 83; DumpSessionLayout = 84; CloseSelf = 85; + NewTabsWithLayoutInfo = 86; } message PluginCommand { @@ -153,9 +154,14 @@ message PluginCommand { MessageToPluginPayload message_to_plugin_payload = 50; KillSessionsPayload kill_sessions_payload = 60; string scan_host_folder_payload = 61; + NewTabsWithLayoutInfoPayload new_tabs_with_layout_info_payload = 62; } } +message NewTabsWithLayoutInfoPayload { + event.LayoutInfo layout_info = 1; +} + message KillSessionsPayload { repeated string session_names = 1; } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 080dd9e28b..771b615172 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -8,9 +8,9 @@ pub use super::generated_api::api::{ FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, MessageToPluginPayload, MovePayload, - NewPluginArgs as ProtobufNewPluginArgs, OpenCommandPanePayload, OpenFilePayload, - PaneId as ProtobufPaneId, PaneType as ProtobufPaneType, - PluginCommand as ProtobufPluginCommand, PluginMessagePayload, + NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload, + OpenCommandPanePayload, OpenFilePayload, PaneId as ProtobufPaneId, + PaneType as ProtobufPaneType, PluginCommand as ProtobufPluginCommand, PluginMessagePayload, RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload, WebRequestPayload, @@ -875,6 +875,19 @@ impl TryFrom for PluginCommand { Some(_) => Err("CloseSelf should have no payload, found a payload"), None => Ok(PluginCommand::CloseSelf), }, + Some(CommandName::NewTabsWithLayoutInfo) => match protobuf_plugin_command.payload { + Some(Payload::NewTabsWithLayoutInfoPayload(new_tabs_with_layout_info_payload)) => { + new_tabs_with_layout_info_payload + .layout_info + .and_then(|layout_info| { + Some(PluginCommand::NewTabsWithLayoutInfo( + layout_info.try_into().ok()?, + )) + }) + .ok_or("Failed to parse NewTabsWithLayoutInfo command") + }, + _ => Err("Mismatched payload for NewTabsWithLayoutInfo"), + }, None => Err("Unrecognized plugin command"), } } @@ -1397,6 +1410,16 @@ impl TryFrom for ProtobufPluginCommand { name: CommandName::CloseSelf as i32, payload: None, }), + PluginCommand::NewTabsWithLayoutInfo(new_tabs_with_layout_info_payload) => { + Ok(ProtobufPluginCommand { + name: CommandName::NewTabsWithLayoutInfo as i32, + payload: Some(Payload::NewTabsWithLayoutInfoPayload( + NewTabsWithLayoutInfoPayload { + layout_info: new_tabs_with_layout_info_payload.try_into().ok(), + }, + )), + }) + }, } } }