feat(plugins): rebind keys api (#3680)

* feat(plugins): add API to explicitly unbind/rebind specific keys in specific modes

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2024-10-15 17:27:44 +02:00 committed by GitHub
parent 5ae36ed58f
commit 0c9d72c51e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 697 additions and 27 deletions

View file

@ -445,6 +445,86 @@ impl ZellijPlugin for State {
skip_plugin_cache,
)
},
BareKey::Char('y') if key.has_modifiers(&[KeyModifier::Alt]) => {
let write_to_disk = true;
let mut keys_to_unbind = vec![
(
InputMode::Locked,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Normal,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Pane,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Tab,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Resize,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Move,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Search,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
(
InputMode::Session,
KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
),
];
let mut keys_to_rebind = vec![
(
InputMode::Locked,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Normal)],
),
(
InputMode::Normal,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Pane,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Tab,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Resize,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Move,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Search,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
(
InputMode::Session,
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
vec![actions::Action::SwitchToMode(InputMode::Locked)],
),
];
rebind_keys(keys_to_unbind, keys_to_rebind, write_to_disk);
},
_ => {},
},
Event::CustomMessage(message, payload) => {

View file

@ -42,10 +42,11 @@ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
cli::CliArgs,
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
data::{ConnectToSession, Event, InputMode, PluginCapabilities},
data::{ConnectToSession, Event, InputMode, KeyWithModifier, PluginCapabilities},
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
home::{default_layout_dir, get_default_data_dir},
input::{
actions::Action,
command::{RunCommand, TerminalAction},
config::Config,
get_mode_info,
@ -109,6 +110,12 @@ pub enum ServerInstruction {
},
ConfigWrittenToDisk(ClientId, Config),
FailedToWriteConfigToDisk(ClientId, Option<PathBuf>), // Pathbuf - file we failed to write
RebindKeys {
client_id: ClientId,
keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
write_config_to_disk: bool,
},
}
impl From<&ServerInstruction> for ServerContext {
@ -145,6 +152,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::FailedToWriteConfigToDisk(..) => {
ServerContext::FailedToWriteConfigToDisk
},
ServerInstruction::RebindKeys { .. } => ServerContext::RebindKeys,
}
}
}
@ -226,6 +234,57 @@ impl SessionConfiguration {
}
(full_reconfigured_config, config_changed)
}
pub fn rebind_keys(
&mut self,
client_id: &ClientId,
keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
) -> (Option<Config>, bool) {
let mut full_reconfigured_config = None;
let mut config_changed = false;
if self.runtime_config.get(client_id).is_none() {
if let Some(saved_config) = self.saved_config.get(client_id) {
self.runtime_config.insert(*client_id, saved_config.clone());
}
}
match self.runtime_config.get_mut(client_id) {
Some(config) => {
for (input_mode, key_with_modifier) in keys_to_unbind {
let keys_in_mode = config
.keybinds
.0
.entry(input_mode)
.or_insert_with(Default::default);
let removed = keys_in_mode.remove(&key_with_modifier);
if removed.is_some() {
config_changed = true;
}
}
for (input_mode, key_with_modifier, actions) in keys_to_rebind {
let keys_in_mode = config
.keybinds
.0
.entry(input_mode)
.or_insert_with(Default::default);
if keys_in_mode.get(&key_with_modifier) != Some(&actions) {
config_changed = true;
keys_in_mode.insert(key_with_modifier, actions);
}
}
if config_changed {
full_reconfigured_config = Some(config.clone());
}
},
None => {
log::error!(
"Could not find runtime or saved configuration for client, cannot rebind keys"
);
},
}
(full_reconfigured_config, config_changed)
}
}
pub(crate) struct SessionMetaData {
@ -1120,6 +1179,42 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_plugin(PluginInstruction::FailedToWriteConfigToDisk { file_path })
.unwrap();
},
ServerInstruction::RebindKeys {
client_id,
keys_to_rebind,
keys_to_unbind,
write_config_to_disk,
} => {
let (new_config, runtime_config_changed) = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.session_configuration
.rebind_keys(&client_id, keys_to_rebind, keys_to_unbind);
if let Some(new_config) = new_config {
if write_config_to_disk {
let clear_defaults = true;
send_to_client!(
client_id,
os_input,
ServerToClientMsg::WriteConfigToDisk {
config: new_config.to_string(clear_defaults)
},
session_state
);
}
if runtime_config_changed {
session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.propagate_configuration_changes(vec![(client_id, new_config)]);
}
}
},
}
}

