feat(plugins): API to temporarily bind keys to send a message to a specific plugin id (#3561)

This commit is contained in:
Aram Drevekenin 2024-08-21 16:46:01 +02:00 committed by GitHub
parent 905ce0a27d
commit 08b6072a69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 117 additions and 34 deletions

View file

@ -134,6 +134,7 @@ pub enum PluginInstruction {
cwd: Option<PathBuf>, cwd: Option<PathBuf>,
skip_cache: bool, skip_cache: bool,
cli_client_id: ClientId, cli_client_id: ClientId,
plugin_and_client_id: Option<(u32, ClientId)>,
}, },
CachePluginEvents { CachePluginEvents {
plugin_id: PluginId, plugin_id: PluginId,
@ -624,42 +625,52 @@ pub(crate) fn plugin_thread_main(
cwd, cwd,
skip_cache, skip_cache,
cli_client_id, cli_client_id,
plugin_and_client_id,
} => { } => {
let should_float = floating.unwrap_or(true); let should_float = floating.unwrap_or(true);
let mut pipe_messages = vec![]; let mut pipe_messages = vec![];
match plugin { if let Some((plugin_id, client_id)) = plugin_and_client_id {
Some(plugin_url) => { let is_private = true;
// send to specific plugin(s) pipe_messages.push((
pipe_to_specific_plugins( Some(plugin_id),
PipeSource::Keybind, Some(client_id),
&plugin_url, PipeMessage::new(PipeSource::Keybind, name, &payload, &args, is_private),
&configuration, ));
&cwd, } else {
skip_cache, match plugin {
should_float, Some(plugin_url) => {
&pane_id_to_replace, // send to specific plugin(s)
&pane_title, pipe_to_specific_plugins(
Some(cli_client_id), PipeSource::Keybind,
&mut pipe_messages, &plugin_url,
&name, &configuration,
&payload, &cwd,
&args, skip_cache,
&bus, should_float,
&mut wasm_bridge, &pane_id_to_replace,
&plugin_aliases, &pane_title,
); Some(cli_client_id),
}, &mut pipe_messages,
None => { &name,
// no specific destination, send to all plugins &payload,
pipe_to_all_plugins( &args,
PipeSource::Keybind, &bus,
&name, &mut wasm_bridge,
&payload, &plugin_aliases,
&args, );
&mut wasm_bridge, },
&mut pipe_messages, 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())?; wasm_bridge.pipe_messages(pipe_messages, shutdown_send.clone())?;
}, },
@ -768,6 +779,8 @@ pub(crate) fn plugin_thread_main(
keybinds, keybinds,
default_mode, default_mode,
} => { } => {
// TODO: notify plugins that this happened so that they can eg. rebind temporary keys that
// were lost
wasm_bridge wasm_bridge
.reconfigure(client_id, keybinds, default_mode) .reconfigure(client_id, keybinds, default_mode)
.non_fatal(); .non_fatal();

View file

@ -921,12 +921,13 @@ pub(crate) fn route_action(
cwd, cwd,
pane_title, pane_title,
launch_new, launch_new,
plugin_id,
.. ..
} => { } => {
if let Some(name) = name.take() { if let Some(name) = name.take() {
let should_open_in_place = in_place.unwrap_or(false); let should_open_in_place = in_place.unwrap_or(false);
let pane_id_to_replace = if should_open_in_place { pane_id } else { None }; let pane_id_to_replace = if should_open_in_place { pane_id } else { None };
if launch_new { if launch_new && plugin_id.is_none() {
// we do this to make sure the plugin is unique (has a unique configuration parameter) // we do this to make sure the plugin is unique (has a unique configuration parameter)
configuration configuration
.get_or_insert_with(BTreeMap::new) .get_or_insert_with(BTreeMap::new)
@ -945,6 +946,7 @@ pub(crate) fn route_action(
pane_title, pane_title,
skip_cache, skip_cache,
cli_client_id: client_id, cli_client_id: client_id,
plugin_and_client_id: plugin_id.map(|plugin_id| (plugin_id, client_id)),
}) })
.with_context(err_context)?; .with_context(err_context)?;
} else { } else {

View file

@ -289,6 +289,7 @@ pub enum Action {
payload: Option<String>, payload: Option<String>,
args: Option<BTreeMap<String, String>>, args: Option<BTreeMap<String, String>>,
plugin: Option<String>, plugin: Option<String>,
plugin_id: Option<u32>, // supercedes plugin if present
configuration: Option<BTreeMap<String, String>>, configuration: Option<BTreeMap<String, String>>,
launch_new: bool, launch_new: bool,
skip_cache: bool, skip_cache: bool,

View file

@ -363,6 +363,27 @@ pub fn kdl_arguments_that_are_strings<'a>(
Ok(args) Ok(args)
} }
pub fn kdl_arguments_that_are_digits<'a>(
arguments: impl Iterator<Item = &'a KdlEntry>,
) -> Result<Vec<i64>, ConfigError> {
let mut args: Vec<i64> = vec![];
for kdl_entry in arguments {
match kdl_entry.value().as_i64() {
Some(digit_value) => {
args.push(digit_value);
},
None => {
return Err(ConfigError::new_kdl_error(
format!("Argument must be a digit"),
kdl_entry.span().offset(),
kdl_entry.span().len(),
));
},
}
}
Ok(args)
}
pub fn kdl_child_string_value_for_entry<'a>( pub fn kdl_child_string_value_for_entry<'a>(
command_metadata: &'a KdlDocument, command_metadata: &'a KdlDocument,
entry_name: &'a str, entry_name: &'a str,
@ -1010,7 +1031,12 @@ impl Action {
in_place, in_place,
cwd, cwd,
pane_title, pane_title,
plugin_id,
} => { } => {
if plugin_id.is_some() {
log::warn!("Not serializing temporary keybinding MessagePluginId");
return None;
}
let mut node = KdlNode::new("MessagePlugin"); let mut node = KdlNode::new("MessagePlugin");
let mut node_children = KdlDocument::new(); let mut node_children = KdlDocument::new();
if let Some(plugin) = plugin { if let Some(plugin) = plugin {
@ -1678,6 +1704,46 @@ impl TryFrom<(&KdlNode, &Options)> for Action {
in_place: None, // TODO: support this in_place: None, // TODO: support this
cwd, cwd,
pane_title: title, pane_title: title,
plugin_id: None,
})
},
"MessagePluginId" => {
let arguments = action_arguments.iter().copied();
let mut args = kdl_arguments_that_are_digits(arguments)?;
let plugin_id = if args.is_empty() {
None
} else {
Some(args.remove(0) as u32)
};
let command_metadata = action_children.iter().next();
let launch_new = false;
let skip_cache = 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 configuration = None;
let name = name
// if no name is provided, 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: None,
configuration,
launch_new,
skip_cache,
floating: None,
in_place: None, // TODO: support this
cwd: None,
pane_title: None,
plugin_id,
}) })
}, },
_ => Err(ConfigError::new_kdl_error( _ => Err(ConfigError::new_kdl_error(

View file

@ -703,6 +703,7 @@ impl TryFrom<ProtobufAction> for Action {
in_place: None, in_place: None,
cwd: None, cwd: None,
pane_title: None, pane_title: None,
plugin_id: None,
}), }),
}, },
_ => Err("Unknown Action"), _ => Err("Unknown Action"),