summaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorspacemaison <tuchsen@protonmail.com>2021-09-22 10:13:21 -0700
committerGitHub <noreply@github.com>2021-09-22 18:13:21 +0100
commitc9372212f68fed52d45590b2dac271ab6270d943 (patch)
tree031f384ab012817656876b5db020b9e9c6a41a1f /zellij-utils
parentc39f02181052f1c0948001d4ae419009fc0df677 (diff)
feat(plugin): add manifest to allow for plugin configuration (#660)
* feat(plugins-manifest): Add a plugins manifest to allow for more configuration of plugins * refactor(plugins-manifest): Better storage of plugin metadata in wasm_vm * fix(plugins-manifest): Inherit permissions from run configuration * refactor(plugins-manifest): Rename things for more clarity - The Plugins/Plugin structs had "Config" appended to them to clarify that they're metadata about plugins, and not the plugins themselves. - The PluginType::OncePerPane variant was renamed to be just PluginType::Pane, and the documentation clarified to explain what it is. - The "service" nomenclature was completely removed in favor of "headless". * refactor(plugins-manifest): Move security warning into start plugin * refactor(plugins-manifest): Remove hack in favor of standard method * refactor(plugins-manifest): Change display of plugin location The only time that a plugin location is displayed in Zellij is the border of the pane. Having `zellij:strider` display instead of just `strider` was a little annoying, so we're stripping out the scheme information from a locations display. * refactor(plugins-manifest): Add a little more documentation * fix(plugins-manifest): Formatting Co-authored-by: Jesse Tuchsen <not@disclosing>
Diffstat (limited to 'zellij-utils')
-rw-r--r--zellij-utils/Cargo.toml2
-rw-r--r--zellij-utils/assets/config/default.yaml7
-rw-r--r--zellij-utils/assets/layouts/default.yaml4
-rw-r--r--zellij-utils/assets/layouts/disable-status-bar.yaml2
-rw-r--r--zellij-utils/assets/layouts/strider.yaml6
-rw-r--r--zellij-utils/src/input/config.rs29
-rw-r--r--zellij-utils/src/input/layout.rs150
-rw-r--r--zellij-utils/src/input/mod.rs1
-rw-r--r--zellij-utils/src/input/plugins.rs315
-rw-r--r--zellij-utils/src/input/unit/fixtures/layouts/three-panes-with-tab-and-default-plugins.yaml5
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs89
-rw-r--r--zellij-utils/src/ipc.rs10
-rw-r--r--zellij-utils/src/setup.rs1
13 files changed, 535 insertions, 86 deletions
diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml
index 66e8c1cf4..8751edbd8 100644
--- a/zellij-utils/Cargo.toml
+++ b/zellij-utils/Cargo.toml
@@ -21,11 +21,13 @@ nix = "0.19.1"
once_cell = "1.7.2"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
+serde_json = "1.0"
signal-hook = "0.3"
strip-ansi-escapes = "0.1.0"
structopt = "0.3"
strum = "0.20.0"
termion = "1.5.0"
+url = { version = "2.2.2", features = ["serde"] }
vte = "0.10.1"
zellij-tile = { path = "../zellij-tile/", version = "0.18.0" }
log = "0.4.14"
diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml
index 60b4069eb..c00fdfbed 100644
--- a/zellij-utils/assets/config/default.yaml
+++ b/zellij-utils/assets/config/default.yaml
@@ -248,6 +248,13 @@ keybinds:
key: [Ctrl: 'q',]
- action: [Detach,]
key: [Char: 'd',]
+plugins:
+ - path: tab-bar
+ tag: tab-bar
+ - path: status-bar
+ tag: status-bar
+ - path: strider
+ tag: strider
# Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
# eg. when terminal window with an active zellij session is closed
diff --git a/zellij-utils/assets/layouts/default.yaml b/zellij-utils/assets/layouts/default.yaml
index 549dea24b..0688e54bb 100644
--- a/zellij-utils/assets/layouts/default.yaml
+++ b/zellij-utils/assets/layouts/default.yaml
@@ -8,7 +8,7 @@ template:
Fixed: 1
run:
plugin:
- path: tab-bar
+ location: "zellij:tab-bar"
- direction: Vertical
body: true
- direction: Vertical
@@ -17,6 +17,6 @@ template:
Fixed: 2
run:
plugin:
- path: status-bar
+ location: "zellij:status-bar"
tabs:
- direction: Vertical
diff --git a/zellij-utils/assets/layouts/disable-status-bar.yaml b/zellij-utils/assets/layouts/disable-status-bar.yaml
index e97bb8f1e..107793980 100644
--- a/zellij-utils/assets/layouts/disable-status-bar.yaml
+++ b/zellij-utils/assets/layouts/disable-status-bar.yaml
@@ -8,6 +8,6 @@ template:
Fixed: 1
run:
plugin:
- path: tab-bar
+ location: "zellij:tab-bar"
- direction: Vertical
body: true
diff --git a/zellij-utils/assets/layouts/strider.yaml b/zellij-utils/assets/layouts/strider.yaml
index ccb2a5748..26e1eba4f 100644
--- a/zellij-utils/assets/layouts/strider.yaml
+++ b/zellij-utils/assets/layouts/strider.yaml
@@ -8,7 +8,7 @@ template:
Fixed: 1
run:
plugin:
- path: tab-bar
+ location: "zellij:tab-bar"
- direction: Vertical
body: true
- direction: Vertical
@@ -17,7 +17,7 @@ template:
Fixed: 2
run:
plugin:
- path: status-bar
+ location: "zellij:status-bar"
tabs:
- direction: Vertical
parts:
@@ -26,5 +26,5 @@ tabs:
Percent: 20
run:
plugin:
- path: strider
+ location: "zellij:strider"
- direction: Horizontal
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index 941efa1d9..e55798e75 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -5,15 +5,16 @@ use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
+use serde::{Deserialize, Serialize};
+use std::convert::{TryFrom, TryInto};
+
use super::keybinds::{Keybinds, KeybindsFromYaml};
use super::options::Options;
+use super::plugins::{PluginsConfig, PluginsConfigError, PluginsConfigFromYaml};
use super::theme::ThemesFromYaml;
use crate::cli::{CliArgs, Command};
use crate::setup;
-use serde::{Deserialize, Serialize};
-use std::convert::TryFrom;
-
const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
type ConfigResult = Result<Config, ConfigError>;
@@ -25,6 +26,8 @@ pub struct ConfigFromYaml {
pub options: Option<Options>,
pub keybinds: Option<KeybindsFromYaml>,
pub themes: Option<ThemesFromYaml>,
+ #[serde(default)]
+ pub plugins: PluginsConfigFromYaml,
}
/// Main configuration.
@@ -33,6 +36,7 @@ pub struct Config {
pub keybinds: Keybinds,
pub options: Options,
pub themes: Option<ThemesFromYaml>,
+ pub plugins: PluginsConfig,
}
#[derive(Debug)]
@@ -47,6 +51,8 @@ pub enum ConfigError {
FromUtf8(std::string::FromUtf8Error),
// Naming a part in a tab is unsupported
LayoutNameInTab(LayoutNameInTabError),
+ // Plugins have a semantic error, usually trying to parse two of the same tag
+ PluginsError(PluginsConfigError),
}
impl Default for Config {
@@ -54,11 +60,13 @@ impl Default for Config {
let keybinds = Keybinds::default();
let options = Options::default();
let themes = None;
+ let plugins = PluginsConfig::default();
Config {
keybinds,
options,
themes,
+ plugins,
}
}
}
@@ -106,9 +114,11 @@ impl Config {
let keybinds = Keybinds::get_default_keybinds_with_config(config.keybinds);
let options = Options::from_yaml(config.options);
let themes = config.themes;
+ let plugins = PluginsConfig::get_plugins_with_default(config.plugins.try_into()?);
Ok(Config {
keybinds,
options,
+ plugins,
themes,
})
}
@@ -129,10 +139,11 @@ impl Config {
}
/// Gets default configuration from assets
- // TODO Deserialize the Configuration from bytes &[u8],
+ // TODO Deserialize the Config from bytes &[u8],
// once serde-yaml supports zero-copy
pub fn from_default_assets() -> ConfigResult {
- Self::from_yaml(String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?.as_str())
+ let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?;
+ Self::from_yaml(cfg.as_str())
}
}
@@ -179,6 +190,7 @@ impl Display for ConfigError {
ConfigError::LayoutNameInTab(ref err) => {
write!(formatter, "There was an error in the layout file, {}", err)
}
+ ConfigError::PluginsError(ref err) => write!(formatter, "PluginsError: {}", err),
}
}
}
@@ -191,6 +203,7 @@ impl std::error::Error for ConfigError {
ConfigError::Serde(ref err) => Some(err),
ConfigError::FromUtf8(ref err) => Some(err),
ConfigError::LayoutNameInTab(ref err) => Some(err),
+ ConfigError::PluginsError(ref err) => Some(err),
}
}
}
@@ -219,6 +232,12 @@ impl From<LayoutNameInTabError> for ConfigError {
}
}
+impl From<PluginsConfigError> for ConfigError {
+ fn from(err: PluginsConfigError) -> ConfigError {
+ ConfigError::PluginsError(err)
+ }
+}
+
// The unit test location.
#[cfg(test)]
mod config_test {
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 9dc8cd47b..033614151 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -18,14 +18,18 @@ use crate::{
};
use crate::{serde, serde_yaml};
+use super::plugins::{PluginTag, PluginsConfigError};
use serde::{Deserialize, Serialize};
+use std::convert::{TryFrom, TryInto};
use std::vec::Vec;
use std::{
cmp::max,
+ fmt, fs,
ops::Not,
path::{Path, PathBuf},
};
use std::{fs::File, io::prelude::*};
+use url::Url;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
#[serde(crate = "self::serde")]
@@ -56,17 +60,68 @@ pub enum SplitSize {
#[serde(crate = "self::serde")]
pub enum Run {
#[serde(rename = "plugin")]
- Plugin(Option<RunPlugin>),
+ Plugin(RunPlugin),
#[serde(rename = "command")]
Command(RunCommand),
}
-#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[serde(crate = "self::serde")]
+pub enum RunFromYaml {
+ #[serde(rename = "plugin")]
+ Plugin(RunPluginFromYaml),
+ #[serde(rename = "command")]
+ Command(RunCommand),
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[serde(crate = "self::serde")]
+pub struct RunPluginFromYaml {
+ #[serde(default)]
+ pub _allow_exec_host_cmd: bool,
+ pub location: Url,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(crate = "self::serde")]
pub struct RunPlugin {
- pub path: PathBuf,
#[serde(default)]
pub _allow_exec_host_cmd: bool,
+ pub location: RunPluginLocation,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[serde(crate = "self::serde")]
+pub enum RunPluginLocation {
+ File(PathBuf),
+ Zellij(PluginTag),
+}
+
+impl From<&RunPluginLocation> for Url {
+ fn from(location: &RunPluginLocation) -> Self {
+ let url = match location {
+ RunPluginLocation::File(path) => format!(
+ "file:{}",
+ path.clone().into_os_string().into_string().unwrap()
+ ),
+ RunPluginLocation::Zellij(tag) => format!("zellij:{}", tag),
+ };
+ Self::parse(&url).unwrap()
+ }
+}
+
+impl fmt::Display for RunPluginLocation {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match self {
+ Self::File(path) => write!(
+ f,
+ "{}",
+ path.clone().into_os_string().into_string().unwrap()
+ ),
+
+ Self::Zellij(tag) => write!(f, "{}", tag),
+ }
+ }
}
// The layout struct ultimately used to build the layouts.
@@ -193,7 +248,7 @@ pub struct LayoutTemplate {
#[serde(default)]
pub body: bool,
pub split_size: Option<SplitSize>,
- pub run: Option<Run>,
+ pub run: Option<RunFromYaml>,
}
impl LayoutTemplate {
@@ -235,9 +290,9 @@ pub struct TabLayout {
#[serde(default)]
pub parts: Vec<TabLayout>,
pub split_size: Option<SplitSize>,
- pub run: Option<Run>,
#[serde(default)]
pub name: String,
+ pub run: Option<RunFromYaml>,
}
impl TabLayout {
@@ -291,25 +346,23 @@ impl Layout {
split_space(space, self)
}
- pub fn merge_tab_layout(&mut self, tab: TabLayout) {
- self.parts.push(tab.into());
- }
-
pub fn merge_layout_parts(&mut self, mut parts: Vec<Layout>) {
self.parts.append(&mut parts);
}
- fn from_vec_tab_layout(tab_layout: Vec<TabLayout>) -> Vec<Self> {
+ fn from_vec_tab_layout(tab_layout: Vec<TabLayout>) -> Result<Vec<Self>, ConfigError> {
tab_layout
.iter()
- .map(|tab_layout| Layout::from(tab_layout.to_owned()))
+ .map(|tab_layout| Layout::try_from(tab_layout.to_owned()))
.collect()
}
- fn from_vec_template_layout(layout_template: Vec<LayoutTemplate>) -> Vec<Self> {
+ fn from_vec_template_layout(
+ layout_template: Vec<LayoutTemplate>,
+ ) -> Result<Vec<Self>, ConfigError> {
layout_template
.iter()
- .map(|layout_template| Layout::from(layout_template.to_owned()))
+ .map(|layout_template| Layout::try_from(layout_template.to_owned()))
.collect()
}
}
@@ -408,15 +461,55 @@ fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneG
pane_positions
}
-impl From<TabLayout> for Layout {
- fn from(tab: TabLayout) -> Self {
- Layout {
+impl TryFrom<Url> for RunPluginLocation {
+ type Error = PluginsConfigError;
+
+ fn try_from(url: Url) -> Result<Self, Self::Error> {
+ match url.scheme() {
+ "zellij" => Ok(Self::Zellij(PluginTag::new(url.path()))),
+ "file" => {
+ let path = PathBuf::from(url.path());
+ let canonicalize = |p: &Path| {
+ fs::canonicalize(p)
+ .map_err(|_| PluginsConfigError::InvalidPluginLocation(p.to_owned()))
+ };
+ canonicalize(&path)
+ .or_else(|_| match path.strip_prefix("/") {
+ Ok(path) => canonicalize(path),
+ Err(_) => Err(PluginsConfigError::InvalidPluginLocation(path.to_owned())),
+ })
+ .map(Self::File)
+ }
+ _ => Err(PluginsConfigError::InvalidUrl(url)),
+ }
+ }
+}
+
+impl TryFrom<RunFromYaml> for Run {
+ type Error = PluginsConfigError;
+
+ fn try_from(run: RunFromYaml) -> Result<Self, Self::Error> {
+ match run {
+ RunFromYaml::Command(command) => Ok(Run::Command(command)),
+ RunFromYaml::Plugin(plugin) => Ok(Run::Plugin(RunPlugin {
+ _allow_exec_host_cmd: plugin._allow_exec_host_cmd,
+ location: plugin.location.try_into()?,
+ })),
+ }
+ }
+}
+
+impl TryFrom<TabLayout> for Layout {
+ type Error = ConfigError;
+
+ fn try_from(tab: TabLayout) -> Result<Self, Self::Error> {
+ Ok(Layout {
direction: tab.direction,
borderless: tab.borderless,
- parts: Self::from_vec_tab_layout(tab.parts),
+ parts: Self::from_vec_tab_layout(tab.parts)?,
split_size: tab.split_size,
- run: tab.run,
- }
+ run: tab.run.map(Run::try_from).transpose()?,
+ })
}
}
@@ -433,15 +526,22 @@ impl From<TabLayout> for LayoutTemplate {
}
}
-impl From<LayoutTemplate> for Layout {
- fn from(template: LayoutTemplate) -> Self {
- Layout {
+impl TryFrom<LayoutTemplate> for Layout {
+ type Error = ConfigError;
+
+ fn try_from(template: LayoutTemplate) -> Result<Self, Self::Error> {
+ Ok(Layout {
direction: template.direction,
borderless: template.borderless,
- parts: Self::from_vec_template_layout(template.parts),
+ parts: Self::from_vec_template_layout(template.parts)?,
split_size: template.split_size,
- run: template.run,
- }
+ run: template
+ .run
+ .map(Run::try_from)
+ // FIXME: This is just Result::transpose but that method is unstable, when it
+ // stabalizes we should swap this out.
+ .map_or(Ok(None), |r| r.map(Some))?,
+ })
}
}
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
index 68aa9dd79..409c9afae 100644
--- a/zellij-utils/src/input/mod.rs
+++ b/zellij-utils/src/input/mod.rs
@@ -7,6 +7,7 @@ pub mod keybinds;
pub mod layout;
pub mod mouse;
pub mod options;
+pub mod plugins;
pub mod theme;
use termion::input::TermRead;
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
new file mode 100644
index 000000000..931d9f780
--- /dev/null
+++ b/zellij-utils/src/input/plugins.rs
@@ -0,0 +1,315 @@
+//! Plugins configuration metadata
+use std::borrow::Borrow;
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::fmt::{self, Display};
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+use super::config::ConfigFromYaml;
+use super::layout::{RunPlugin, RunPluginLocation};
+use crate::setup;
+pub use zellij_tile::data::PluginTag;
+
+lazy_static! {
+ static ref DEFAULT_CONFIG_PLUGINS: PluginsConfig = {
+ let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec()).unwrap();
+ let cfg_yaml: ConfigFromYaml = serde_yaml::from_str(cfg.as_str()).unwrap();
+ PluginsConfig::try_from(cfg_yaml.plugins).unwrap()
+ };
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
+pub struct PluginsConfigFromYaml(Vec<PluginConfigFromYaml>);
+
+/// Used in the config struct for plugin metadata
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct PluginsConfig(HashMap<PluginTag, PluginConfig>);
+
+impl PluginsConfig {
+ pub fn new() -> Self {
+ Self(HashMap::new())
+ }
+
+ /// Entrypoint from the config module
+ pub fn get_plugins_with_default(user_plugins: Self) -> Self {
+ let mut base_plugins = DEFAULT_CONFIG_PLUGINS.clone();
+ base_plugins.0.extend(user_plugins.0);
+ base_plugins
+ }
+
+ /// Get plugin config from run configuration specified in layout files.
+ 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()
+ }
+}
+
+impl Default for PluginsConfig {
+ fn default() -> Self {
+ Self::get_plugins_with_default(PluginsConfig::new())
+ }
+}
+
+impl TryFrom<PluginsConfigFromYaml> for PluginsConfig {
+ type Error = PluginsConfigError;
+
+ fn try_from(yaml: PluginsConfigFromYaml) -> Result<Self, PluginsConfigError> {
+ let mut plugins = HashMap::new();
+ for plugin in yaml.0 {
+ if plugins.contains_key(&plugin.tag) {
+ return Err(PluginsConfigError::DuplicatePlugins(plugin.tag));
+ }
+ plugins.insert(plugin.tag.clone(), plugin.into());
+ }
+
+ Ok(PluginsConfig(plugins))
+ }
+}
+
+impl From<PluginConfigFromYaml> for PluginConfig {
+ fn from(plugin: PluginConfigFromYaml) -> Self {
+ PluginConfig {
+ path: plugin.path,
+ run: match plugin.run {
+ PluginTypeFromYaml::Pane => PluginType::Pane(None),
+ PluginTypeFromYaml::Headless => PluginType::Headless,
+ },
+ _allow_exec_host_cmd: plugin._allow_exec_host_cmd,
+ location: RunPluginLocation::Zellij(plugin.tag),
+ }
+ }
+}
+
+/// Plugin metadata
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct PluginConfig {
+ /// Path of the plugin, see resolve_wasm_bytes for resolution semantics
+ pub path: PathBuf,
+ /// Plugin type
+ pub run: PluginType,
+ /// Allow command execution from plugin
+ pub _allow_exec_host_cmd: bool,
+ /// Original location of the
+ pub location: RunPluginLocation,
+}
+
+impl PluginConfig {
+ /// Resolve wasm plugin bytes for the plugin path and given plugin directory. Attempts to first
+ /// resolve the plugin path as an absolute path, then adds a ".wasm" extension to the path and
+ /// resolves that, finally we use the plugin directoy joined with the path with an appended
+ /// ".wasm" extension. So if our path is "tab-bar" and the given plugin dir is
+ /// "/home/bob/.zellij/plugins" the lookup chain will be this:
+ ///
+ /// ```bash
+ /// /tab-bar
+ /// /tab-bar.wasm
+ /// /home/bob/.zellij/plugins/tab-bar.wasm
+ /// ```
+ ///
+ pub fn resolve_wasm_bytes(&self, plugin_dir: &Path) -> Option<Vec<u8>> {
+ fs::read(&self.path)
+ .or_else(|_| fs::read(&self.path.with_extension("wasm")))
+ .or_else(|_| fs::read(plugin_dir.join(&self.path).with_extension("wasm")))
+ .ok()
+ }
+
+ /// Sets the tab index inside of the plugin type of the run field.
+ pub fn set_tab_index(&mut self, tab_index: usize) {
+ match self.run {
+ PluginType::Pane(..) => {
+ self.run = PluginType::Pane(Some(tab_index));
+ }
+ PluginType::Headless => {}
+ }
+ }
+}
+
+/// Type of the plugin. Defaults to Pane.
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum PluginType {
+ // TODO: A plugin with output thats cloned across every pane in a tab, or across the entire
+ // application might be useful
+ // Tab
+ // Static
+ /// Starts immediately when Zellij is started and runs without a visible pane
+ Headless,
+ /// Runs once per pane declared inside a layout file
+ Pane(Option<usize>), // tab_index
+}
+
+impl Default for PluginType {
+ fn default() -> Self {
+ Self::Pane(None)
+ }
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
+pub struct PluginConfigFromYaml {
+ pub path: PathBuf,
+ pub tag: PluginTag,
+ #[serde(default)]
+ pub run: PluginTypeFromYaml,
+ #[serde(default)]
+ pub config: serde_yaml::Value,
+ #[serde(default)]
+ pub _allow_exec_host_cmd: bool,
+}
+
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")