View file

@ -8470,3 +8470,84 @@ pub fn load_new_plugin_plugin_command() {
.count();
assert_eq!(request_state_update_requests, 3);
}
#[test]
#[ignore]
pub fn rebind_keys_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
let plugin_host_folder = PathBuf::from(temp_folder.path());
let cache_path = plugin_host_folder.join("permissions_test.kdl");
let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
let plugin_should_float = Some(false);
let plugin_title = Some("test_plugin".to_owned());
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
configuration: Default::default(),
..Default::default()
});
let tab_index = 1;
let client_id = 1;
let size = Size {
cols: 121,
rows: 20,
};
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
received_screen_instructions,
ScreenInstruction::Exit,
screen_receiver,
1,
&PermissionType::ChangeApplicationState,
cache_path,
plugin_thread_sender,
client_id
);
let received_server_instruction = Arc::new(Mutex::new(vec![]));
let server_thread = log_actions_in_thread_struct!(
received_server_instruction,
ServerInstruction::RebindKeys,
server_receiver,
1
);
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
let _ = plugin_thread_sender.send(PluginInstruction::Load(
plugin_should_float,
false,
plugin_title,
run_plugin,
Some(tab_index),
None,
client_id,
size,
None,
false,
));
std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None,
Some(client_id),
Event::Key(KeyWithModifier::new(BareKey::Char('y')).with_alt_modifier()), // this triggers the enent in the fixture plugin
)]));
std::thread::sleep(std::time::Duration::from_millis(500));
teardown();
server_thread.join().unwrap(); // this might take a while if the cache is cold
let rebind_event = received_server_instruction
.lock()
.unwrap()
.iter()
.rev()
.find_map(|i| {
if let ServerInstruction::RebindKeys { .. } = i {
Some(i.clone())
} else {
None
}
})
.clone();
assert_snapshot!(format!("{:#?}", rebind_event));
}

View file

