summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-03-21 16:52:15 +0100
committerGitHub <noreply@github.com>2024-03-21 16:52:15 +0100
commit07b76ee610deb3a37082cf9a650f443fa579aa07 (patch)
treeff3d78914fbfb02001a38f2809aeb6c2619ec66d
parent8504881e4eeaa0dbc53203351c9529aed1068249 (diff)
feat(pipes): allow piping messages to plugins from keybindings (#3212)
-rw-r--r--default-plugins/fixture-plugin-for-tests/src/main.rs1
-rw-r--r--zellij-server/src/plugins/mod.rs67
-rw-r--r--zellij-server/src/route.rs45
-rw-r--r--zellij-utils/assets/prost/api.action.rs3
-rw-r--r--zellij-utils/assets/prost/api.pipe_message.rs3
-rw-r--r--zellij-utils/src/data.rs1
-rw-r--r--zellij-utils/src/errors.rs1
-rw-r--r--zellij-utils/src/input/actions.rs13
-rw-r--r--zellij-utils/src/kdl/mod.rs61
-rw-r--r--zellij-utils/src/plugin_api/action.proto1
-rw-r--r--zellij-utils/src/plugin_api/action.rs50
-rw-r--r--zellij-utils/src/plugin_api/pipe_message.proto1
-rw-r--r--zellij-utils/src/plugin_api/pipe_message.rs2
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;