Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): rebind keys at runtime #3422

Merged
merged 8 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
style(fmt): rustfmt
  • Loading branch information
imsnif committed Jun 14, 2024
commit d1e542d60cdde88db7dcf6fa2ec8ac21eccaaeb8
7 changes: 5 additions & 2 deletions default-plugins/fixture-plugin-for-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,16 @@ impl ZellijPlugin for State {
);
},
BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
rebind_keys("
rebind_keys(
"
keybinds {
locked {
bind \"a\" { NewTab; }
}
}
".to_owned());
"
.to_owned(),
);
},
_ => {},
},
Expand Down
7 changes: 5 additions & 2 deletions zellij-client/src/input_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ impl InputHandler {
) {
// we interpret the keys into actions on the server side so that we can change the
// keybinds at runtime
self.os_input
.send_to_server(ClientToServerMsg::Key(key.clone(), raw_bytes, is_kitty_keyboard_protocol));
self.os_input.send_to_server(ClientToServerMsg::Key(
key.clone(),
raw_bytes,
is_kitty_keyboard_protocol,
));
}
fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) {
match ansi_stdin_instructions {
Expand Down
47 changes: 31 additions & 16 deletions zellij-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext},
cli::CliArgs,
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
data::{ConnectToSession, Event, PluginCapabilities, InputMode},
data::{ConnectToSession, Event, InputMode, PluginCapabilities},
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
home::{default_layout_dir, get_default_data_dir},
input::{
command::{RunCommand, TerminalAction},
get_mode_info,
keybinds::Keybinds,
layout::Layout,
options::Options,
plugins::PluginAliases,
keybinds::Keybinds,
},
ipc::{ClientAttributes, ExitReason, ServerToClientMsg},
};
Expand Down Expand Up @@ -125,9 +125,7 @@ impl From<&ServerInstruction> for ServerContext {
ServerInstruction::DisconnectAllClientsExcept(..) => {
ServerContext::DisconnectAllClientsExcept
},
ServerInstruction::ChangeMode(..) => {
ServerContext::ChangeMode
},
ServerInstruction::ChangeMode(..) => ServerContext::ChangeMode,
ServerInstruction::ChangeModeForAllClients(..) => {
ServerContext::ChangeModeForAllClients
},
Expand Down Expand Up @@ -161,33 +159,45 @@ pub(crate) struct SessionMetaData {
impl SessionMetaData {
pub fn set_client_keybinds(&mut self, client_id: ClientId, keybinds: Keybinds) {
self.client_keybinds.insert(client_id, keybinds);
self.client_input_modes.insert(client_id, self.config_options.default_mode.unwrap_or_default());
self.client_input_modes.insert(
client_id,
self.config_options.default_mode.unwrap_or_default(),
);
}
pub fn get_client_keybinds_and_mode(&self, client_id: &ClientId) -> Option<(&Keybinds, &InputMode)> {
match (self.client_keybinds.get(client_id), self.client_input_modes.get(client_id)) {
pub fn get_client_keybinds_and_mode(
&self,
client_id: &ClientId,
) -> Option<(&Keybinds, &InputMode)> {
match (
self.client_keybinds.get(client_id),
self.client_input_modes.get(client_id),
) {
(Some(client_keybinds), Some(client_input_mode)) => {
Some((client_keybinds, client_input_mode))
},
_ => None
_ => None,
}
}
pub fn change_mode_for_all_clients(&mut self, input_mode: InputMode) {
let all_clients: Vec<ClientId> = self.client_input_modes.keys().copied().collect();
for client_id in all_clients {
self.client_input_modes.insert(client_id, input_mode);
}

}
pub fn rebind_keys(&mut self, client_id: ClientId, new_keybinds: String) -> Option<Keybinds> {
if let Some(current_keybinds) = self.client_keybinds.get_mut(&client_id) {
match Keybinds::from_string(new_keybinds, current_keybinds.clone(), &self.config_options) {
match Keybinds::from_string(
new_keybinds,
current_keybinds.clone(),
&self.config_options,
) {
Ok(new_keybinds) => {
*current_keybinds = new_keybinds.clone();
return Some(new_keybinds);
},
Err(e) => {
log::error!("Failed to parse keybindings: {}", e);
}
},
}
} else {
log::error!("Failed to bind keys for client: {client_id}");
Expand Down Expand Up @@ -528,8 +538,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
) => {
let mut rlock = session_data.write().unwrap();
let session_data = rlock.as_mut().unwrap();
session_data
.set_client_keybinds(client_id, attrs.keybinds.clone());
session_data.set_client_keybinds(client_id, attrs.keybinds.clone());
session_state
.write()
.unwrap()
Expand Down Expand Up @@ -897,7 +906,13 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.change_mode_for_all_clients(input_mode);
},
ServerInstruction::RebindKeys(client_id, new_keybinds) => {
let new_keybinds = session_data.write().unwrap().as_mut().unwrap().rebind_keys(client_id, new_keybinds).clone();
let new_keybinds = session_data
.write()
.unwrap()
.as_mut()
.unwrap()
.rebind_keys(client_id, new_keybinds)
.clone();
if let Some(new_keybinds) = new_keybinds {
session_data
.write()
Expand All @@ -908,7 +923,7 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.send_to_screen(ScreenInstruction::RebindKeys(new_keybinds, client_id))
.unwrap();
}
}
},
}
}

Expand Down
5 changes: 1 addition & 4 deletions zellij-server/src/plugins/zellij_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,10 +848,7 @@ fn rebind_keys(env: &ForeignFunctionEnv, new_keybinds: String) -> Result<()> {
let client_id = env.plugin_env.client_id;
env.plugin_env
.senders
.send_to_server(ServerInstruction::RebindKeys(
client_id,
new_keybinds,
))
.send_to_server(ServerInstruction::RebindKeys(client_id, new_keybinds))
.with_context(err_context)?;
Ok(())
}
Expand Down
23 changes: 11 additions & 12 deletions zellij-server/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ pub(crate) fn route_action(
)]))
.with_context(err_context)?;
senders
.send_to_server(ServerInstruction::ChangeMode(
client_id,
mode,
))
.send_to_server(ServerInstruction::ChangeMode(client_id, mode))
.with_context(err_context)?;
senders
.send_to_screen(ScreenInstruction::ChangeMode(
Expand Down Expand Up @@ -1003,12 +1000,14 @@ pub(crate) fn route_thread_main(
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
match rlocked_sessions.get_client_keybinds_and_mode(&client_id) {
Some((keybinds, input_mode)) => {
for action in keybinds.get_actions_for_key_in_mode_or_default_action(
&input_mode,
&key,
raw_bytes,
is_kitty_keyboard_protocol,
) {
for action in keybinds
.get_actions_for_key_in_mode_or_default_action(
&input_mode,
&key,
raw_bytes,
is_kitty_keyboard_protocol,
)
{
if route_action(
action,
client_id,
Expand All @@ -1026,10 +1025,10 @@ pub(crate) fn route_thread_main(
},
None => {
log::error!("Failed to get keybindings for client");
}
},
}
}
}
},
ClientToServerMsg::Action(action, maybe_pane_id, maybe_client_id) => {
let client_id = maybe_client_id.unwrap_or(client_id);
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
Expand Down
9 changes: 6 additions & 3 deletions zellij-server/src/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use zellij_utils::data::{
};
use zellij_utils::errors::prelude::*;
use zellij_utils::input::command::RunCommand;
use zellij_utils::input::options::Clipboard;
use zellij_utils::input::keybinds::Keybinds;
use zellij_utils::input::options::Clipboard;
use zellij_utils::pane_size::{Size, SizeInPixels};
use zellij_utils::{
consts::{session_info_folder_for_session, ZELLIJ_SOCK_DIR},
Expand Down Expand Up @@ -2153,7 +2153,10 @@ impl Screen {
}
pub fn rebind_keys(&mut self, new_keybinds: Keybinds, client_id: ClientId) -> Result<()> {
if self.connected_clients_contains(&client_id) {
let mode_info = self.mode_info.entry(client_id).or_insert_with(|| self.default_mode_info.clone()) ;
let mode_info = self
.mode_info
.entry(client_id)
.or_insert_with(|| self.default_mode_info.clone());
mode_info.update_keybinds(new_keybinds);
for tab in self.tabs.values_mut() {
tab.change_mode_info(mode_info.clone(), client_id);
Expand Down Expand Up @@ -4026,7 +4029,7 @@ pub(crate) fn screen_thread_main(
},
ScreenInstruction::RebindKeys(new_keybinds, client_id) => {
screen.rebind_keys(new_keybinds, client_id).non_fatal();
}
},
}
}
Ok(())
Expand Down
20 changes: 9 additions & 11 deletions zellij-server/src/unit/screen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2477,17 +2477,15 @@ pub fn send_cli_switch_mode_action() {
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);

let switch_mode_action =
received_server_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
ServerInstruction::ChangeModeForAllClients(..) => true,
_ => false,
}).cloned();


let switch_mode_action = received_server_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
ServerInstruction::ChangeModeForAllClients(..) => true,
_ => false,
})
.cloned();

assert_snapshot!(format!("{:?}", switch_mode_action));
}
Expand Down
6 changes: 2 additions & 4 deletions zellij-utils/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::input::actions::Action;
use crate::input::config::ConversionError;
use crate::input::layout::SplitSize;
use crate::input::keybinds::Keybinds;
use crate::input::layout::SplitSize;
use clap::ArgEnum;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
Expand Down Expand Up @@ -936,9 +936,7 @@ impl PermissionType {
PermissionType::MessageAndLaunchOtherPlugins => {
"Send messages to and launch other plugins".to_owned()
},
PermissionType::RebindKeys => {
"Rebind keys".to_owned()
}
PermissionType::RebindKeys => "Rebind keys".to_owned(),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions zellij-utils/src/ipc.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! IPC stuff for starting to split things into a client and server model.
use crate::{
cli::CliArgs,
data::{ClientId, ConnectToSession, Style, KeyWithModifier},
data::{ClientId, ConnectToSession, KeyWithModifier, Style},
errors::{get_current_ctx, prelude::*, ErrorContext},
input::keybinds::Keybinds,
input::{actions::Action, layout::Layout, options::Options, plugins::PluginAliases},
Expand Down Expand Up @@ -85,7 +85,7 @@ pub enum ClientToServerMsg {
Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
),
Action(Action, Option<u32>, Option<ClientId>), // u32 is the terminal id
Key(KeyWithModifier, Vec<u8>, bool), // key, raw_bytes, is_kitty_keyboard_protocol
Key(KeyWithModifier, Vec<u8>, bool), // key, raw_bytes, is_kitty_keyboard_protocol
ClientExited,
KillSession,
ConnStatus,
Expand Down
6 changes: 5 additions & 1 deletion zellij-utils/src/kdl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1880,7 +1880,11 @@ impl Keybinds {
}
Ok(input_mode_keybinds)
}
pub fn from_string(stringified_keybindings: String, base_keybinds: Keybinds, config_options: &Options) -> Result<Self, ConfigError> {
pub fn from_string(
stringified_keybindings: String,
base_keybinds: Keybinds,
config_options: &Options,
) -> Result<Self, ConfigError> {
let document: KdlDocument = stringified_keybindings.parse()?;
if let Some(kdl_keybinds) = document.get("keybinds") {
Keybinds::from_kdl(&kdl_keybinds, base_keybinds, config_options)
Expand Down
10 changes: 4 additions & 6 deletions zellij-utils/src/plugin_api/plugin_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,12 +1426,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
)),
})
},
PluginCommand::RebindKeys(rebind_keys_payload) => {
Ok(ProtobufPluginCommand {
name: CommandName::RebindKeys as i32,
payload: Some(Payload::RebindKeysPayload(rebind_keys_payload)),
})
},
PluginCommand::RebindKeys(rebind_keys_payload) => Ok(ProtobufPluginCommand {
name: CommandName::RebindKeys as i32,
payload: Some(Payload::RebindKeysPayload(rebind_keys_payload)),
}),
}
}
}
2 changes: 1 addition & 1 deletion zellij-utils/src/plugin_api/plugin_permission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl TryFrom<ProtobufPermissionType> for PermissionType {
ProtobufPermissionType::MessageAndLaunchOtherPlugins => {
Ok(PermissionType::MessageAndLaunchOtherPlugins)
},
ProtobufPermissionType::RebindKeys=> Ok(PermissionType::RebindKeys),
ProtobufPermissionType::RebindKeys => Ok(PermissionType::RebindKeys),
}
}
}
Expand Down
Loading