diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-02-26 15:30:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-26 15:30:15 +0100 |
commit | 21273ac95a2fed6b07d8550fe2d4f65993be7037 (patch) | |
tree | f5a7aa488ece8f21c17350bb3ec14ceaf15f60a7 /zellij-utils/src | |
parent | 27bffbf1533b4b2d3c10b1305557c75ddd121374 (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')
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 |