@ -1,11 +1,231 @@
---
source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 6566
expression: "format!(\"{:#?}\", rebind_keys_event)"
assertion_line: 8552
expression: "format!(\"{:#?}\", rebind_event)"
---
Some(
RebindKeys(
1,
"\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ",
RebindKeys {
client_id: 1,
keys_to_rebind: [
(
Locked,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Normal,
),
],
),
(
Normal,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Pane,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Tab,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Resize,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Move,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Search,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
(
Session,
KeyWithModifier {
bare_key: Char(
'a',
),
key_modifiers: {
Ctrl,
},
},
[
SwitchToMode(
Locked,
),
],
),
],
keys_to_unbind: [
(
Locked,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Normal,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Pane,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Tab,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Resize,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Move,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Search,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
(
Session,
KeyWithModifier {
bare_key: Char(
'g',
),
key_modifiers: {
Ctrl,
},
},
),
],
write_config_to_disk: true,
},
)

View file

@ -18,8 +18,8 @@ use std::{
};
use wasmtime::{Caller, Linker};
use zellij_utils::data::{
CommandType, ConnectToSession, FloatingPaneCoordinates, HttpVerb, LayoutInfo, MessageToPlugin,
OriginatingPlugin, PermissionStatus, PermissionType, PluginPermission,
CommandType, ConnectToSession, FloatingPaneCoordinates, HttpVerb, KeyWithModifier, LayoutInfo,
MessageToPlugin, OriginatingPlugin, PermissionStatus, PermissionType, PluginPermission,
};
use zellij_utils::input::permission::PermissionCache;
use zellij_utils::{
@ -346,6 +346,11 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) {
load_in_background,
skip_plugin_cache,
} => load_new_plugin(env, url, config, load_in_background, skip_plugin_cache),
PluginCommand::RebindKeys {
keys_to_rebind,
keys_to_unbind,
write_config_to_disk,
} => rebind_keys(env, keys_to_rebind, keys_to_unbind, write_config_to_disk)?,
},
(PermissionStatus::Denied, permission) => {
log::error!(
@ -970,6 +975,25 @@ fn reconfigure(env: &PluginEnv, new_config: String, write_config_to_disk: bool)
Ok(())
}
fn rebind_keys(
env: &PluginEnv,
keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
write_config_to_disk: bool,
) -> Result<()> {
let err_context = || "Failed to rebind_keys";
let client_id = env.client_id;
env.senders
.send_to_server(ServerInstruction::RebindKeys {
client_id,
keys_to_rebind,
keys_to_unbind,
write_config_to_disk,
})
.with_context(err_context)?;
Ok(())
}
fn switch_to_mode(env: &PluginEnv, input_mode: InputMode) {
let action = Action::SwitchToMode(input_mode);
let error_msg = || format!("failed to switch to mode in plugin {}", env.name());
@ -1874,7 +1898,9 @@ fn check_command_permission(
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
PluginCommand::Reconfigure(..) => PermissionType::Reconfigure,
PluginCommand::RebindKeys { .. } | PluginCommand::Reconfigure(..) => {
PermissionType::Reconfigure
},
_ => return (PermissionStatus::Granted, None),
};

View file

@ -6,6 +6,7 @@ use std::{
};
use zellij_utils::data::*;
use zellij_utils::errors::prelude::*;
use zellij_utils::input::actions::Action;
pub use zellij_utils::plugin_api;
use zellij_utils::plugin_api::plugin_command::ProtobufPluginCommand;
use zellij_utils::plugin_api::plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion};
@ -853,7 +854,7 @@ pub fn dump_session_layout() {
unsafe { host_run_plugin_command() };
}
/// Rebind keys for the current user
/// Change configuration for the current user
pub fn reconfigure(new_config: String, save_configuration_file: bool) {
let plugin_command = PluginCommand::Reconfigure(new_config, save_configuration_file);
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
@ -1102,6 +1103,22 @@ pub fn load_new_plugin<S: AsRef<str>>(
unsafe { host_run_plugin_command() };
}
/// Rebind keys for the current user
pub fn rebind_keys(
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
write_config_to_disk: bool,
) {
let plugin_command = PluginCommand::RebindKeys {
keys_to_rebind,
keys_to_unbind,
write_config_to_disk,
};
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
unsafe { host_run_plugin_command() };
}
// Utility Functions
#[allow(unused)]

View file

@ -5,7 +5,7 @@ pub struct PluginCommand {
pub name: i32,
#[prost(
oneof = "plugin_command::Payload",
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87"
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88"
)]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
@ -172,10 +172,40 @@ pub mod plugin_command {
ReloadPluginPayload(super::ReloadPluginPayload),
#[prost(message, tag = "87")]
LoadNewPluginPayload(super::LoadNewPluginPayload),
#[prost(message, tag = "88")]
RebindKeysPayload(super::RebindKeysPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RebindKeysPayload {
#[prost(message, repeated, tag = "1")]
pub keys_to_rebind: ::prost::alloc::vec::Vec<KeyToRebind>,
#[prost(message, repeated, tag = "2")]
pub keys_to_unbind: ::prost::alloc::vec::Vec<KeyToUnbind>,
#[prost(bool, tag = "3")]
pub write_config_to_disk: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyToRebind {
#[prost(enumeration = "super::input_mode::InputMode", tag = "1")]
pub input_mode: i32,
#[prost(message, optional, tag = "2")]
pub key: ::core::option::Option<super::key::Key>,
#[prost(message, repeated, tag = "3")]
pub actions: ::prost::alloc::vec::Vec<super::action::Action>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyToUnbind {
#[prost(enumeration = "super::input_mode::InputMode", tag = "1")]
pub input_mode: i32,
#[prost(message, optional, tag = "2")]
pub key: ::core::option::Option<super::key::Key>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LoadNewPluginPayload {
#[prost(string, tag = "1")]
pub plugin_url: ::prost::alloc::string::String,
@ -684,6 +714,7 @@ pub enum CommandName {
BreakPanesToTabWithIndex = 109,
ReloadPlugin = 110,
LoadNewPlugin = 111,
RebindKeys = 112,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@ -806,6 +837,7 @@ impl CommandName {
CommandName::BreakPanesToTabWithIndex => "BreakPanesToTabWithIndex",
CommandName::ReloadPlugin => "ReloadPlugin",
CommandName::LoadNewPlugin => "LoadNewPlugin",
CommandName::RebindKeys => "RebindKeys",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@ -925,6 +957,7 @@ impl CommandName {
"BreakPanesToTabWithIndex" => Some(Self::BreakPanesToTabWithIndex),
"ReloadPlugin" => Some(Self::ReloadPlugin),
"LoadNewPlugin" => Some(Self::LoadNewPlugin),
"RebindKeys" => Some(Self::RebindKeys),
_ => None,
}
}

View file

@ -1873,4 +1873,9 @@ pub enum PluginCommand {
load_in_background: bool,
skip_plugin_cache: bool,
},
RebindKeys {
keys_to_rebind: Vec<(InputMode, KeyWithModifier, Vec<Action>)>,
keys_to_unbind: Vec<(InputMode, KeyWithModifier)>,
write_config_to_disk: bool,
},
}

View file

@ -484,6 +484,7 @@ pub enum ServerContext {
Reconfigure,
ConfigWrittenToDisk,
FailedToWriteConfigToDisk,
RebindKeys,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]

View file

@ -7,6 +7,8 @@ import "command.proto";
import "message.proto";
import "resize.proto";
import "plugin_permission.proto";
import "input_mode.proto";
import "key.proto";
package api.plugin_command;
@ -123,6 +125,7 @@ enum CommandName {
BreakPanesToTabWithIndex = 109;
ReloadPlugin = 110;
LoadNewPlugin = 111;
RebindKeys = 112;
}
message PluginCommand {
@ -205,9 +208,27 @@ message PluginCommand {
BreakPanesToTabWithIndexPayload break_panes_to_tab_with_index_payload = 85;
ReloadPluginPayload reload_plugin_payload = 86;
LoadNewPluginPayload load_new_plugin_payload = 87;
RebindKeysPayload rebind_keys_payload = 88;
}
}
message RebindKeysPayload {
repeated KeyToRebind keys_to_rebind = 1;
repeated KeyToUnbind keys_to_unbind = 2;
bool write_config_to_disk = 3;
}
message KeyToRebind {
input_mode.InputMode input_mode = 1;
key.Key key = 2;
repeated action.Action actions = 3;
}
message KeyToUnbind {
input_mode.InputMode input_mode = 1;
key.Key key = 2;
}
message LoadNewPluginPayload {
string plugin_url = 1;
repeated ContextItem plugin_config = 2;

View file

@ -9,28 +9,29 @@ pub use super::generated_api::api::{
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, HidePaneWithIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KillSessionsPayload, LoadNewPluginPayload,
MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload,
MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabsWithLayoutInfoPayload,
OpenCommandPanePayload, OpenFilePayload, PageScrollDownInPaneIdPayload,
PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneType as ProtobufPaneType,
PluginCommand as ProtobufPluginCommand, PluginMessagePayload, ReconfigurePayload,
ReloadPluginPayload, RequestPluginPermissionPayload, RerunCommandPanePayload,
ResizePaneIdWithDirectionPayload, ResizePayload, RunCommandPayload,
ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload,
ScrollUpInPaneIdPayload, SetTimeoutPayload, ShowPaneWithIdPayload, SubscribePayload,
SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload,
LoadNewPluginPayload, MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload,
MovePaneWithPaneIdPayload, MovePayload, NewPluginArgs as ProtobufNewPluginArgs,
NewTabsWithLayoutInfoPayload, OpenCommandPanePayload, OpenFilePayload,
PageScrollDownInPaneIdPayload, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId,
PaneType as ProtobufPaneType, PluginCommand as ProtobufPluginCommand, PluginMessagePayload,
RebindKeysPayload, ReconfigurePayload, ReloadPluginPayload, RequestPluginPermissionPayload,
RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload,
RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetTimeoutPayload,
ShowPaneWithIdPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload,
TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload,
WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
};
use crate::data::{
ConnectToSession, FloatingPaneCoordinates, HttpVerb, MessageToPlugin, NewPluginArgs, PaneId,
PermissionType, PluginCommand,
ConnectToSession, FloatingPaneCoordinates, HttpVerb, InputMode, KeyWithModifier,
MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand,
};
use crate::input::actions::Action;
use crate::input::layout::SplitSize;
use std::collections::BTreeMap;
@ -184,6 +185,60 @@ impl TryFrom<PaneId> for ProtobufPaneId {
}
}
impl TryFrom<(InputMode, KeyWithModifier, Vec<Action>)> for KeyToRebind {
type Error = &'static str;
fn try_from(
key_to_rebind: (InputMode, KeyWithModifier, Vec<Action>),
) -> Result<Self, &'static str> {
Ok(KeyToRebind {
input_mode: key_to_rebind.0 as i32,
key: Some(key_to_rebind.1.try_into()?),
actions: key_to_rebind
.2
.into_iter()
.filter_map(|a| a.try_into().ok())
.collect(),
})
}
}
impl TryFrom<(InputMode, KeyWithModifier)> for KeyToUnbind {
type Error = &'static str;
fn try_from(key_to_unbind: (InputMode, KeyWithModifier)) -> Result<Self, &'static str> {
Ok(KeyToUnbind {
input_mode: key_to_unbind.0 as i32,
key: Some(key_to_unbind.1.try_into()?),
})
}
}
fn key_to_rebind_to_plugin_command_assets(
key_to_rebind: KeyToRebind,
) -> Option<(InputMode, KeyWithModifier, Vec<Action>)> {
Some((
ProtobufInputMode::from_i32(key_to_rebind.input_mode)?
.try_into()
.ok()?,
key_to_rebind.key?.try_into().ok()?,
key_to_rebind
.actions
.into_iter()
.filter_map(|a| a.try_into().ok())
.collect(),
))
}
fn key_to_unbind_to_plugin_command_assets(
key_to_unbind: KeyToUnbind,
) -> Option<(InputMode, KeyWithModifier)> {
Some((
ProtobufInputMode::from_i32(key_to_unbind.input_mode)?
.try_into()
.ok()?,
key_to_unbind.key?.try_into().ok()?,
))
}
impl TryFrom<ProtobufPluginCommand> for PluginCommand {
type Error = &'static str;
fn try_from(protobuf_plugin_command: ProtobufPluginCommand) -> Result<Self, &'static str> {
@ -1226,6 +1281,24 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for LoadNewPlugin"),
},
Some(CommandName::RebindKeys) => match protobuf_plugin_command.payload {
Some(Payload::RebindKeysPayload(rebind_keys_payload)) => {
Ok(PluginCommand::RebindKeys {
keys_to_rebind: rebind_keys_payload
.keys_to_rebind
.into_iter()
.filter_map(|k| key_to_rebind_to_plugin_command_assets(k))
.collect(),
keys_to_unbind: rebind_keys_payload
.keys_to_unbind
.into_iter()
.filter_map(|k| key_to_unbind_to_plugin_command_assets(k))
.collect(),
write_config_to_disk: rebind_keys_payload.write_config_to_disk,
})
},
_ => Err("Mismatched payload for RebindKeys"),
},
None => Err("Unrecognized plugin command"),
}
}
@ -2031,6 +2104,24 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
should_load_plugin_in_background: load_in_background,
})),
}),
PluginCommand::RebindKeys {
keys_to_rebind,
keys_to_unbind,
write_config_to_disk,
} => Ok(ProtobufPluginCommand {
name: CommandName::RebindKeys as i32,
payload: Some(Payload::RebindKeysPayload(RebindKeysPayload {
keys_to_rebind: keys_to_rebind
.into_iter()
.filter_map(|k| k.try_into().ok())
.collect(),
keys_to_unbind: keys_to_unbind
.into_iter()
.filter_map(|k| k.try_into().ok())
.collect(),
write_config_to_disk,
})),
}),
}
}
}