zellij_utils/input/
plugins.rs1use std::collections::BTreeMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5use thiserror::Error;
6
7use serde::{Deserialize, Serialize};
8use url::Url;
9
10use super::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation};
11#[cfg(not(target_family = "wasm"))]
12use crate::consts::ASSET_MAP;
13use crate::consts::BUILTIN_PLUGIN_NAMES;
14pub use crate::data::PluginTag;
15use crate::errors::prelude::*;
16
17#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
18pub struct PluginAliases {
19 pub aliases: BTreeMap<String, RunPlugin>,
20}
21
22impl PluginAliases {
23 pub fn merge(&mut self, other: Self) {
24 self.aliases.extend(other.aliases);
25 }
26 pub fn from_data(aliases: BTreeMap<String, RunPlugin>) -> Self {
27 PluginAliases { aliases }
28 }
29 pub fn list(&self) -> Vec<String> {
30 self.aliases.keys().cloned().collect()
31 }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
36pub struct PluginConfig {
37 pub path: PathBuf,
39 pub _allow_exec_host_cmd: bool,
41 pub location: RunPluginLocation,
43 pub initial_userspace_configuration: PluginUserConfiguration,
45 pub initial_cwd: Option<PathBuf>,
47}
48
49impl PluginConfig {
50 pub fn from_run_plugin(run_plugin: &RunPlugin) -> Option<PluginConfig> {
51 match &run_plugin.location {
52 RunPluginLocation::File(path) => Some(PluginConfig {
53 path: path.clone(),
54 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
55 location: run_plugin.location.clone(),
56 initial_userspace_configuration: run_plugin.configuration.clone(),
57 initial_cwd: run_plugin.initial_cwd.clone(),
58 }),
59 RunPluginLocation::Zellij(tag) => {
60 let tag = tag.to_string();
61 if BUILTIN_PLUGIN_NAMES.contains(&tag.as_str()) {
62 Some(PluginConfig {
63 path: PathBuf::from(&tag),
64 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
65 location: RunPluginLocation::parse(&format!("zellij:{}", tag), None)
66 .ok()?,
67 initial_userspace_configuration: run_plugin.configuration.clone(),
68 initial_cwd: run_plugin.initial_cwd.clone(),
69 })
70 } else {
71 None
72 }
73 },
74 RunPluginLocation::Remote(_) => Some(PluginConfig {
75 path: PathBuf::new(),
76 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
77 location: run_plugin.location.clone(),
78 initial_userspace_configuration: run_plugin.configuration.clone(),
79 initial_cwd: run_plugin.initial_cwd.clone(),
80 }),
81 }
82 }
83 pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Result<Vec<u8>> {
103 let err_context =
104 |err: std::io::Error, path: &PathBuf| format!("{}: '{}'", err, path.display());
105
106 #[allow(unused_mut)]
108 let mut paths: Vec<PathBuf> = vec![
109 self.path.clone(),
110 self.path.with_extension("wasm"),
111 plugin_dir.join(&self.path).with_extension("wasm"),
112 ];
113 #[cfg(not(target_family = "wasm"))]
114 paths.push(
115 crate::home::system_data_dir()
116 .join("plugins")
117 .join(&self.path)
118 .with_extension("wasm"),
119 );
120 paths.dedup();
123
124 let mut last_err: Result<Vec<u8>> = Err(anyhow!("failed to load plugin from disk"));
130 for path in paths {
131 #[cfg(not(target_family = "wasm"))]
134 if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
135 let asset_path = PathBuf::from("plugins").join(&path);
136 if let Some(bytes) = ASSET_MAP.get(&asset_path) {
137 log::debug!("Loaded plugin '{}' from internal assets", path.display());
138
139 if plugin_dir.join(&path).with_extension("wasm").exists() {
140 log::info!(
141 "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
142 path.display(),
143 plugin_dir.display()
144 );
145 }
146
147 return Ok(bytes.to_vec());
148 }
149 }
150
151 match fs::read(&path) {
153 Ok(val) => {
154 log::debug!("Loaded plugin '{}' from disk", path.display());
155 return Ok(val);
156 },
157 Err(err) => {
158 last_err = last_err.with_context(|| err_context(err, &path));
159 },
160 }
161 }
162
163 #[cfg(not(target_family = "wasm"))]
165 if self.is_builtin() {
166 let plugin_path = self.path.with_extension("wasm");
168
169 if cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin_name() {
170 return Err(ZellijError::BuiltinPluginMissing {
171 plugin_path,
172 plugin_dir: plugin_dir.to_owned(),
173 source: last_err.unwrap_err(),
174 })
175 .context("failed to load a plugin");
176 } else {
177 return Err(ZellijError::BuiltinPluginNonexistent {
178 plugin_path,
179 source: last_err.unwrap_err(),
180 })
181 .context("failed to load a plugin");
182 }
183 }
184
185 return last_err;
186 }
187
188 pub fn is_builtin(&self) -> bool {
189 matches!(self.location, RunPluginLocation::Zellij(_))
190 }
191
192 pub fn is_builtin_name(&self) -> bool {
193 self.path
194 .file_stem()
195 .and_then(|stem| stem.to_str())
196 .map(|name| BUILTIN_PLUGIN_NAMES.contains(&name))
197 .unwrap_or(false)
198 }
199}
200
201#[derive(Error, Debug, PartialEq)]
202pub enum PluginsConfigError {
203 #[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))]
204 DuplicatePlugins(PluginTag),
205 #[error("Failed to parse url: {0:?}")]
206 InvalidUrl(#[from] url::ParseError),
207 #[error("Only 'file:', 'http(s):' and 'zellij:' url schemes are supported for plugin lookup. '{0}' does not match either.")]
208 InvalidUrlScheme(Url),
209 #[error("Could not find plugin at the path: '{0:?}'")]
210 InvalidPluginLocation(PathBuf),
211}