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;
13pub use crate::data::PluginTag;
14use crate::errors::prelude::*;
15
16#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
17pub struct PluginAliases {
18 pub aliases: BTreeMap<String, RunPlugin>,
19}
20
21impl PluginAliases {
22 pub fn merge(&mut self, other: Self) {
23 self.aliases.extend(other.aliases);
24 }
25 pub fn from_data(aliases: BTreeMap<String, RunPlugin>) -> Self {
26 PluginAliases { aliases }
27 }
28 pub fn list(&self) -> Vec<String> {
29 self.aliases.keys().cloned().collect()
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
35pub struct PluginConfig {
36 pub path: PathBuf,
38 pub _allow_exec_host_cmd: bool,
40 pub location: RunPluginLocation,
42 pub initial_userspace_configuration: PluginUserConfiguration,
44 pub initial_cwd: Option<PathBuf>,
46}
47
48impl PluginConfig {
49 pub fn from_run_plugin(run_plugin: &RunPlugin) -> Option<PluginConfig> {
50 match &run_plugin.location {
51 RunPluginLocation::File(path) => Some(PluginConfig {
52 path: path.clone(),
53 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
54 location: run_plugin.location.clone(),
55 initial_userspace_configuration: run_plugin.configuration.clone(),
56 initial_cwd: run_plugin.initial_cwd.clone(),
57 }),
58 RunPluginLocation::Zellij(tag) => {
59 let tag = tag.to_string();
60 if tag == "status-bar"
61 || tag == "tab-bar"
62 || tag == "compact-bar"
63 || tag == "strider"
64 || tag == "session-manager"
65 || tag == "configuration"
66 || tag == "plugin-manager"
67 || tag == "about"
68 || tag == "share"
69 || tag == "multiple-select"
70 || tag == "layout-manager"
71 || tag == "link"
72 {
73 Some(PluginConfig {
74 path: PathBuf::from(&tag),
75 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
76 location: RunPluginLocation::parse(&format!("zellij:{}", tag), None)
77 .ok()?,
78 initial_userspace_configuration: run_plugin.configuration.clone(),
79 initial_cwd: run_plugin.initial_cwd.clone(),
80 })
81 } else {
82 None
83 }
84 },
85 RunPluginLocation::Remote(_) => Some(PluginConfig {
86 path: PathBuf::new(),
87 _allow_exec_host_cmd: run_plugin._allow_exec_host_cmd,
88 location: run_plugin.location.clone(),
89 initial_userspace_configuration: run_plugin.configuration.clone(),
90 initial_cwd: run_plugin.initial_cwd.clone(),
91 }),
92 }
93 }
94 pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Result<Vec<u8>> {
111 let err_context =
112 |err: std::io::Error, path: &PathBuf| format!("{}: '{}'", err, path.display());
113
114 let paths_arr = [
116 &self.path,
117 &self.path.with_extension("wasm"),
118 &plugin_dir.join(&self.path).with_extension("wasm"),
119 ];
120 let mut paths = paths_arr.to_vec();
123 paths.dedup();
124
125 let mut last_err: Result<Vec<u8>> = Err(anyhow!("failed to load plugin from disk"));
131 for path in paths {
132 #[cfg(not(target_family = "wasm"))]
135 if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
136 let asset_path = PathBuf::from("plugins").join(path);
137 if let Some(bytes) = ASSET_MAP.get(&asset_path) {
138 log::debug!("Loaded plugin '{}' from internal assets", path.display());
139
140 if plugin_dir.join(path).with_extension("wasm").exists() {
141 log::info!(
142 "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
143 path.display(),
144 plugin_dir.display()
145 );
146 }
147
148 return Ok(bytes.to_vec());
149 }
150 }
151
152 match fs::read(&path) {
154 Ok(val) => {
155 log::debug!("Loaded plugin '{}' from disk", path.display());
156 return Ok(val);
157 },
158 Err(err) => {
159 last_err = last_err.with_context(|| err_context(err, &path));
160 },
161 }
162 }
163
164 #[cfg(not(target_family = "wasm"))]
166 if self.is_builtin() {
167 let plugin_path = self.path.with_extension("wasm");
169
170 if cfg!(feature = "disable_automatic_asset_installation")
171 && ASSET_MAP.contains_key(&PathBuf::from("plugins").join(&plugin_path))
172 {
173 return Err(ZellijError::BuiltinPluginMissing {
174 plugin_path,
175 plugin_dir: plugin_dir.to_owned(),
176 source: last_err.unwrap_err(),
177 })
178 .context("failed to load a plugin");
179 } else {
180 return Err(ZellijError::BuiltinPluginNonexistent {
181 plugin_path,
182 source: last_err.unwrap_err(),
183 })
184 .context("failed to load a plugin");
185 }
186 }
187
188 return last_err;
189 }
190
191 pub fn is_builtin(&self) -> bool {
192 matches!(self.location, RunPluginLocation::Zellij(_))
193 }
194}
195
196#[derive(Error, Debug, PartialEq)]
197pub enum PluginsConfigError {
198 #[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))]
199 DuplicatePlugins(PluginTag),
200 #[error("Failed to parse url: {0:?}")]
201 InvalidUrl(#[from] url::ParseError),
202 #[error("Only 'file:', 'http(s):' and 'zellij:' url schemes are supported for plugin lookup. '{0}' does not match either.")]
203 InvalidUrlScheme(Url),
204 #[error("Could not find plugin at the path: '{0:?}'")]
205 InvalidPluginLocation(PathBuf),
206}