diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-02-26 15:30:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-26 15:30:15 +0100 |
commit | 21273ac95a2fed6b07d8550fe2d4f65993be7037 (patch) | |
tree | f5a7aa488ece8f21c17350bb3ec14ceaf15f60a7 /zellij-server/src/plugins | |
parent | 27bffbf1533b4b2d3c10b1305557c75ddd121374 (diff) |
feat(plugins): introduce plugin aliases (#3157)
* working prototype with passing tests
* new tests and passing plugin tests as well
* style(code): cleanups
* cleanup strider from unused search feature
* prototype of removing old plugin block from the config
* aliases working from config file and all tests passing
* fixups and cleanups
* use aliases in layouts
* update test snapshot
* style(fmt): rustfmt
Diffstat (limited to 'zellij-server/src/plugins')
6 files changed, 555 insertions, 405 deletions
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index ebae02a39..80a590453 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -32,11 +32,8 @@ use zellij_utils::{ errors::{prelude::*, ContextType, PluginContext}, input::{ command::TerminalAction, - layout::{ - FloatingPaneLayout, Layout, PluginUserConfiguration, Run, RunPlugin, RunPluginLocation, - TiledPaneLayout, - }, - plugins::PluginsConfig, + layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout}, + plugins::PluginAliases, }, ipc::ClientAttributes, pane_size::Size, @@ -50,7 +47,7 @@ pub enum PluginInstruction { Option<bool>, // should float bool, // should be opened in place Option<String>, // pane title - RunPlugin, + RunPluginOrAlias, usize, // tab index Option<PaneId>, // pane id to replace if this is to be opened "in-place" ClientId, @@ -63,7 +60,7 @@ pub enum PluginInstruction { Reload( Option<bool>, // should float Option<String>, // pane title - RunPlugin, + RunPluginOrAlias, usize, // tab index Size, ), @@ -173,19 +170,18 @@ pub(crate) fn plugin_thread_main( bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf, - plugins: PluginsConfig, - layout: Box<Layout>, + mut layout: Box<Layout>, path_to_default_shell: PathBuf, zellij_cwd: PathBuf, capabilities: PluginCapabilities, client_attributes: ClientAttributes, default_shell: Option<TerminalAction>, + plugin_aliases: Box<PluginAliases>, ) -> Result<()> { info!("Wasm main thread starts"); - let plugin_dir = data_dir.join("plugins/"); let plugin_global_data_dir = plugin_dir.join("data"); - + layout.populate_plugin_aliases_in_layout(&plugin_aliases); let store = Arc::new(Mutex::new(store)); // use this channel to ensure that tasks spawned from this thread terminate before exiting @@ -193,7 +189,6 @@ pub(crate) fn plugin_thread_main( let (shutdown_send, shutdown_receive) = channel::bounded::<()>(1); let mut wasm_bridge = WasmBridge::new( - plugins, bus.senders.clone(), store, plugin_dir, @@ -213,38 +208,42 @@ pub(crate) fn plugin_thread_main( should_float, should_be_open_in_place, pane_title, - run, + mut run_plugin_or_alias, tab_index, pane_id_to_replace, client_id, size, cwd, skip_cache, - ) => match wasm_bridge.load_plugin( - &run, - Some(tab_index), - size, - cwd.clone(), - skip_cache, - Some(client_id), - None, - ) { - Ok((plugin_id, client_id)) => { - drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin( - should_float, - should_be_open_in_place, - run, - pane_title, - Some(tab_index), - plugin_id, - pane_id_to_replace, - cwd, - Some(client_id), - ))); - }, - Err(e) => { - log::error!("Failed to load plugin: {e}"); - }, + ) => { + run_plugin_or_alias.populate_run_plugin_if_needed(&plugin_aliases); + let run_plugin = run_plugin_or_alias.get_run_plugin(); + match wasm_bridge.load_plugin( + &run_plugin, + Some(tab_index), + size, + cwd.clone(), + skip_cache, + Some(client_id), + None, + ) { + Ok((plugin_id, client_id)) => { + drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin( + should_float, + should_be_open_in_place, + run_plugin_or_alias, + pane_title, + Some(tab_index), + plugin_id, + pane_id_to_replace, + cwd, + Some(client_id), + ))); + }, + Err(e) => { + log::error!("Failed to load plugin: {e}"); + }, + } }, PluginInstruction::Update(updates) => { wasm_bridge.update_plugins(updates, shutdown_send.clone())?; @@ -252,50 +251,69 @@ pub(crate) fn plugin_thread_main( PluginInstruction::Unload(pid) => { wasm_bridge.unload_plugin(pid)?; }, - PluginInstruction::Reload(should_float, pane_title, run, tab_index, size) => { - match wasm_bridge.reload_plugin(&run) { - Ok(_) => { - let _ = bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread); - }, - Err(err) => match err.downcast_ref::<ZellijError>() { - Some(ZellijError::PluginDoesNotExist) => { - log::warn!("Plugin {} not found, starting it instead", run.location); - // we intentionally do not provide the client_id here because it belongs to - // the cli who spawned the command and is not an existing client_id - let skip_cache = true; // when reloading we always skip cache - match wasm_bridge.load_plugin( - &run, - Some(tab_index), - size, - None, - skip_cache, - None, - None, - ) { - Ok((plugin_id, _client_id)) => { - let should_be_open_in_place = false; - drop(bus.senders.send_to_screen(ScreenInstruction::AddPlugin( - should_float, - should_be_open_in_place, - run, - pane_title, + PluginInstruction::Reload( + should_float, + pane_title, + mut run_plugin_or_alias, + tab_index, + size, + ) => { + run_plugin_or_alias.populate_run_plugin_if_needed(&plugin_aliases); + match run_plugin_or_alias.get_run_plugin() { + Some(run_plugin) => { + match wasm_bridge.reload_plugin(&run_plugin) { + Ok(_) => { + let _ = bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread); + }, + Err(err) => match err.downcast_ref::<ZellijError>() { + Some(ZellijError::PluginDoesNotExist) => { + log::warn!( + "Plugin {} not found, starting it instead", + run_plugin.location + ); + // we intentionally do not provide the client_id here because it belongs to + // the cli who spawned the command and is not an existing client_id + let skip_cache = true; // when reloading we always skip cache + match wasm_bridge.load_plugin( + &Some(run_plugin), Some(tab_index), - plugin_id, + size, None, + skip_cache, None, None, - ))); + ) { + Ok((plugin_id, _client_id)) => { + let should_be_open_in_place = false; + drop(bus.senders.send_to_screen( + ScreenInstruction::AddPlugin( + should_float, + should_be_open_in_place, + run_plugin_or_alias, + pane_title, + Some(tab_index), + plugin_id, + None, + None, + None, + ), + )); + }, + Err(e) => { + log::error!("Failed to load plugin: {e}"); + }, + }; }, - Err(e) => { - log::error!("Failed to load plugin: {e}"); + _ => { + return Err(err); }, - }; - }, - _ => { - return Err(err); - }, + }, + } + }, + None => { + log::error!("Failed to find plugin info for: {:?}", run_plugin_or_alias) }, } }, @@ -311,15 +329,21 @@ pub(crate) fn plugin_thread_main( PluginInstruction::NewTab( cwd, terminal_action, - tab_layout, - floating_panes_layout, + mut tab_layout, + mut floating_panes_layout, tab_index, client_id, ) => { - let mut plugin_ids: HashMap< - (RunPluginLocation, PluginUserConfiguration), - Vec<PluginId>, - > = HashMap::new(); + let mut plugin_ids: HashMap<RunPluginOrAlias, Vec<PluginId>> = HashMap::new(); + tab_layout = tab_layout.or_else(|| Some(layout.new_tab().0)); + tab_layout + .as_mut() + .map(|t| t.populate_plugin_aliases_in_layout(&plugin_aliases)); + floating_panes_layout.iter_mut().for_each(|f| { + f.run + .as_mut() + .map(|f| f.populate_run_plugin_if_needed(&plugin_aliases)); + }); let mut extracted_run_instructions = tab_layout .clone() .unwrap_or_else(|| layout.new_tab().0) @@ -337,10 +361,11 @@ pub(crate) fn plugin_thread_main( .collect(); extracted_run_instructions.append(&mut extracted_floating_plugins); for run_instruction in extracted_run_instructions { - if let Some(Run::Plugin(run)) = run_instruction { + if let Some(Run::Plugin(run_plugin_or_alias)) = run_instruction { + let run_plugin = run_plugin_or_alias.get_run_plugin(); let skip_cache = false; let (plugin_id, _client_id) = wasm_bridge.load_plugin( - &run, + &run_plugin, Some(tab_index), size, None, @@ -349,7 +374,7 @@ pub(crate) fn plugin_thread_main( None, )?; plugin_ids - .entry((run.location, run.configuration)) + .entry(run_plugin_or_alias.clone()) .or_default() .push(plugin_id); } @@ -488,6 +513,7 @@ pub(crate) fn plugin_thread_main( &args, &bus, &mut wasm_bridge, + &plugin_aliases, ); }, None => { @@ -550,6 +576,7 @@ pub(crate) fn plugin_thread_main( &Some(message.message_args), &bus, &mut wasm_bridge, + &plugin_aliases, ); }, None => { @@ -660,16 +687,19 @@ fn pipe_to_specific_plugins( args: &Option<BTreeMap<String, String>>, bus: &Bus<PluginInstruction>, wasm_bridge: &mut WasmBridge, + plugin_aliases: &PluginAliases, ) { let is_private = true; let size = Size::default(); - match RunPlugin::from_url(&plugin_url) { - Ok(mut run_plugin) => { - if let Some(configuration) = configuration { - run_plugin.configuration = PluginUserConfiguration::new(configuration.clone()); - } + match RunPluginOrAlias::from_url( + &plugin_url, + configuration, + Some(plugin_aliases), + cwd.clone(), + ) { + Ok(run_plugin_or_alias) => { let all_plugin_ids = wasm_bridge.get_or_load_plugins( - run_plugin, + run_plugin_or_alias, size, cwd.clone(), skip_cache, @@ -683,7 +713,7 @@ fn pipe_to_specific_plugins( pipe_messages.push(( Some(plugin_id), client_id, - PipeMessage::new(pipe_source.clone(), name, payload, args, is_private), // PipeMessage::new(PipeSource::Cli(pipe_id.clone()), &name, &payload, &args, is_private) + PipeMessage::new(pipe_source.clone(), name, payload, args, is_private), )); } }, diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 98fe1a075..07212c3b3 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -8,9 +8,11 @@ use tempfile::tempdir; use wasmer::Store; use zellij_utils::data::{Event, Key, PermissionStatus, PermissionType, PluginCapabilities}; use zellij_utils::errors::ErrorContext; -use zellij_utils::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}; +use zellij_utils::input::layout::{ + Layout, PluginAlias, PluginUserConfiguration, RunPlugin, RunPluginLocation, RunPluginOrAlias, +}; use zellij_utils::input::permission::PermissionCache; -use zellij_utils::input::plugins::PluginsConfig; +use zellij_utils::input::plugins::PluginAliases; use zellij_utils::ipc::ClientAttributes; use zellij_utils::lazy_static::lazy_static; use zellij_utils::pane_size::Size; @@ -249,6 +251,17 @@ fn create_plugin_thread( let plugin_capabilities = PluginCapabilities::default(); let client_attributes = ClientAttributes::default(); let default_shell_action = None; // TODO: change me + let mut plugin_aliases = PluginAliases::default(); + plugin_aliases.aliases.insert( + "fixture_plugin_for_tests".to_owned(), + RunPlugin::from_url(&format!( + "file:{}/../target/e2e-data/plugins/fixture-plugin-for-tests.wasm", + std::env::var_os("CARGO_MANIFEST_DIR") + .unwrap() + .to_string_lossy() + )) + .unwrap(), + ); let plugin_thread = std::thread::Builder::new() .name("plugin_thread".to_string()) .spawn(move || { @@ -257,13 +270,13 @@ fn create_plugin_thread( plugin_bus, store, data_dir, - PluginsConfig::default(), Box::new(Layout::default()), default_shell, zellij_cwd, plugin_capabilities, client_attributes, default_shell_action, + Box::new(plugin_aliases), ) .expect("TEST") }) @@ -335,13 +348,13 @@ fn create_plugin_thread_with_server_receiver( plugin_bus, store, data_dir, - PluginsConfig::default(), Box::new(Layout::default()), default_shell, zellij_cwd, plugin_capabilities, client_attributes, default_shell_action, + Box::new(PluginAliases::default()), ) .expect("TEST"); }) @@ -419,13 +432,13 @@ fn create_plugin_thread_with_pty_receiver( plugin_bus, store, data_dir, - PluginsConfig::default(), Box::new(Layout::default()), default_shell, zellij_cwd, plugin_capabilities, client_attributes, default_shell_action, + Box::new(PluginAliases::default()), ) .expect("TEST") }) @@ -498,13 +511,13 @@ fn create_plugin_thread_with_background_jobs_receiver( plugin_bus, store, data_dir, - PluginsConfig::default(), Box::new(Layout::default()), default_shell, zellij_cwd, plugin_capabilities, client_attributes, default_shell_action, + Box::new(PluginAliases::default()), ) .expect("TEST") }) @@ -554,11 +567,90 @@ pub fn load_new_plugin_from_hd() { let (plugin_thread_sender, screen_receiver, teardown) = create_plugin_thread(None); let plugin_should_float = Some(false); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: 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::PluginBytes, + 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, + 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::InputReceived, + )])); // will be cached and sent to the plugin once it's loaded + screen_thread.join().unwrap(); // this might take a while if the cache is cold + teardown(); + let plugin_bytes_event = received_screen_instructions + .lock() + .unwrap() + .iter() + .find_map(|i| { + if let ScreenInstruction::PluginBytes(plugin_render_assets) = i { + for plugin_render_asset in plugin_render_assets { + let plugin_id = plugin_render_asset.plugin_id; + let client_id = plugin_render_asset.client_id; + let plugin_bytes = plugin_render_asset.bytes.clone(); + let plugin_bytes = String::from_utf8_lossy(plugin_bytes.as_slice()).to_string(); + if plugin_bytes.contains("InputReceived") { + return Some((plugin_id, client_id, plugin_bytes)); + } + } + } + None + }); + assert_snapshot!(format!("{:#?}", plugin_bytes_event)); +} + +#[test] +#[ignore] +pub fn load_new_plugin_with_plugin_alias() { + // here we load our fixture plugin into the plugin thread, and then send it an update message + // expecting tha thte plugin will log the received event and render it later after the update + // message (this is what the fixture plugin does) + // we then listen on our mock screen receiver to make sure we got a PluginBytes instruction + // that contains said render, and assert against it + let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its + 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(None); + let plugin_should_float = Some(false); + let plugin_title = Some("test_plugin".to_owned()); + let run_plugin = RunPluginOrAlias::Alias(PluginAlias { + name: "fixture_plugin_for_tests".to_owned(), + configuration: Default::default(), + run_plugin: None, + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -629,11 +721,11 @@ pub fn plugin_workers() { let plugin_host_folder = PathBuf::from(temp_folder.path()); let cache_path = plugin_host_folder.join("permissions_test.kdl"); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -708,11 +800,11 @@ pub fn plugin_workers_persist_state() { let plugin_host_folder = PathBuf::from(temp_folder.path()); let cache_path = plugin_host_folder.join("permissions_test.kdl"); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -797,11 +889,11 @@ pub fn can_subscribe_to_hd_events() { create_plugin_thread(Some(plugin_host_folder)); let plugin_should_float = Some(false); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -874,11 +966,11 @@ pub fn switch_to_mode_plugin_command() { create_plugin_thread(Some(plugin_host_folder)); let plugin_should_float = Some(false); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -945,11 +1037,11 @@ pub fn switch_to_mode_plugin_command_permission_denied() { create_plugin_thread(Some(plugin_host_folder)); let plugin_should_float = Some(false); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -1016,11 +1108,11 @@ pub fn new_tabs_with_layout_plugin_command() { create_plugin_thread(Some(plugin_host_folder)); let plugin_should_float = Some(false); let plugin_title = Some("test_plugin".to_owned()); - let run_plugin = RunPlugin { + let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin { _allow_exec_host_cmd: false, location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)), configuration: Default::default(), - }; + }); let tab_index = 1; let client_id = 1; let size = Size { @@ -1101,11 +1193,11 @@ pub fn new_tab_plugin_command() { create_plugin_thread(Some( |