summaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-07-25 10:04:12 +0200
committerGitHub <noreply@github.com>2023-07-25 10:04:12 +0200
commitc95d0e769f31b21f5e2d4aaf6465468344f1bfd6 (patch)
tree9589f0875b91b73460b807e90817907bf3d7d8c6 /zellij-utils
parent6cf795a7df6c83b65a4535b6af0338b4a0b1742f (diff)
feat(plugins): make plugins configurable (#2646)
* work * make every plugin entry point configurable * make integration tests pass * make e2e tests pass * add test for plugin configuration * add test snapshot * add plugin config parsing test * cleanups * style(fmt): rustfmt * style(comment): remove commented code
Diffstat (limited to 'zellij-utils')
-rw-r--r--zellij-utils/src/cli.rs8
-rw-r--r--zellij-utils/src/input/actions.rs29
-rw-r--r--zellij-utils/src/input/config.rs4
-rw-r--r--zellij-utils/src/input/layout.rs29
-rw-r--r--zellij-utils/src/input/plugins.rs6
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs25
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap20
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs65
-rw-r--r--zellij-utils/src/kdl/mod.rs107
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap49
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap13
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap13
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap13
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap16
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap13
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap13
16 files changed, 371 insertions, 52 deletions
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 6639f810b..fd2bdcda7 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -2,7 +2,7 @@ use crate::data::{Direction, InputMode, Resize};
use crate::setup::Setup;
use crate::{
consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV},
- input::options::CliOptions,
+ input::{layout::PluginUserConfiguration, options::CliOptions},
};
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
@@ -292,6 +292,8 @@ pub enum CliAction {
requires("command")
)]
start_suspended: bool,
+ #[clap(short, long, value_parser)]
+ configuration: Option<PluginUserConfiguration>,
},
/// Open the specified file in a new zellij pane with your default EDITOR
Edit {
@@ -376,10 +378,14 @@ pub enum CliAction {
QueryTabNames,
StartOrReloadPlugin {
url: String,
+ #[clap(short, long, value_parser)]
+ configuration: Option<PluginUserConfiguration>,
},
LaunchOrFocusPlugin {
#[clap(short, long, value_parser)]
floating: bool,
url: Url,
+ #[clap(short, long, value_parser)]
+ configuration: Option<PluginUserConfiguration>,
},
}
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index c2071846e..568c0a2c2 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -230,8 +230,8 @@ pub enum Action {
/// Query all tab names
QueryTabNames,
/// Open a new tiled (embedded, non-floating) plugin pane
- NewTiledPluginPane(RunPluginLocation, Option<String>), // String is an optional name
- NewFloatingPluginPane(RunPluginLocation, Option<String>), // String is an optional name
+ NewTiledPluginPane(RunPlugin, Option<String>), // String is an optional name
+ NewFloatingPluginPane(RunPlugin, Option<String>), // String is an optional name
StartOrReloadPlugin(RunPlugin),
CloseTerminalPane(u32),
ClosePluginPane(u32),
@@ -292,21 +292,24 @@ impl Action {
name,
close_on_exit,
start_suspended,
+ configuration,
} => {
let current_dir = get_current_dir();
let cwd = cwd
.map(|cwd| current_dir.join(cwd))
.or_else(|| Some(current_dir));
+ let user_configuration = configuration.unwrap_or_default();
if let Some(plugin) = plugin {
+ let location = RunPluginLocation::parse(&plugin, cwd)
+ .map_err(|e| format!("Failed to parse plugin loction {plugin}: {}", e))?;
+ let plugin = RunPlugin {
+ _allow_exec_host_cmd: false,
+ location,
+ configuration: user_configuration,
+ };
if floating {
- let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
- format!("Failed to parse plugin loction {plugin}: {}", e)
- })?;
Ok(vec![Action::NewFloatingPluginPane(plugin, name)])
} else {
- let plugin = RunPluginLocation::parse(&plugin, cwd).map_err(|e| {
- format!("Failed to parse plugin location {plugin}: {}", e)
- })?;
// it is intentional that a new tiled plugin pane cannot include a
// direction
// this is because the cli client opening a tiled plugin pane is a
@@ -480,23 +483,29 @@ impl Action {
CliAction::PreviousSwapLayout => Ok(vec![Action::PreviousSwapLayout]),
CliAction::NextSwapLayout => Ok(vec![Action::NextSwapLayout]),
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
- CliAction::StartOrReloadPlugin { url } => {
+ CliAction::StartOrReloadPlugin { url, configuration } => {
let current_dir = get_current_dir();
let run_plugin_location = RunPluginLocation::parse(&url, Some(current_dir))
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
+ configuration: configuration.unwrap_or_default(),
};
Ok(vec![Action::StartOrReloadPlugin(run_plugin)])
},
- CliAction::LaunchOrFocusPlugin { url, floating } => {
+ CliAction::LaunchOrFocusPlugin {
+ url,
+ floating,
+ configuration,
+ } => {
let current_dir = get_current_dir();
let run_plugin_location = RunPluginLocation::parse(url.as_str(), Some(current_dir))
.map_err(|e| format!("Failed to parse plugin location: {}", e))?;
let run_plugin = RunPlugin {
location: run_plugin_location,
_allow_exec_host_cmd: false,
+ configuration: configuration.unwrap_or_default(),
};
Ok(vec![Action::LaunchOrFocusPlugin(run_plugin, floating)])
},
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index 4ad4ef89d..55170aec2 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -600,6 +600,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
_allow_exec_host_cmd: false,
+ userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@@ -609,6 +610,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
_allow_exec_host_cmd: false,
+ userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@@ -618,6 +620,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("strider")),
_allow_exec_host_cmd: true,
+ userspace_configuration: Default::default(),
},
);
expected_plugin_configuration.insert(
@@ -627,6 +630,7 @@ mod config_test {
run: PluginType::Pane(None),
location: RunPluginLocation::Zellij(PluginTag::new("compact-bar")),
_allow_exec_host_cmd: false,
+ userspace_configuration: Default::default(),
},
);
assert_eq!(
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index c1d95a125..55e5fc69f 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -212,6 +212,35 @@ pub struct RunPlugin {
#[serde(default)]
pub _allow_exec_host_cmd: bool,
pub location: RunPluginLocation,
+ pub configuration: PluginUserConfiguration,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub struct PluginUserConfiguration(BTreeMap<String, String>);
+
+impl PluginUserConfiguration {
+ pub fn new(configuration: BTreeMap<String, String>) -> Self {
+ PluginUserConfiguration(configuration)
+ }
+ pub fn inner(&self) -> &BTreeMap<String, String> {
+ &self.0
+ }
+}
+
+impl FromStr for PluginUserConfiguration {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let mut ret = BTreeMap::new();
+ let configs = s.split(',');
+ for config in configs {
+ let mut config = config.split('=');
+ let key = config.next().ok_or("invalid configuration key")?.to_owned();
+ let value = config.map(|c| c.to_owned()).collect::<Vec<_>>().join("=");
+ ret.insert(key, value);
+ }
+ Ok(PluginUserConfiguration(ret))
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index 47cc3aa74..f12aba2a2 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -8,7 +8,7 @@ use thiserror::Error;
use serde::{Deserialize, Serialize};
use url::Url;
-use super::layout::{RunPlugin, RunPluginLocation};
+use super::layout::{PluginUserConfiguration, RunPlugin, RunPluginLocation};
#[cfg(not(target_family = "wasm"))]
use crate::consts::ASSET_MAP;
pub use crate::data::PluginTag;
@@ -48,9 +48,11 @@ impl PluginsConfig {
run: PluginType::Pane(None),
_allow_exec_host_cmd: run._allow_exec_host_cmd,
location: run.location.clone(),
+ userspace_configuration: run.configuration.clone(),
}),
RunPluginLocation::Zellij(tag) => self.0.get(tag).cloned().map(|plugin| PluginConfig {
_allow_exec_host_cmd: run._allow_exec_host_cmd,
+ userspace_configuration: run.configuration.clone(),
..plugin
}),
}
@@ -86,6 +88,8 @@ pub struct PluginConfig {
pub _allow_exec_host_cmd: bool,
/// Original location of the
pub location: RunPluginLocation,
+ /// Custom configuration for this plugin
+ pub userspace_configuration: PluginUserConfiguration,
}
impl PluginConfig {
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index fee88aae4..038161a49 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -504,9 +504,18 @@ fn layout_with_plugin_panes() {
pane {
plugin location="file:/path/to/my/plugin.wasm"
}
+ pane {
+ plugin location="zellij:status-bar" {
+ config_key_1 "config_value_1"
+ "2" true
+ }
+ }
}
"#;
let layout = Layout::from_kdl(kdl_layout, "layout_file_name".into(), None, None).unwrap();
+ let mut expected_plugin_configuration = BTreeMap::new();
+ expected_plugin_configuration.insert("config_key_1".to_owned(), "config_value_1".to_owned());
+ expected_plugin_configuration.insert("2".to_owned(), "true".to_owned());
let expected_layout = Layout {
template: Some((
TiledPaneLayout {
@@ -515,6 +524,7 @@ fn layout_with_plugin_panes() {
run: Some(Run::Plugin(RunPlugin {
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
_allow_exec_host_cmd: false,
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -524,6 +534,15 @@ fn layout_with_plugin_panes() {
"/path/to/my/plugin.wasm",
)),
_allow_exec_host_cmd: false,
+ configuration: Default::default(),
+ })),
+ ..Default::default()
+ },
+ TiledPaneLayout {
+ run: Some(Run::Plugin(RunPlugin {
+ location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
+ _allow_exec_host_cmd: false,
+ configuration: PluginUserConfiguration(expected_plugin_configuration),
})),
..Default::default()
},
@@ -2016,6 +2035,7 @@ fn run_plugin_location_parsing() {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -2025,6 +2045,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"/path/to/my/plugin.wasm",
)),
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -2032,6 +2053,7 @@ fn run_plugin_location_parsing() {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("plugin.wasm")),
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -2041,6 +2063,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"relative/with space/plugin.wasm",
)),
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -2050,6 +2073,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"/absolute/with space/plugin.wasm",
)),
+ configuration: Default::default(),
})),
..Default::default()
},
@@ -2059,6 +2083,7 @@ fn run_plugin_location_parsing() {
location: RunPluginLocation::File(PathBuf::from(
"c:/absolute/windows/plugin.wasm",
)),
+ configuration: Default::default(),
})),
..Default::default()
},
diff --git a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap
index 98d4fef5a..e9f849b7f 100644
--- a/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap
+++ b/zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap
@@ -1,6 +1,6 @@
---
source: zellij-utils/src/input/./unit/layout_test.rs
-assertion_line: 1850
+assertion_line: 1863
expression: "format!(\"{:#?}\", layout)"
---
Layout {
@@ -66,6 +66,9 @@ Layout {
"tab-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
@@ -150,6 +153,9 @@ Layout {
"status-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
@@ -194,6 +200,9 @@ Layout {
"tab-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
@@ -331,6 +340,9 @@ Layout {
"status-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
@@ -375,6 +387,9 @@ Layout {
"tab-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
@@ -578,6 +593,9 @@ Layout {
"status-bar",
),
),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
},
),
),
diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs
index a5cab4b1b..9cee47988 100644
--- a/zellij-utils/src/kdl/kdl_layout_parser.rs
+++ b/zellij-utils/src/kdl/kdl_layout_parser.rs
@@ -2,9 +2,9 @@ use crate::input::{
command::RunCommand,
config::ConfigError,
layout::{
- FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, Run, RunPlugin,
- RunPluginLocation, SplitDirection, SplitSize, SwapFloatingLayout, SwapTiledLayout,
- TiledPaneLayout,
+ FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, PluginUserConfiguration, Run,
+ RunPlugin, RunPluginLocation, SplitDirection, SplitSize, SwapFloatingLayout,
+ SwapTiledLayout, TiledPaneLayout,
},
};
@@ -14,7 +14,8 @@ use std::collections::{BTreeMap, HashMap, HashSet};
use std::str::FromStr;
use crate::{
- kdl_child_with_name, kdl_children_nodes, kdl_get_bool_property_or_child_value,
+ kdl_child_with_name, kdl_children_nodes, kdl_first_entry_as_bool, kdl_first_entry_as_i64,
+ kdl_first_entry_as_string, kdl_get_bool_property_or_child_value,
kdl_get_bool_property_or_child_value_with_error, kdl_get_child,
kdl_get_int_property_or_child_value, kdl_get_property_or_child,
kdl_get_string_property_or_child_value, kdl_get_string_property_or_child_value_with_error,
@@ -121,6 +122,11 @@ impl<'a> KdlLayoutParser<'a> {
|| property_name == "min_panes"
|| property_name == "exact_panes"
}
+ pub fn is_a_reserved_plugin_property(property_name: &str) -> bool {
+ property_name == "location"
+ || property_name == "_allow_exec_host_cmd"
+ || property_name == "path"
+ }
fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> {
if name.contains(char::is_whitespace) {
Err(ConfigError::new_layout_kdl_error(
@@ -305,11 +311,62 @@ impl<'a> KdlLayoutParser<'a> {
url_node.span().len(),
)
})?;
+ let configuration = KdlLayoutParser::parse_plugin_user_configuration(&plugin_block)?;
Ok(Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd,
location,
+ configuration,
})))
}
+ pub fn parse_plugin_user_configuration(
+ plugin_block: &KdlNode,
+ ) -> Result<PluginUserConfiguration, ConfigError> {
+ let mut configuration = BTreeMap::new();
+ for user_configuration_entry in plugin_block.entries() {
+ let name = user_configuration_entry.name();
+ let value = user_configuration_entry.value();
+ if let Some(name) = name {
+ let name = name.to_string();
+ if KdlLayoutParser::is_a_reserved_plugin_property(&name) {
+ continue;
+ }
+ configuration.insert(name, value.to_string());
+ }
+ // we ignore "bare" (eg. `plugin i_am_a_bare_true_argument { arg_one 1; }`) entries
+ // to prevent diverging behaviour with the keybindings config
+ }
+ if let Some(user_config) = kdl_children_nodes!(plugin_block) {
+ for user_configuration_entry in user_config {
+ let config_entry_name = kdl_name!(user_configuration_entry);
+ if KdlLayoutParser::is_a_reserved_plugin_property(&config_entry_name) {
+ continue;
+ }
+ let config_entry_str_value = kdl_first_entry_as_string!(user_configuration_entry)
+ .map(|s| format!("{}", s.to_string()));
+ let config_entry_int_value = kdl_first_entry_as_i64!(user_configuration_entry)
+ .map(|s| format!("{}", s.to_string()));
+ let config_entry_bool_value = kdl_first_entry_as_bool!(user_configuration_entry)
+ .map(|s| format!("{}", s.to_string()));
+ let config_entry_children = user_configuration_entry
+ .children()
+ .map(|s| format!("{}", s.to_string().trim()));
+ let config_entry_value = config_entry_str_value
+ .or(config_entry_int_value)
+ .or(config_entry_bool_value)
+ .or(config_entry_children)
+ .ok_or(ConfigError::new_kdl_error(
+ format!(
+ "Failed to parse plugin block configuration: {:?}",
+ user_configuration_entry
+ ),
+ plugin_block.span().offset(),
+ plugin_block.span().len(),
+ ))?;
+ configuration.insert(config_entry_name.into(), config_entry_value);
+ }
+ }
+ Ok(PluginUserConfiguration::new(configuration))
+ }
fn parse_args(&self, pane_node: &KdlNode) -> Result<Option<Vec<String>>, ConfigError> {
match kdl_get_child!(pane_node, "args") {
Some(kdl_args) => {
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index a75292900..341415c1c 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -3,13 +3,13 @@ use crate::data::{Direction, InputMode, Key, Palette, PaletteColor, Resize};
use crate::envs::EnvironmentVariables;
use crate::input::config::{Config, ConfigError, KdlError};
use crate::input::keybinds::Keybinds;
-use crate::input::layout::{Layout, RunPlugin, RunPluginLocation};
+use crate::input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation};
use crate::input::options::{Clipboard, OnForceClose, Options};
use crate::input::plu