diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-03-21 16:52:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-21 16:52:15 +0100 |
commit | 07b76ee610deb3a37082cf9a650f443fa579aa07 (patch) | |
tree | ff3d78914fbfb02001a38f2809aeb6c2619ec66d | |
parent | 8504881e4eeaa0dbc53203351c9529aed1068249 (diff) |
feat(pipes): allow piping messages to plugins from keybindings (#3212)
-rw-r--r-- | default-plugins/fixture-plugin-for-tests/src/main.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/plugins/mod.rs | 67 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 45 | ||||
-rw-r--r-- | zellij-utils/assets/prost/api.action.rs | 3 | ||||
-rw-r--r-- | zellij-utils/assets/prost/api.pipe_message.rs | 3 | ||||
-rw-r--r-- | zellij-utils/src/data.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/input/actions.rs | 13 | ||||
-rw-r--r-- | zellij-utils/src/kdl/mod.rs | 61 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/action.proto | 1 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 50 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/pipe_message.proto | 1 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/pipe_message.rs | 2 |
13 files changed, 227 insertions, 22 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index c241281a1..e9555dacb 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -339,6 +339,7 @@ impl ZellijPlugin for State { let input_pipe_id = match pipe_message.source { PipeSource::Cli(id) => id.clone(), PipeSource::Plugin(id) => format!("{}", id), + PipeSource::Keybind => format!("keybind"), }; let name = pipe_message.name; let payload = pipe_message.payload; diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 1f2f2be7c..44e88ae0f 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -119,6 +119,19 @@ pub enum PluginInstruction { skip_cache: bool, cli_client_id: ClientId, }, + KeybindPipe { + name: String, + payload: Option<String>, + plugin: Option<String>, + args: Option<BTreeMap<String, String>>, + configuration: Option<BTreeMap<String, String>>, + floating: Option<bool>, + pane_id_to_replace: Option<PaneId>, + pane_title: Option<String>, + cwd: Option<PathBuf>, + skip_cache: bool, + cli_client_id: ClientId, + }, CachePluginEvents { plugin_id: PluginId, }, @@ -164,6 +177,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::MessageFromPlugin { .. } => PluginContext::MessageFromPlugin, PluginInstruction::UnblockCliPipes { .. } => PluginContext::UnblockCliPipes, PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem, + PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe, } } } @@ -429,7 +443,7 @@ pub(crate) fn plugin_thread_main( )]; wasm_bridge.update_plugins(updates, shutdown_send.clone())?; }, - PluginInstruction::PluginSubscribedToEvents(_plugin_id, _client_id, events) => { + PluginInstruction::PluginSubscribedToEvents(_plugin_id, _client_id, _events) => { // no-op, there used to be stuff we did here - now there isn't, but we might want // to add stuff here in the future }, @@ -529,6 +543,57 @@ pub(crate) fn plugin_thread_main( } wasm_bridge.pipe_messages(pipe_messages, shutdown_send.clone())?; }, + PluginInstruction::KeybindPipe { + name, + payload, + plugin, + args, + configuration, + floating, + pane_id_to_replace, + pane_title, + cwd, + skip_cache, + cli_client_id, + } => { + let should_float = floating.unwrap_or(true); + let mut pipe_messages = vec![]; + match plugin { + Some(plugin_url) => { + // send to specific plugin(s) + pipe_to_specific_plugins( + PipeSource::Keybind, + &plugin_url, + &configuration, + &cwd, + skip_cache, + should_float, + &pane_id_to_replace, + &pane_title, + Some(cli_client_id), + &mut pipe_messages, + &name, + &payload, + &args, + &bus, + &mut wasm_bridge, + &plugin_aliases, + ); + }, + None => { + // no specific destination, send to all plugins + pipe_to_all_plugins( + PipeSource::Keybind, + &name, + &payload, + &args, + &mut wasm_bridge, + &mut pipe_messages, + ); + }, + } + wasm_bridge.pipe_messages(pipe_messages, shutdown_send.clone())?; + }, PluginInstruction::CachePluginEvents { plugin_id } => { wasm_bridge.cache_plugin_events(plugin_id); }, diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index c8a7f4194..fef5b7542 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -1,4 +1,4 @@ -use std::collections::{HashSet, VecDeque}; +use std::collections::{BTreeMap, HashSet, VecDeque}; use std::sync::{Arc, RwLock}; use crate::thread_bus::ThreadSenders; @@ -10,6 +10,7 @@ use crate::{ screen::ScreenInstruction, ServerInstruction, SessionMetaData, SessionState, }; +use uuid::Uuid; use zellij_utils::{ channels::SenderWithContext, data::{Direction, Event, PluginCapabilities, ResizeStrategy}, @@ -883,6 +884,48 @@ pub(crate) fn route_action( log::error!("Message must have a name"); } }, + Action::KeybindPipe { + mut name, + payload, + plugin, + args, + mut configuration, + floating, + in_place, + skip_cache, + cwd, + pane_title, + launch_new, + .. + } => { + if let Some(name) = name.take() { + let should_open_in_place = in_place.unwrap_or(false); + let pane_id_to_replace = if should_open_in_place { pane_id } else { None }; + if launch_new { + // we do this to make sure the plugin is unique (has a unique configuration parameter) + configuration + .get_or_insert_with(BTreeMap::new) + .insert("_zellij_id".to_owned(), Uuid::new_v4().to_string()); + } + senders + .send_to_plugin(PluginInstruction::KeybindPipe { + name, + payload, + plugin, + args, + configuration, + floating, + pane_id_to_replace, + cwd, + pane_title, + skip_cache, + cli_client_id: client_id, + }) + .with_context(err_context)?; + } else { + log::error!("Message must have a name"); + } + }, } Ok(should_break) } diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index fe2c2144a..651f59ebe 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -454,6 +454,7 @@ pub enum ActionName { LaunchPlugin = 81, CliPipe = 82, MoveTab = 83, + KeybindPipe = 84, } impl ActionName { /// String value of the enum field names used in the ProtoBuf definition. @@ -546,6 +547,7 @@ impl ActionName { ActionName::LaunchPlugin => "LaunchPlugin", ActionName::CliPipe => "CliPipe", ActionName::MoveTab => "MoveTab", + ActionName::KeybindPipe => "KeybindPipe", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -635,6 +637,7 @@ impl ActionName { "LaunchPlugin" => Some(Self::LaunchPlugin), "CliPipe" => Some(Self::CliPipe), "MoveTab" => Some(Self::MoveTab), + "KeybindPipe" => Some(Self::KeybindPipe), _ => None, } } diff --git a/zellij-utils/assets/prost/api.pipe_message.rs b/zellij-utils/assets/prost/api.pipe_message.rs index 96c566ff8..a9987563f 100644 --- a/zellij-utils/assets/prost/api.pipe_message.rs +++ b/zellij-utils/assets/prost/api.pipe_message.rs @@ -29,6 +29,7 @@ pub struct Arg { pub enum PipeSource { Cli = 0, Plugin = 1, + Keybind = 2, } impl PipeSource { /// String value of the enum field names used in the ProtoBuf definition. @@ -39,6 +40,7 @@ impl PipeSource { match self { PipeSource::Cli => "Cli", PipeSource::Plugin => "Plugin", + PipeSource::Keybind => "Keybind", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -46,6 +48,7 @@ impl PipeSource { match value { "Cli" => Some(Self::Cli), "Plugin" => Some(Self::Plugin), + "Keybind" => Some(Self::Keybind), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 9bb0d1f96..998392ff7 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1165,6 +1165,7 @@ pub enum HttpVerb { pub enum PipeSource { Cli(String), // String is the pipe_id of the CLI pipe (used for blocking/unblocking) Plugin(u32), // u32 is the lugin id + Keybind, // TODO: consider including the actual keybind here? } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 9a100e385..c6ef80e68 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -401,6 +401,7 @@ pub enum PluginContext { MessageFromPlugin, UnblockCliPipes, WatchFilesystem, + KeybindPipe, } /// Stack call representations corresponding to the different types of [`ClientInstruction`]s. diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index fdf040300..e72b6b64c 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -284,6 +284,19 @@ pub enum Action { cwd: Option<PathBuf>, pane_title: Option<String>, }, + KeybindPipe { + name: Option<String>, + payload: Option<String>, + args: Option<BTreeMap<String, String>>, + plugin: Option<String>, + configuration: Option<BTreeMap<String, String>>, + launch_new: bool, + skip_cache: bool, + floating: Option<bool>, + in_place: Option<bool>, + cwd: Option<PathBuf>, + pane_title: Option<String>, + }, } impl Action { diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 69abe52a0..d3794d114 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -15,6 +15,7 @@ use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig}; use kdl_layout_parser::KdlLayoutParser; use std::collections::{BTreeMap, HashMap, HashSet}; use strum::IntoEnumIterator; +use uuid::Uuid; use miette::NamedSource; @@ -1078,6 +1079,64 @@ impl TryFrom<(&KdlNode, &Options)> for Action { action_arguments, kdl_action ), + "MessagePlugin" => { + let arguments = action_arguments.iter().copied(); + let mut args = kdl_arguments_that_are_strings(arguments)?; + let plugin_path = if args.is_empty() { + None + } else { + Some(args.remove(0)) + }; + + let command_metadata = action_children.iter().next(); + let launch_new = command_metadata + .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "launch_new")) + .unwrap_or(false); + let skip_cache = command_metadata + .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "skip_cache")) + .unwrap_or(false); + let should_float = command_metadata + .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating")) + .unwrap_or(false); + let name = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "name")) + .map(|n| n.to_owned()); + let payload = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "payload")) + .map(|p| p.to_owned()); + let title = command_metadata + .and_then(|c_m| kdl_child_string_value_for_entry(c_m, "title")) + .map(|t| t.to_owned()); + let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?; + let configuration = if configuration.inner().is_empty() { + None + } else { + Some(configuration.inner().clone()) + }; + let cwd = kdl_get_string_property_or_child_value!(kdl_action, "cwd") + .map(|s| PathBuf::from(s)); + + let name = name + // first we try to take the explicitly supplied message name + // then we use the plugin, to facilitate using aliases + .or_else(|| plugin_path.clone()) + // then we use a uuid to at least have some sort of identifier for this message + .or_else(|| Some(Uuid::new_v4().to_string())); + + Ok(Action::KeybindPipe { + name, + payload, + args: None, // TODO: consider supporting this if there's a need + plugin: plugin_path, + configuration, + launch_new, + skip_cache, + floating: Some(should_float), + in_place: None, // TODO: support this + cwd, + pane_title: title, + }) + }, _ => Err(ConfigError::new_kdl_error( format!("Unsupported action: {}", action_name).into(), kdl_action.span().offset(), @@ -1854,7 +1913,7 @@ impl PluginAliases { { let configuration = KdlLayoutParser::parse_plugin_user_configuration(&alias_definition)?; - let mut initial_cwd = + let initial_cwd = kdl_get_string_property_or_child_value!(alias_definition, "cwd") .map(|s| PathBuf::from(s)); let run_plugin = RunPlugin::from_url(string_url)? diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index d59da4ad6..d78d5e695 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -243,6 +243,7 @@ enum ActionName { LaunchPlugin = 81; CliPipe = 82; MoveTab = 83; + KeybindPipe = 84; } message Position { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 1c35c8548..14d5ad090 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -689,6 +689,23 @@ impl TryFrom<ProtobufAction> for Action { }, _ => Err("Wrong payload for Action::RenameSession"), }, + Some(ProtobufActionName::KeybindPipe) => match protobuf_action.optional_payload { + Some(_) => Err("KeybindPipe should not have a payload"), + // TODO: at some point we might want to support a payload here + None => Ok(Action::KeybindPipe { + name: None, + payload: None, + args: None, + plugin: None, + configuration: None, + launch_new: false, + skip_cache: false, + floating: None, + in_place: None, + cwd: None, + pane_title: None, + }), + }, _ => Err("Unknown Action"), } } @@ -1181,25 +1198,16 @@ impl TryFrom<Action> for ProtobufAction { skip_plugin_cache, _cwd, _coordinates, - ) => { - // let plugin_url: Url = match run_plugin { - // RunPluginOrAlias::RunPlugin(run_plugin) => Url::from(&run_plugin.location), - // RunPluginOrAlias::Alias(plugin_alias) => { - // // TODO: support plugin alias - // unimplemented!() - // } - // }; - Ok(ProtobufAction { - name: ProtobufActionName::NewFloatingPluginPane as i32, - optional_payload: Some(OptionalPayload::NewFloatingPluginPanePayload( - NewPluginPanePayload { - plugin_url: run_plugin.location_string(), - pane_name, - skip_plugin_cache, - }, - )), - }) - }, + ) => Ok(ProtobufAction { + name: ProtobufActionName::NewFloatingPluginPane as i32, + optional_payload: Some(OptionalPayload::NewFloatingPluginPanePayload( + NewPluginPanePayload { + plugin_url: run_plugin.location_string(), + pane_name, + skip_plugin_cache, + }, + )), + }), Action::StartOrReloadPlugin(run_plugin) => Ok(ProtobufAction { name: ProtobufActionName::StartOrReloadPlugin as i32, optional_payload: Some(OptionalPayload::StartOrReloadPluginPayload( @@ -1273,6 +1281,10 @@ impl TryFrom<Action> for ProtobufAction { name: ProtobufActionName::RenameSession as i32, optional_payload: Some(OptionalPayload::RenameSessionPayload(session_name)), }), + Action::KeybindPipe { .. } => Ok(ProtobufAction { + name: ProtobufActionName::KeybindPipe as i32, + optional_payload: None, + }), Action::NoOp | Action::Confirm | Action::NewInPlacePane(..) diff --git a/zellij-utils/src/plugin_api/pipe_message.proto b/zellij-utils/src/plugin_api/pipe_message.proto index 5f488a758..a1d027320 100644 --- a/zellij-utils/src/plugin_api/pipe_message.proto +++ b/zellij-utils/src/plugin_api/pipe_message.proto @@ -15,6 +15,7 @@ message PipeMessage { enum PipeSource { Cli = 0; Plugin = 1; + Keybind = 2; } message Arg { diff --git a/zellij-utils/src/plugin_api/pipe_message.rs b/zellij-utils/src/plugin_api/pipe_message.rs index dbfe49305..b1e32209c 100644 --- a/zellij-utils/src/plugin_api/pipe_message.rs +++ b/zellij-utils/src/plugin_api/pipe_message.rs @@ -19,6 +19,7 @@ impl TryFrom<ProtobufPipeMessage> for PipeMessage { (Some(ProtobufPipeSource::Plugin), _, Some(plugin_source_id)) => { PipeSource::Plugin(plugin_source_id) }, + (Some(ProtobufPipeSource::Keybind), _, _) => PipeSource::Keybind, _ => return Err("Invalid PipeSource or payload"), }; let name = protobuf_pipe_message.name; @@ -49,6 +50,7 @@ impl TryFrom<PipeMessage> for ProtobufPipeMessage { PipeSource::Plugin(plugin_id) => { (ProtobufPipeSource::Plugin as i32, None, Some(plugin_id)) }, + PipeSource::Keybind => (ProtobufPipeSource::Keybind as i32, None, None), }; let name = pipe_message.name; let payload = pipe_message.payload; |