diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-01-17 12:10:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-17 12:10:49 +0100 |
commit | d780bd91052d8282ba5a7f06c6fb7faa7ca7cc18 (patch) | |
tree | ca08219a38b9e6a3b1c027682359074c86e0dbb5 /zellij-utils/src/plugin_api | |
parent | f6d57295a02393e26c74afb007bf673bcbb454e8 (diff) |
feat(plugins): introduce 'pipes', allowing users to pipe data to and control plugins from the command line (#3066)
* prototype - working with message from the cli
* prototype - pipe from the CLI to plugins
* prototype - pipe from the CLI to plugins and back again
* prototype - working with better cli interface
* prototype - working after removing unused stuff
* prototype - working with launching plugin if it is not launched, also fixed event ordering
* refactor: change message to cli-message
* prototype - allow plugins to send messages to each other
* fix: allow cli messages to send plugin parameters (and implement backpressure)
* fix: use input_pipe_id to identify cli pipes instead of their message name
* fix: come cleanups and add skip_cache parameter
* fix: pipe/client-server communication robustness
* fix: leaking messages between plugins while loading
* feat: allow plugins to specify how a new plugin instance is launched when sending messages
* fix: add permissions
* refactor: adjust cli api
* fix: improve cli plugin loading error messages
* docs: cli pipe
* fix: take plugin configuration into account when messaging between plugins
* refactor: pipe message protobuf interface
* refactor: update(event) -> pipe
* refactor - rename CliMessage to CliPipe
* fix: add is_private to pipes and change some naming
* refactor - cli client
* refactor: various cleanups
* style(fmt): rustfmt
* fix(pipes): backpressure across multiple plugins
* style: some cleanups
* style(fmt): rustfmt
* style: fix merge conflict mistake
* style(wording): clarify pipe permission
Diffstat (limited to 'zellij-utils/src/plugin_api')
-rw-r--r-- | zellij-utils/src/plugin_api/action.proto | 9 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/mod.rs | 1 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/pipe_message.proto | 23 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/pipe_message.rs | 71 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 40 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 144 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.proto | 2 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.rs | 8 |
9 files changed, 295 insertions, 4 deletions
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index da10d82a8..0ed5b3b7a 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -53,9 +53,17 @@ message Action { IdAndName rename_tab_payload = 44; string rename_session_payload = 45; LaunchOrFocusPluginPayload launch_plugin_payload = 46; + CliPipePayload message_payload = 47; } } +message CliPipePayload { + optional string name = 1; + string payload = 2; + repeated NameAndValue args = 3; + optional string plugin = 4; +} + message IdAndName { bytes name = 1; uint32 id = 2; @@ -227,6 +235,7 @@ enum ActionName { BreakPaneLeft = 79; RenameSession = 80; LaunchPlugin = 81; + CliPipe = 82; } message Position { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 71eaaa130..82de4c7ee 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -1246,6 +1246,7 @@ impl TryFrom<Action> for ProtobufAction { | Action::Deny | Action::Copy | Action::DumpLayout + | Action::CliPipe { .. } | Action::SkipConfirm(..) => Err("Unsupported action"), } } diff --git a/zellij-utils/src/plugin_api/mod.rs b/zellij-utils/src/plugin_api/mod.rs index 40057fde0..0e26485e9 100644 --- a/zellij-utils/src/plugin_api/mod.rs +++ b/zellij-utils/src/plugin_api/mod.rs @@ -5,6 +5,7 @@ pub mod file; pub mod input_mode; pub mod key; pub mod message; +pub mod pipe_message; pub mod plugin_command; pub mod plugin_ids; pub mod plugin_permission; diff --git a/zellij-utils/src/plugin_api/pipe_message.proto b/zellij-utils/src/plugin_api/pipe_message.proto new file mode 100644 index 000000000..5f488a758 --- /dev/null +++ b/zellij-utils/src/plugin_api/pipe_message.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package api.pipe_message; + +message PipeMessage { + PipeSource source = 1; + optional string cli_source_id = 2; + optional uint32 plugin_source_id = 3; + string name = 4; + optional string payload = 5; + repeated Arg args = 6; + bool is_private = 7; +} + +enum PipeSource { + Cli = 0; + Plugin = 1; +} + +message Arg { + string key = 1; + string value = 2; +} diff --git a/zellij-utils/src/plugin_api/pipe_message.rs b/zellij-utils/src/plugin_api/pipe_message.rs new file mode 100644 index 000000000..dbfe49305 --- /dev/null +++ b/zellij-utils/src/plugin_api/pipe_message.rs @@ -0,0 +1,71 @@ +pub use super::generated_api::api::pipe_message::{ + Arg as ProtobufArg, PipeMessage as ProtobufPipeMessage, PipeSource as ProtobufPipeSource, +}; +use crate::data::{PipeMessage, PipeSource}; + +use std::convert::TryFrom; + +impl TryFrom<ProtobufPipeMessage> for PipeMessage { + type Error = &'static str; + fn try_from(protobuf_pipe_message: ProtobufPipeMessage) -> Result<Self, &'static str> { + let source = match ( + ProtobufPipeSource::from_i32(protobuf_pipe_message.source), + protobuf_pipe_message.cli_source_id, + protobuf_pipe_message.plugin_source_id, + ) { + (Some(ProtobufPipeSource::Cli), Some(cli_source_id), _) => { + PipeSource::Cli(cli_source_id) + }, + (Some(ProtobufPipeSource::Plugin), _, Some(plugin_source_id)) => { + PipeSource::Plugin(plugin_source_id) + }, + _ => return Err("Invalid PipeSource or payload"), + }; + let name = protobuf_pipe_message.name; + let payload = protobuf_pipe_message.payload; + let args = protobuf_pipe_message + .args + .into_iter() + .map(|arg| (arg.key, arg.value)) + .collect(); + let is_private = protobuf_pipe_message.is_private; + Ok(PipeMessage { + source, + name, + payload, + args, + is_private, + }) + } +} + +impl TryFrom<PipeMessage> for ProtobufPipeMessage { + type Error = &'static str; + fn try_from(pipe_message: PipeMessage) -> Result<Self, &'static str> { + let (source, cli_source_id, plugin_source_id) = match pipe_message.source { + PipeSource::Cli(input_pipe_id) => { + (ProtobufPipeSource::Cli as i32, Some(input_pipe_id), None) + }, + PipeSource::Plugin(plugin_id) => { + (ProtobufPipeSource::Plugin as i32, None, Some(plugin_id)) + }, + }; + let name = pipe_message.name; + let payload = pipe_message.payload; + let args: Vec<_> = pipe_message + .args + .into_iter() + .map(|(key, value)| ProtobufArg { key, value }) + .collect(); + let is_private = pipe_message.is_private; + Ok(ProtobufPipeMessage { + source, + cli_source_id, + plugin_source_id, + name, + payload, + args, + is_private, + }) + } +} diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 53994a88c..6ffb0345b 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -87,6 +87,10 @@ enum CommandName { DeleteDeadSession = 73; DeleteAllDeadSessions = 74; RenameSession = 75; + UnblockCliPipeInput = 76; + BlockCliPipeInput = 77; + CliPipeOutput = 78; + MessageToPlugin = 79; } message PluginCommand { @@ -137,9 +141,45 @@ message PluginCommand { WebRequestPayload web_request_payload = 44; string delete_dead_session_payload = 45; string rename_session_payload = 46; + string unblock_cli_pipe_input_payload = 47; + string block_cli_pipe_input_payload = 48; + CliPipeOutputPayload cli_pipe_output_payload = 49; + MessageToPluginPayload message_to_plugin_payload = 50; } } +message CliPipeOutputPayload { + string pipe_name = 1; + string output = 2; +} + +message MessageToPluginPayload { + optional string plugin_url = 1; + repeated ContextItem plugin_config = 2; + string message_name = 3; + optional string message_payload = 4; + repeated ContextItem message_args = 5; + optional NewPluginArgs new_plugin_args = 6; +} + +message NewPluginArgs { + optional bool should_float = 1; + optional PaneId pane_id_to_replace = 2; + optional string pane_title = 3; + optional string cwd = 4; + bool skip_cache = 5; +} + +message PaneId { + PaneType pane_type = 1; + uint32 id = 2; +} + +enum PaneType { + Terminal = 0; + Plugin = 1; +} + message SwitchSessionPayload { optional string name = 1; optional uint32 tab_position = 2; diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index ed476687c..fa570a267 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -3,9 +3,11 @@ pub use super::generated_api::api::{ event::{EventNameList as ProtobufEventNameList, Header}, input_mode::InputMode as ProtobufInputMode, plugin_command::{ - plugin_command::Payload, CommandName, ContextItem, EnvVariable, ExecCmdPayload, - HttpVerb as ProtobufHttpVerb, IdAndNewName, MovePayload, OpenCommandPanePayload, - OpenFilePayload, PluginCommand as ProtobufPluginCommand, PluginMessagePayload, + plugin_command::Payload, CliPipeOutputPayload, CommandName, ContextItem, EnvVariable, + ExecCmdPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, MessageToPluginPayload, + MovePayload, NewPluginArgs as ProtobufNewPluginArgs, OpenCommandPanePayload, + OpenFilePayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType, + PluginCommand as ProtobufPluginCommand, PluginMessagePayload, RequestPluginPermissionPayload, ResizePayload, RunCommandPayload, SetTimeoutPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload, UnsubscribePayload, WebRequestPayload, @@ -14,7 +16,10 @@ pub use super::generated_api::api::{ resize::ResizeAction as ProtobufResizeAction, }; -use crate::data::{ConnectToSession, HttpVerb, PermissionType, PluginCommand}; +use crate::data::{ + ConnectToSession, HttpVerb, MessageToPlugin, NewPluginArgs, PaneId, PermissionType, + PluginCommand, +}; use std::collections::BTreeMap; use std::convert::TryFrom; @@ -42,6 +47,33 @@ impl Into<ProtobufHttpVerb> for HttpVerb { } } +impl TryFrom<ProtobufPaneId> for PaneId { + type Error = &'static str; + fn try_from(protobuf_pane_id: ProtobufPaneId) -> Result<Self, &'static str> { + match ProtobufPaneType::from_i32(protobuf_pane_id.pane_type) { + Some(ProtobufPaneType::Terminal) => Ok(PaneId::Terminal(protobuf_pane_id.id)), + Some(ProtobufPaneType::Plugin) => Ok(PaneId::Plugin(protobuf_pane_id.id)), + None => Err("Failed to convert PaneId"), + } + } +} + +impl TryFrom<PaneId> for ProtobufPaneId { + type Error = &'static str; + fn try_from(pane_id: PaneId) -> Result<Self, &'static str> { + match pane_id { + PaneId::Terminal(id) => Ok(ProtobufPaneId { + pane_type: ProtobufPaneType::Terminal as i32, + id, + }), + PaneId::Plugin(id) => Ok(ProtobufPaneId { + pane_type: ProtobufPaneType::Plugin as i32, + id, + }), + } + } +} + impl TryFrom<ProtobufPluginCommand> for PluginCommand { type Error = &'static str; fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> { @@ -641,6 +673,62 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for RenameSession"), }, + Some(CommandName::UnblockCliPipeInput) => match protobuf_plugin_command.payload { + Some(Payload::UnblockCliPipeInputPayload(pipe_name)) => { + Ok(PluginCommand::UnblockCliPipeInput(pipe_name)) + }, + _ => Err("Mismatched payload for UnblockPipeInput"), + }, + Some(CommandName::BlockCliPipeInput) => match protobuf_plugin_command.payload { + Some(Payload::BlockCliPipeInputPayload(pipe_name)) => { + Ok(PluginCommand::BlockCliPipeInput(pipe_name)) + }, + _ => Err("Mismatched payload for BlockPipeInput"), + }, + Some(CommandName::CliPipeOutput) => match protobuf_plugin_command.payload { + Some(Payload::CliPipeOutputPayload(CliPipeOutputPayload { pipe_name, output })) => { + Ok(PluginCommand::CliPipeOutput(pipe_name, output)) + }, + _ => Err("Mismatched payload for PipeOutput"), + }, + Some(CommandName::MessageToPlugin) => match protobuf_plugin_command.payload { + Some(Payload::MessageToPluginPayload(MessageToPluginPayload { + plugin_url, + plugin_config, + message_name, + message_payload, + message_args, + new_plugin_args, + })) => { + let plugin_config: BTreeMap<String, String> = plugin_config + .into_iter() + .map(|e| (e.name, e.value)) + .collect(); + let message_args: BTreeMap<String, String> = message_args + .into_iter() + .map(|e| (e.name, e.value)) + .collect(); + Ok(PluginCommand::MessageToPlugin(MessageToPlugin { + plugin_url, + plugin_config, + message_name, + message_payload, + message_args, + new_plugin_args: new_plugin_args.and_then(|protobuf_new_plugin_args| { + Some(NewPluginArgs { + should_float: protobuf_new_plugin_args.should_float, + pane_id_to_replace: protobuf_new_plugin_args + .pane_id_to_replace + .and_then(|p_id| PaneId::try_from(p_id).ok()), + pane_title: protobuf_new_plugin_args.pane_title, + cwd: protobuf_new_plugin_args.cwd.map(|cwd| PathBuf::from(cwd)), + skip_cache: protobuf_new_plugin_args.skip_cache, + }) + }), + })) + }, + _ => Err("Mismatched payload for PipeOutput"), + }, None => Err("Unrecognized plugin command"), } } @@ -1069,6 +1157,54 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { name: CommandName::RenameSession as i32, payload: Some(Payload::RenameSessionPayload(new_session_name)), }), + PluginCommand::UnblockCliPipeInput(pipe_name) => Ok(ProtobufPluginCommand { + name: CommandName::UnblockCliPipeInput as i32, + payload: Some(Payload::UnblockCliPipeInputPayload(pipe_name)), + }), + PluginCommand::BlockCliPipeInput(pipe_name) => Ok(ProtobufPluginCommand { + name: CommandName::BlockCliPipeInput as i32, + payload: Some(Payload::BlockCliPipeInputPayload(pipe_name)), + }), + PluginCommand::CliPipeOutput(pipe_name, output) => Ok(ProtobufPluginCommand { + name: CommandName::CliPipeOutput as i32, + payload: Some(Payload::CliPipeOutputPayload(CliPipeOutputPayload { + pipe_name, + output, + })), + }), + PluginCommand::MessageToPlugin(message_to_plugin) => { + let plugin_config: Vec<_> = message_to_plugin + .plugin_config + .into_iter() + .map(|(name, value)| ContextItem { name, value }) + .collect(); + let message_args: Vec<_> = message_to_plugin + .message_args + .into_iter() + .map(|(name, value)| ContextItem { name, value }) + .collect(); + Ok(ProtobufPluginCommand { + name: CommandName::MessageToPlugin as i32, + payload: Some(Payload::MessageToPluginPayload(MessageToPluginPayload { + plugin_url: message_to_plugin.plugin_url, + plugin_config, + message_name: message_to_plugin.message_name, + message_payload: message_to_plugin.message_payload, + message_args, + new_plugin_args: message_to_plugin.new_plugin_args.map(|m_t_p| { + ProtobufNewPluginArgs { + should_float: m_t_p.should_float, + pane_id_to_replace: m_t_p + .pane_id_to_replace + .and_then(|p_id| ProtobufPaneId::try_from(p_id).ok()), + pane_title: m_t_p.pane_title, + cwd: m_t_p.cwd.map(|cwd| cwd.display().to_string()), + skip_cache: m_t_p.skip_cache, + } + }), + })), + }) + }, } } } diff --git a/zellij-utils/src/plugin_api/plugin_permission.proto b/zellij-utils/src/plugin_api/plugin_permission.proto index a796c7481..761384c1d 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.proto +++ b/zellij-utils/src/plugin_api/plugin_permission.proto @@ -10,4 +10,6 @@ enum PermissionType { OpenTerminalsOrPlugins = 4; WriteToStdin = 5; WebAccess = 6; + ReadCliPipes = 7; + MessageAndLaunchOtherPlugins = 8; } diff --git a/zellij-utils/src/plugin_api/plugin_permission.rs b/zellij-utils/src/plugin_api/plugin_permission.rs index c9f0d49f5..4f258ac28 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.rs +++ b/zellij-utils/src/plugin_api/plugin_permission.rs @@ -20,6 +20,10 @@ impl TryFrom<ProtobufPermissionType> for PermissionType { }, ProtobufPermissionType::WriteToStdin => Ok(PermissionType::WriteToStdin), ProtobufPermissionType::WebAccess => Ok(PermissionType::WebAccess), + ProtobufPermissionType::ReadCliPipes => Ok(PermissionType::ReadCliPipes), + ProtobufPermissionType::MessageAndLaunchOtherPlugins => { + Ok(PermissionType::MessageAndLaunchOtherPlugins) + }, } } } @@ -41,6 +45,10 @@ impl TryFrom<PermissionType> for ProtobufPermissionType { }, PermissionType::WriteToStdin => Ok(ProtobufPermissionType::WriteToStdin), PermissionType::WebAccess => Ok(ProtobufPermissionType::WebAccess), + PermissionType::ReadCliPipes => Ok(ProtobufPermissionType::ReadCliPipes), + PermissionType::MessageAndLaunchOtherPlugins => { + Ok(ProtobufPermissionType::MessageAndLaunchOtherPlugins) + }, } } } |