use std::borrow::Borrow;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
use serde::{Deserialize, Serialize};
use url::Url;
use super::layout::{RunPlugin, RunPluginLocation};
#[cfg(not(target_family = "wasm"))]
use crate::consts::ASSET_MAP;
pub use crate::data::PluginTag;
use crate::errors::prelude::*;
use std::collections::BTreeMap;
use std::fmt;
#[derive(Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginsConfig(pub HashMap<PluginTag, PluginConfig>);
impl fmt::Debug for PluginsConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut stable_sorted = BTreeMap::new();
for (plugin_tag, plugin_config) in self.0.iter() {
stable_sorted.insert(plugin_tag, plugin_config);
}
write!(f, "{:#?}", stable_sorted)
}
}
impl PluginsConfig {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn from_data(data: HashMap<PluginTag, PluginConfig>) -> Self {
PluginsConfig(data)
}
pub fn get(&self, run: impl Borrow<RunPlugin>) -> Option<PluginConfig> {
let run = run.borrow();
match &run.location {
RunPluginLocation::File(path) => Some(PluginConfig {
path: path.clone(),
run: PluginType::Pane(None),
_allow_exec_host_cmd: run._allow_exec_host_cmd,
location: run.location.clone(),
}),
RunPluginLocation::Zellij(tag) => self.0.get(tag).cloned().map(|plugin| PluginConfig {
_allow_exec_host_cmd: run._allow_exec_host_cmd,
..plugin
}),
}
}
pub fn iter(&self) -> impl Iterator<Item = &PluginConfig> {
self.0.values()
}
pub fn merge(&self, other: Self) -> Self {
let mut plugin_config = self.0.clone();
plugin_config.extend(other.0);
Self(plugin_config)
}
}
impl Default for PluginsConfig {
fn default() -> Self {
PluginsConfig(HashMap::new())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct PluginConfig {
pub path: PathBuf,
pub run: PluginType,
pub _allow_exec_host_cmd: bool,
pub location: RunPluginLocation,
}
impl PluginConfig {
pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Result<Vec<u8>> {
let err_context =
|err: std::io::Error, path: &PathBuf| format!("{}: '{}'", err, path.display());
let paths_arr = [
&self.path,
&self.path.with_extension("wasm"),
&plugin_dir.join(&self.path).with_extension("wasm"),
];
let mut paths = paths_arr.to_vec();
paths.dedup();
let mut last_err: Result<Vec<u8>> = Err(anyhow!("failed to load plugin from disk"));
for path in paths {
#[cfg(not(target_family = "wasm"))]
if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
let asset_path = PathBuf::from("plugins").join(path);
if let Some(bytes) = ASSET_MAP.get(&asset_path) {
log::debug!("Loaded plugin '{}' from internal assets", path.display());
if plugin_dir.join(path).with_extension("wasm").exists() {
log::info!(
"Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
path.display(),
plugin_dir.display()
);
}
return Ok(bytes.to_vec());
}
}
match fs::read(&path) {
Ok(val) => {
log::debug!("Loaded plugin '{}' from disk", path.display());
return Ok(val);
},
Err(err) => {
last_err = last_err.with_context(|| err_context(err, &path));
},
}
}
#[cfg(not(target_family = "wasm"))]
if self.is_builtin() {
let plugin_path = self.path.with_extension("wasm");
if cfg!(feature = "disable_automatic_asset_installation")
&& ASSET_MAP.contains_key(&PathBuf::from("plugins").join(&plugin_path))
{
return Err(ZellijError::BuiltinPluginMissing {
plugin_path,
plugin_dir: plugin_dir.to_owned(),
source: last_err.unwrap_err(),
})
.context("failed to load a plugin");
} else {
return Err(ZellijError::BuiltinPluginNonexistent {
plugin_path,
source: last_err.unwrap_err(),
})
.context("failed to load a plugin");
}
}
return last_err;
}
pub fn set_tab_index(&mut self, tab_index: usize) {
match self.run {
PluginType::Pane(..) => {
self.run = PluginType::Pane(Some(tab_index));
},
PluginType::Headless => {},
}
}
pub fn is_builtin(&self) -> bool {
matches!(self.location, RunPluginLocation::Zellij(_))
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum PluginType {
Headless,
Pane(Option<usize>), }
impl Default for PluginType {
fn default() -> Self {
Self::Pane(None)
}
}
#[derive(Error, Debug, PartialEq)]
pub enum PluginsConfigError {
#[error("Duplication in plugin tag names is not allowed: '{}'", String::from(.0.clone()))]
DuplicatePlugins(PluginTag),
#[error("Only 'file:' and 'zellij:' url schemes are supported for plugin lookup. '{0}' does not match either.")]
InvalidUrl(Url),
#[error("Could not find plugin at the path: '{0:?}'")]
InvalidPluginLocation(PathBuf),
}