summaryrefslogtreecommitdiffstats
path: root/zellij-server/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'zellij-server/src/plugins')
-rw-r--r--zellij-server/src/plugins/mod.rs214
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs415
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__load_new_plugin_with_plugin_alias.snap12
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__start_or_reload_plugin.snap22
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs283
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs14
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(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 {
@@ -1172,11 +1264,11 @@ pub fn go_to_next_tab_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 {
@@ -1242,11 +1334,11 @@ pub fn go_to_previous_tab_plugin_command() {
create_plugin_thread(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());