summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-02-26 15:30:15 +0100
committerGitHub <noreply@github.com>2024-02-26 15:30:15 +0100
commit21273ac95a2fed6b07d8550fe2d4f65993be7037 (patch)
treef5a7aa488ece8f21c17350bb3ec14ceaf15f60a7 /zellij-utils/src
parent27bffbf1533b4b2d3c10b1305557c75ddd121374 (diff)
feat(plugins): introduce plugin aliases (#3157)
* working prototype with passing tests * new tests and passing plugin tests as well * style(code): cleanups * cleanup strider from unused search feature * prototype of removing old plugin block from the config * aliases working from config file and all tests passing * fixups and cleanups * use aliases in layouts * update test snapshot * style(fmt): rustfmt
Diffstat (limited to 'zellij-utils/src')
-rw-r--r--zellij-utils/src/cli.rs2
-rw-r--r--zellij-utils/src/input/actions.rs82
-rw-r--r--zellij-utils/src/input/config.rs86
-rw-r--r--zellij-utils/src/input/layout.rs196
-rw-r--r--zellij-utils/src/input/plugins.rs113
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs47
-rw-r--r--zellij-utils/src/input/unit/snapshots/zellij_utils__input__layout__layout_test__can_load_swap_layouts_from_a_different_file.snap134
-rw-r--r--zellij-utils/src/ipc.rs4
-rw-r--r--zellij-utils/src/kdl/kdl_layout_parser.rs31
-rw-r--r--zellij-utils/src/kdl/mod.rs99
-rw-r--r--zellij-utils/src/plugin_api/action.rs104
-rw-r--r--zellij-utils/src/session_serialization.rs31
-rw-r--r--zellij-utils/src/setup.rs14
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-2.snap322
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap190
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap190
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap168
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap24
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap190
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap190
20 files changed, 1194 insertions, 1023 deletions
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs
index 595293f4e..e5b65d394 100644
--- a/zellij-utils/src/cli.rs
+++ b/zellij-utils/src/cli.rs
@@ -627,7 +627,7 @@ pub enum CliAction {
in_place: bool,
#[clap(short, long, value_parser)]
move_to_focused_tab: bool,
- url: Url,
+ url: String,
#[clap(short, long, value_parser)]
configuration: Option<PluginUserConfiguration>,
#[clap(short, long, value_parser)]
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index b19529e48..18ed0a8da 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -2,8 +2,8 @@
use super::command::RunCommandAction;
use super::layout::{
- FloatingPaneLayout, Layout, RunPlugin, RunPluginLocation, SwapFloatingLayout, SwapTiledLayout,
- TiledPaneLayout,
+ FloatingPaneLayout, Layout, PluginAlias, RunPlugin, RunPluginLocation, RunPluginOrAlias,
+ SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
};
use crate::cli::CliAction;
use crate::data::{Direction, Resize};
@@ -217,9 +217,9 @@ pub enum Action {
LeftClick(Position),
RightClick(Position),
MiddleClick(Position),
- LaunchOrFocusPlugin(RunPlugin, bool, bool, bool, bool), // bools => should float,
+ LaunchOrFocusPlugin(RunPluginOrAlias, bool, bool, bool, bool), // bools => should float,
// move_to_focused_tab, should_open_in_place, skip_cache
- LaunchPlugin(RunPlugin, bool, bool, bool, Option<PathBuf>), // bools => should float,
+ LaunchPlugin(RunPluginOrAlias, bool, bool, bool, Option<PathBuf>), // bools => should float,
// should_open_in_place, skip_cache, Option<PathBuf> is cwd
LeftMouseRelease(Position),
RightMouseRelease(Position),
@@ -246,19 +246,19 @@ pub enum Action {
/// Query all tab names
QueryTabNames,
/// Open a new tiled (embedded, non-floating) plugin pane
- NewTiledPluginPane(RunPlugin, Option<String>, bool, Option<PathBuf>), // String is an optional name, bool is
+ NewTiledPluginPane(RunPluginOrAlias, Option<String>, bool, Option<PathBuf>), // String is an optional name, bool is
// skip_cache, Option<PathBuf> is cwd
NewFloatingPluginPane(
- RunPlugin,
+ RunPluginOrAlias,
Option<String>,
bool,
Option<PathBuf>,
Option<FloatingPaneCoordinates>,
), // String is an optional name, bool is
// skip_cache, Option<PathBuf> is cwd
- NewInPlacePluginPane(RunPlugin, Option<String>, bool), // String is an optional name, bool is
+ NewInPlacePluginPane(RunPluginOrAlias, Option<String>, bool), // String is an optional name, bool is
// skip_cache
- StartOrReloadPlugin(RunPlugin),
+ StartOrReloadPlugin(RunPluginOrAlias),
CloseTerminalPane(u32),
ClosePluginPane(u32),
FocusTerminalPaneWithId(u32, bool), // bool is should_float_if_hidden
@@ -351,14 +351,20 @@ impl Action {
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.clone())
- .map_err(|e| format!("Failed to parse plugin loction {plugin}: {}", e))?;
- let plugin = RunPlugin {
- _allow_exec_host_cmd: false,
- location,
- configuration: user_configuration,
+ let plugin = match RunPluginLocation::parse(&plugin, cwd.clone()) {
+ Ok(location) => {
+ let user_configuration = configuration.unwrap_or_default();
+ RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location,
+ configuration: user_configuration,
+ })
+ },
+ Err(_) => RunPluginOrAlias::Alias(PluginAlias::new(
+ &plugin,
+ &configuration.map(|c| c.inner().clone()),
+ )),
};
if floating {
Ok(vec![Action::NewFloatingPluginPane(
@@ -571,14 +577,13 @@ impl Action {
CliAction::QueryTabNames => Ok(vec![Action::QueryTabNames]),
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)])
+ let run_plugin_or_alias = RunPluginOrAlias::from_url(
+ &url,
+ &configuration.map(|c| c.inner().clone()),
+ None,
+ Some(current_dir),
+ )?;
+ Ok(vec![Action::StartOrReloadPlugin(run_plugin_or_alias)])
},
CliAction::LaunchOrFocusPlugin {
url,
@@ -589,15 +594,14 @@ impl Action {
skip_plugin_cache,
} => {
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(),
- };
+ let run_plugin_or_alias = RunPluginOrAlias::from_url(
+ url.as_str(),
+ &configuration.map(|c| c.inner().clone()),
+ None,
+ Some(current_dir),
+ )?;
Ok(vec![Action::LaunchOrFocusPlugin(
- run_plugin,
+ run_plugin_or_alias,
floating,
move_to_focused_tab,
in_place,
@@ -612,16 +616,14 @@ impl Action {
skip_plugin_cache,
} => {
let current_dir = get_current_dir();
- let run_plugin_location =
- RunPluginLocation::parse(url.as_str(), Some(current_dir.clone()))
- .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(),
- };
+ let run_plugin_or_alias = RunPluginOrAlias::from_url(
+ &url.as_str(),
+ &configuration.map(|c| c.inner().clone()),
+ None,
+ Some(current_dir.clone()),
+ )?;
Ok(vec![Action::LaunchPlugin(
- run_plugin,
+ run_plugin_or_alias,
floating,
in_place,
skip_plugin_cache,
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index eb8fe521d..cf6620a9c 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -9,7 +9,7 @@ use std::convert::TryFrom;
use super::keybinds::Keybinds;
use super::options::Options;
-use super::plugins::{PluginsConfig, PluginsConfigError};
+use super::plugins::{PluginAliases, PluginsConfigError};
use super::theme::{Themes, UiConfig};
use crate::cli::{CliArgs, Command};
use crate::envs::EnvironmentVariables;
@@ -25,7 +25,7 @@ pub struct Config {
pub keybinds: Keybinds,
pub options: Options,
pub themes: Themes,
- pub plugins: PluginsConfig,
+ pub plugins: PluginAliases,
pub ui: UiConfig,
pub env: EnvironmentVariables,
}
@@ -226,7 +226,7 @@ impl Config {
self.options = self.options.merge(other.options);
self.keybinds.merge(other.keybinds.clone());
self.themes = self.themes.merge(other.themes);
- self.plugins = self.plugins.merge(other.plugins);
+ self.plugins.merge(other.plugins);
self.ui = self.ui.merge(other.ui);
self.env = self.env.merge(other.env);
Ok(())
@@ -237,11 +237,11 @@ impl Config {
mod config_test {
use super::*;
use crate::data::{InputMode, Palette, PaletteColor, PluginTag};
- use crate::input::layout::RunPluginLocation;
+ use crate::input::layout::{RunPlugin, RunPluginLocation};
use crate::input::options::{Clipboard, OnForceClose};
- use crate::input::plugins::{PluginConfig, PluginType, PluginsConfig};
+ use crate::input::plugins::{PluginConfig, PluginType};
use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig};
- use std::collections::HashMap;
+ use std::collections::{BTreeMap, HashMap};
use std::io::Write;
use tempfile::tempdir;
@@ -591,60 +591,54 @@ mod config_test {
fn can_define_plugin_configuration_in_configfile() {
let config_contents = r#"
plugins {
- tab-bar { path "tab-bar"; }
- status-bar { path "status-bar"; }
- strider {
- path "strider"
- _allow_exec_host_cmd true
+ tab-bar location="zellij:tab-bar"
+ status-bar location="zellij:status-bar"
+ strider location="zellij:strider"
+ compact-bar location="zellij:compact-bar"
+ session-manager location="zellij:session-manager"
+ welcome-screen location="zellij:session-manager" {
+ welcome_screen true
}
- compact-bar { path "compact-bar"; }
+ filepicker location="zellij:strider"
}
"#;
let config = Config::from_kdl(config_contents, None).unwrap();
- let mut expected_plugin_configuration = HashMap::new();
+ let mut expected_plugin_configuration = BTreeMap::new();
expected_plugin_configuration.insert(
- PluginTag::new("tab-bar"),
- PluginConfig {
- path: PathBuf::from("tab-bar"),
- run: PluginType::Pane(None),
- location: RunPluginLocation::Zellij(PluginTag::new("tab-bar")),
- _allow_exec_host_cmd: false,
- userspace_configuration: Default::default(),
- },
+ "tab-bar".to_owned(),
+ RunPlugin::from_url("zellij:tab-bar").unwrap(),
);
expected_plugin_configuration.insert(
- PluginTag::new("status-bar"),
- PluginConfig {
- path: PathBuf::from("status-bar"),
- run: PluginType::Pane(None),
- location: RunPluginLocation::Zellij(PluginTag::new("status-bar")),
- _allow_exec_host_cmd: false,
- userspace_configuration: Default::default(),
- },
+ "status-bar".to_owned(),
+ RunPlugin::from_url("zellij:status-bar").unwrap(),
);
expected_plugin_configuration.insert(
- PluginTag::new("strider"),
- PluginConfig {
- path: PathBuf::from("strider"),
- run: PluginType::Pane(None),
- location: RunPluginLocation::Zellij(PluginTag::new("strider")),
- _allow_exec_host_cmd: true,
- userspace_configuration: Default::default(),
- },
+ "strider".to_owned(),
+ RunPlugin::from_url("zellij:strider").unwrap(),
);
expected_plugin_configuration.insert(
- PluginTag::new("compact-bar"),
- PluginConfig {
- path: PathBuf::from("compact-bar"),
- run: PluginType::Pane(None),
- location: RunPluginLocation::Zellij(PluginTag::new("compact-bar")),
- _allow_exec_host_cmd: false,
- userspace_configuration: Default::default(),
- },
+ "compact-bar".to_owned(),
+ RunPlugin::from_url("zellij:compact-bar").unwrap(),
+ );
+ expected_plugin_configuration.insert(
+ "session-manager".to_owned(),
+ RunPlugin::from_url("zellij:session-manager").unwrap(),
+ );
+ let mut welcome_screen_configuration = BTreeMap::new();
+ welcome_screen_configuration.insert("welcome_screen".to_owned(), "true".to_owned());
+ expected_plugin_configuration.insert(
+ "welcome-screen".to_owned(),
+ RunPlugin::from_url("zellij:session-manager")
+ .unwrap()
+ .with_configuration(welcome_screen_configuration),
+ );
+ expected_plugin_configuration.insert(
+ "filepicker".to_owned(),
+ RunPlugin::from_url("zellij:strider").unwrap(),
);
assert_eq!(
config.plugins,
- PluginsConfig::from_data(expected_plugin_configuration),
+ PluginAliases::from_data(expected_plugin_configuration),
"Plugins defined in config"
);
}
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 3dfadb402..ad2fca896 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -23,7 +23,7 @@ use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
-use super::plugins::{PluginTag, PluginsConfigError};
+use super::plugins::{PluginAliases, PluginTag, PluginsConfigError};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::vec::Vec;
@@ -80,10 +80,122 @@ impl SplitSize {
}
}
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
+pub enum RunPluginOrAlias {
+ RunPlugin(RunPlugin),
+ Alias(PluginAlias),
+}
+
+impl Default for RunPluginOrAlias {
+ fn default() -> Self {
+ RunPluginOrAlias::RunPlugin(Default::default())
+ }
+}
+
+impl RunPluginOrAlias {
+ pub fn location_string(&self) -> String {
+ match self {
+ RunPluginOrAlias::RunPlugin(run_plugin) => run_plugin.location.display(),
+ RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.name.clone(),
+ }
+ }
+ pub fn populate_run_plugin_if_needed(&mut self, plugin_aliases: &PluginAliases) {
+ if let RunPluginOrAlias::Alias(run_plugin_alias) = self {
+ if run_plugin_alias.run_plugin.is_some() {
+ log::warn!("Overriding plugin alias");
+ }
+ let merged_run_plugin = plugin_aliases
+ .aliases
+ .get(run_plugin_alias.name.as_str())
+ .map(|r| {
+ r.clone().merge_configuration(
+ &run_plugin_alias
+ .configuration
+ .as_ref()
+ .map(|c| c.inner().clone()),
+ )
+ });
+ run_plugin_alias.run_plugin = merged_run_plugin;
+ }
+ }
+ pub fn get_run_plugin(&self) -> Option<RunPlugin> {
+ match self {
+ RunPluginOrAlias::RunPlugin(run_plugin) => Some(run_plugin.clone()),
+ RunPluginOrAlias::Alias(plugin_alias) => plugin_alias.run_plugin.clone(),
+ }
+ }
+ pub fn get_configuration(&self) -> Option<PluginUserConfiguration> {
+ self.get_run_plugin().map(|r| r.configuration.clone())
+ }
+ pub fn from_url(
+ url: &str,
+ configuration: &Option<BTreeMap<String, String>>,
+ alias_dict: Option<&PluginAliases>,
+ cwd: Option<PathBuf>,
+ ) -> Result<Self, String> {
+ match RunPluginLocation::parse(&url, cwd) {
+ Ok(location) => Ok(RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location,
+ configuration: configuration
+ .as_ref()
+ .map(|c| PluginUserConfiguration::new(c.clone()))
+ .unwrap_or_default(),
+ })),
+ Err(PluginsConfigError::InvalidUrlScheme(_))
+ | Err(PluginsConfigError::InvalidUrl(..)) => {
+ let mut plugin_alias = PluginAlias::new(&url, configuration);
+ if let Some(alias_dict) = alias_dict {
+ plugin_alias.run_plugin = alias_dict
+ .aliases
+ .get(url)
+ .map(|r| r.clone().merge_configuration(configuration));
+ }
+ Ok(RunPluginOrAlias::Alias(plugin_alias))
+ },
+ Err(e) => {
+ return Err(format!("Failed to parse plugin location {url}: {}", e));
+ },
+ }
+ }
+ pub fn is_equivalent_to_run(&self, run: &Option<Run>) -> bool {
+ match (self, run) {
+ (
+ RunPluginOrAlias::Alias(self_alias),
+ Some(Run::Plugin(RunPluginOrAlias::Alias(run_alias))),
+ ) => {
+ self_alias.name == run_alias.name
+ && self_alias
+ .configuration
+ .as_ref()
+ // we do the is_empty() checks because an empty configuration is the same as no
+ // configuration (i.e. None)
+ .and_then(|c| if c.inner().is_empty() { None } else { Some(c) })
+ == run_alias.configuration.as_ref().and_then(|c| {
+ if c.inner().is_empty() {
+ None
+ } else {
+ Some(c)
+ }
+ })
+ },
+ (
+ RunPluginOrAlias::Alias(self_alias),
+ Some(Run::Plugin(RunPluginOrAlias::RunPlugin(other_run_plugin))),
+ ) => self_alias.run_plugin.as_ref() == Some(other_run_plugin),
+ (
+ RunPluginOrAlias::RunPlugin(self_run_plugin),
+ Some(Run::Plugin(RunPluginOrAlias::RunPlugin(other_run_plugin))),
+ ) => self_run_plugin == other_run_plugin,
+ _ => false,
+ }
+ }
+}
+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Run {
#[serde(rename = "plugin")]
- Plugin(RunPlugin),
+ Plugin(RunPluginOrAlias),
#[serde(rename = "command")]
Command(RunCommand),
EditFile(PathBuf, Option<usize>, Option<PathBuf>), // TODO: merge this with TerminalAction::OpenFile
@@ -227,6 +339,23 @@ impl Run {
Run::Cwd(cwd) => Some(cwd.clone()),
}
}
+ pub fn get_run_plugin(&self) -> Option<RunPlugin> {
+ match self {
+ Run::Plugin(RunPluginOrAlias::RunPlugin(run_plugin)) => Some(run_plugin.clone()),
+ Run::Plugin(RunPluginOrAlias::Alias(plugin_alias)) => {
+ plugin_alias.run_plugin.as_ref().map(|r| r.clone())
+ },
+ _ => None,
+ }
+ }
+ pub fn populate_run_plugin_if_needed(&mut self, alias_dict: &PluginAliases) {
+ match self {
+ Run::Plugin(run_plugin_alias) => {
+ run_plugin_alias.populate_run_plugin_if_needed(alias_dict)
+ },
+ _ => {},
+ }
+ }
}
#[allow(clippy::derive_hash_xor_eq)]
@@ -246,6 +375,35 @@ impl RunPlugin {
..Default::default()
})
}
+ pub fn with_configuration(mut self, configuration: BTreeMap<String, String>) -> Self {
+ self.configuration = PluginUserConfiguration::new(configuration);
+ self
+ }
+ pub fn merge_configuration(mut self, configuration: &Option<BTreeMap<String, String>>) -> Self {
+ if let Some(configuration) = configuration {
+ self.configuration.merge(configuration);
+ }
+ self
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Hash, Default, PartialEq, Eq)]
+pub struct PluginAlias {
+ pub name: String,
+ pub configuration: Option<PluginUserConfiguration>,
+ pub run_plugin: Option<RunPlugin>,
+}
+
+impl PluginAlias {
+ pub fn new(name: &str, configuration: &Option<BTreeMap<String, String>>) -> Self {
+ PluginAlias {
+ name: name.to_owned(),
+ configuration: configuration
+ .as_ref()
+ .map(|c| PluginUserConfiguration::new(c.clone())),
+ ..Default::default()
+ }
+ }
}
#[allow(clippy::derive_hash_xor_eq)]
@@ -280,6 +438,11 @@ impl PluginUserConfiguration {
pub fn insert(&mut self, config_key: impl Into<String>, config_value: impl Into<String>) {
self.0.insert(config_key.into(), config_value.into());
}
+ pub fn merge(&mut self, other_config: &BTreeMap<Strin