feat(pipes): allow piping messages to plugins from keybindings (#3212)
This commit is contained in:
parent
8504881e4e
commit
07b76ee610
13 changed files with 227 additions and 22 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -401,6 +401,7 @@ pub enum PluginContext {
|
|||
MessageFromPlugin,
|
||||
UnblockCliPipes,
|
||||
WatchFilesystem,
|
||||
KeybindPipe,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ enum ActionName {
|
|||
LaunchPlugin = 81;
|
||||
CliPipe = 82;
|
||||
MoveTab = 83;
|
||||
KeybindPipe = 84;
|
||||
}
|
||||
|
||||
message Position {
|
||||
|
|
|
|||
|
|
@ -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(..)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ message PipeMessage {
|
|||
enum PipeSource {
|
||||
Cli = 0;
|
||||
Plugin = 1;
|
||||
Keybind = 2;
|
||||
}
|
||||
|
||||
message Arg {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue