diff options
| author | Aram Drevekenin <aram@poor.dev> | 2024-07-17 16:08:31 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-17 16:08:31 +0200 |
| commit | 84ff29dd024c73e94f3e65d56d907ae903fe3752 (patch) | |
| tree | b3f01370b3b1af71f018eeb27a1437b75e959721 /zellij-server | |
| parent | docs(changelog): scroll region fix (diff) | |
| download | sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.gz sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.tar.xz sl0ppij-84ff29dd024c73e94f3e65d56d907ae903fe3752.zip | |
feat(ui): configuration screen + configure non-colliding keys at runtime (#3502)
* rebind => reconfigure
* persist keybinds and mode info to new tabs
* add configuration plugin
* make tests pass
* remove warnings
* style(fmt): rustfmt
Diffstat (limited to 'zellij-server')
13 files changed, 283 insertions, 47 deletions
diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 9a3231b3..fbde2f86 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -97,7 +97,7 @@ pub enum ServerInstruction { DisconnectAllClientsExcept(ClientId), ChangeMode(ClientId, InputMode), ChangeModeForAllClients(InputMode), - RebindKeys(ClientId, String), // String -> stringified keybindings + Reconfigure(ClientId, String), // String -> stringified configuration } impl From<&ServerInstruction> for ServerContext { @@ -129,7 +129,7 @@ impl From<&ServerInstruction> for ServerContext { ServerInstruction::ChangeModeForAllClients(..) => { ServerContext::ChangeModeForAllClients }, - ServerInstruction::RebindKeys(..) => ServerContext::RebindKeys, + ServerInstruction::Reconfigure(..) => ServerContext::Reconfigure, } } } @@ -149,7 +149,7 @@ pub(crate) struct SessionMetaData { pub config_options: Box<Options>, pub client_keybinds: HashMap<ClientId, Keybinds>, pub client_input_modes: HashMap<ClientId, InputMode>, - pub default_mode: InputMode, + pub default_mode: HashMap<ClientId, InputMode>, screen_thread: Option<thread::JoinHandle<()>>, pty_thread: Option<thread::JoinHandle<()>>, plugin_thread: Option<thread::JoinHandle<()>>, @@ -570,6 +570,10 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { default_mode, &attrs, session_data.capabilities, + session_data + .client_keybinds + .get(&client_id) + .unwrap_or(&session_data.client_attributes.keybinds), Some(default_mode), ); session_data @@ -911,24 +915,57 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { .unwrap() .change_mode_for_all_clients(input_mode); }, - ServerInstruction::RebindKeys(client_id, new_keybinds) => { + ServerInstruction::Reconfigure(client_id, new_config) => { + let mut new_default_mode = None; + match Options::from_string(&new_config) { + Ok(mut new_config_options) => { + if let Some(default_mode) = new_config_options.default_mode.take() { + new_default_mode = Some(default_mode); + session_data + .write() + .unwrap() + .as_mut() + .unwrap() + .default_mode + .insert(client_id, default_mode); + } + }, + Err(e) => { + log::error!("Failed to parse config: {}", e); + }, + } + let new_keybinds = session_data .write() .unwrap() .as_mut() .unwrap() - .rebind_keys(client_id, new_keybinds) + .rebind_keys(client_id, new_config) .clone(); - if let Some(new_keybinds) = new_keybinds { - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::RebindKeys(new_keybinds, client_id)) - .unwrap(); - } + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::Reconfigure { + client_id, + keybinds: new_keybinds.clone(), + default_mode: new_default_mode, + }) + .unwrap(); + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_plugin(PluginInstruction::Reconfigure { + client_id, + keybinds: new_keybinds, + default_mode: new_default_mode, + }) + .unwrap(); }, } } @@ -1167,7 +1204,7 @@ fn init_session( plugin_thread: Some(plugin_thread), pty_writer_thread: Some(pty_writer_thread), background_jobs_thread: Some(background_jobs_thread), - default_mode, + default_mode: HashMap::new(), } } diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs index 639a389e..223ecb55 100644 --- a/zellij-server/src/plugins/mod.rs +++ b/zellij-server/src/plugins/mod.rs @@ -31,6 +31,7 @@ use zellij_utils::{ errors::{prelude::*, ContextType, PluginContext}, input::{ command::TerminalAction, + keybinds::Keybinds, layout::{FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginOrAlias, TiledPaneLayout}, plugins::PluginAliases, }, @@ -142,6 +143,11 @@ pub enum PluginInstruction { message: MessageToPlugin, }, UnblockCliPipes(Vec<PluginRenderAsset>), + Reconfigure { + client_id: ClientId, + keybinds: Option<Keybinds>, + default_mode: Option<InputMode>, + }, WatchFilesystem, Exit, } @@ -182,6 +188,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::WatchFilesystem => PluginContext::WatchFilesystem, PluginInstruction::KeybindPipe { .. } => PluginContext::KeybindPipe, PluginInstruction::DumpLayoutToPlugin(..) => PluginContext::DumpLayoutToPlugin, + PluginInstruction::Reconfigure { .. } => PluginContext::Reconfigure, } } } @@ -734,6 +741,15 @@ pub(crate) fn plugin_thread_main( .context("failed to unblock input pipe"); } }, + PluginInstruction::Reconfigure { + client_id, + keybinds, + default_mode, + } => { + wasm_bridge + .reconfigure(client_id, keybinds, default_mode) + .non_fatal(); + }, PluginInstruction::WatchFilesystem => { wasm_bridge.start_fs_watcher_if_not_started(); }, diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 6ed36c05..51ca3e16 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -29,6 +29,7 @@ use zellij_utils::{ data::{InputMode, PluginCapabilities}, errors::prelude::*, input::command::TerminalAction, + input::keybinds::Keybinds, input::layout::Layout, input::plugins::PluginConfig, ipc::ClientAttributes, @@ -69,6 +70,7 @@ pub struct PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, } impl<'a> PluginLoader<'a> { @@ -89,6 +91,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: &HashMap<ClientId, Keybinds>, ) -> Result<()> { let err_context = || format!("failed to reload plugin {plugin_id} from memory"); let mut connected_clients: Vec<ClientId> = @@ -97,6 +100,7 @@ impl<'a> PluginLoader<'a> { return Err(anyhow!("No connected clients, cannot reload plugin")); } let first_client_id = connected_clients.remove(0); + let keybinds = keybinds.get(&first_client_id).cloned(); let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes( &plugin_cache, @@ -115,6 +119,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; plugin_loader .load_module_from_memory() @@ -152,6 +157,7 @@ impl<'a> PluginLoader<'a> { skip_cache: bool, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, ) -> Result<()> { let err_context = || format!("failed to start plugin {plugin_id} for client {client_id}"); let mut plugin_loader = PluginLoader::new( @@ -173,6 +179,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; if skip_cache { plugin_loader @@ -226,6 +233,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, ) -> Result<()> { let mut new_plugins = HashSet::new(); for plugin_id in plugin_map.lock().unwrap().plugin_ids() { @@ -249,6 +257,7 @@ impl<'a> PluginLoader<'a> { default_layout.clone(), layout_dir.clone(), default_mode, + keybinds.clone(), )?; plugin_loader .load_module_from_memory() @@ -278,6 +287,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: &HashMap<ClientId, Keybinds>, ) -> Result<()> { let err_context = || format!("failed to reload plugin id {plugin_id}"); @@ -287,6 +297,7 @@ impl<'a> PluginLoader<'a> { return Err(anyhow!("No connected clients, cannot reload plugin")); } let first_client_id = connected_clients.remove(0); + let keybinds = keybinds.get(&first_client_id).cloned(); let mut plugin_loader = PluginLoader::new_from_existing_plugin_attributes( &plugin_cache, @@ -305,6 +316,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, )?; plugin_loader .compile_module() @@ -338,6 +350,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, ) -> Result<Self> { let plugin_own_data_dir = ZELLIJ_SESSION_CACHE_DIR .join(Url::from(&plugin.location).to_string()) @@ -366,6 +379,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, }) } pub fn new_from_existing_plugin_attributes( @@ -385,6 +399,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, ) -> Result<Self> { let err_context = || "Failed to find existing plugin"; let (running_plugin, _subscriptions, _workers) = { @@ -420,6 +435,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, ) } pub fn new_from_different_client_id( @@ -439,6 +455,7 @@ impl<'a> PluginLoader<'a> { default_layout: Box<Layout>, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: Option<Keybinds>, ) -> Result<Self> { let err_context = || "Failed to find existing plugin"; let running_plugin = { @@ -475,6 +492,7 @@ impl<'a> PluginLoader<'a> { default_layout, layout_dir, default_mode, + keybinds, ) } pub fn load_module_from_memory(&mut self) -> Result<Module> { @@ -731,6 +749,7 @@ impl<'a> PluginLoader<'a> { self.default_layout.clone(), self.layout_dir.clone(), self.default_mode, + self.keybinds.clone(), )?; plugin_loader_for_client .load_module_from_memory() @@ -832,6 +851,11 @@ impl<'a> PluginLoader<'a> { layout_dir: self.layout_dir.clone(), default_mode: self.default_mode.clone(), subscriptions: Arc::new(Mutex::new(HashSet::new())), + keybinds: self + .keybinds + .as_ref() + .unwrap_or_else(|| &self.client_attributes.keybinds) + .clone(), stdin_pipe, stdout_pipe, }; diff --git a/zellij-server/src/plugins/plugin_map.rs b/zellij-server/src/plugins/plugin_map.rs index c5ac6d5f..6390841a 100644 --- a/zellij-server/src/plugins/plugin_map.rs +++ b/zellij-server/src/plugins/plugin_map.rs @@ -22,6 +22,7 @@ use zellij_utils::{ data::InputMode, data::PluginCapabilities, input::command::TerminalAction, + input::keybinds::Keybinds, input::layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}, input::plugins::PluginConfig, ipc::ClientAttributes, @@ -289,6 +290,7 @@ pub struct PluginEnv { pub subscriptions: Arc<Mutex<Subscriptions>>, pub stdin_pipe: Arc<Mutex<VecDeque<u8>>>, pub stdout_pipe: Arc<Mutex<VecDeque<u8>>>, + pub keybinds: Keybinds, } #[derive(Clone)] @@ -424,4 +426,10 @@ impl RunningPlugin { false } } + pub fn update_keybinds(&mut self, keybinds: Keybinds) { + self.store.data_mut().keybinds = keybinds; + } + pub fn update_default_mode(&mut self, default_mode: InputMode) { + self.store.data_mut().default_mode = default_mode; + } } diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index c1f4fcd5..5ef220db 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -6492,7 +6492,7 @@ pub fn disconnect_other_clients_plugins_command() { #[test] #[ignore] -pub fn rebind_keys_plugin_command() { +pub fn reconfigure_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()); @@ -6527,7 +6527,7 @@ pub fn rebind_keys_plugin_command() { let received_server_instruction = Arc::new(Mutex::new(vec![])); let server_thread = log_actions_in_thread!( received_server_instruction, - ServerInstruction::RebindKeys, + ServerInstruction::Reconfigure, server_receiver, 1 ); @@ -6555,20 +6555,20 @@ pub fn rebind_keys_plugin_command() { 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_keys_event = received_server_instruction + let reconfigure_event = received_server_instruction .lock() .unwrap() .iter() .rev() .find_map(|i| { - if let ServerInstruction::RebindKeys(..) = i { + if let ServerInstruction::Reconfigure(..) = i { Some(i.clone()) } else { None } }) .clone(); - assert_snapshot!(format!("{:#?}", rebind_keys_event)); + assert_snapshot!(format!("{:#?}", reconfigure_event)); } #[test] diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap index 465f1088..28c7dedc 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__granted_permission_request_result.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 5500 +assertion_line: 5505 expression: "format!(\"{:#?}\", permissions)" --- Some( @@ -14,6 +14,6 @@ Some( WebAccess, ReadCliPipes, MessageAndLaunchOtherPlugins, - RebindKeys, + Reconfigure, ], ) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap new file mode 100644 index 00000000..3f788d7d --- /dev/null +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__reconfigure_plugin_command.snap @@ -0,0 +1,11 @@ +--- +source: zellij-server/src/plugins/./unit/plugin_tests.rs +assertion_line: 6571 +expression: "format!(\"{:#?}\", reconfigure_event)" +--- +Some( + Reconfigure( + 1, + "\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ", + ), +) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap index a0fec410..75f47da7 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__request_plugin_permissions.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 5409 +assertion_line: 5414 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( @@ -16,6 +16,6 @@ Some( WebAccess, ReadCliPipes, MessageAndLaunchOtherPlugins, - RebindKeys, + Reconfigure, ], ) diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 2abc22d3..00a7795b 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -21,6 +21,7 @@ use zellij_utils::async_std::task::{self, JoinHandle}; use zellij_utils::consts::ZELLIJ_CACHE_DIR; use zellij_utils::data::{InputMode, PermissionStatus, PermissionType, PipeMessage, PipeSource}; use zellij_utils::downloader::Downloader; +use zellij_utils::input::keybinds::Keybinds; use zellij_utils::input::permission::PermissionCache; use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; use zellij_utils::plugin_api::event::ProtobufEvent; @@ -103,6 +104,7 @@ pub struct WasmBridge { pending_pipes: PendingPipes, layout_dir: Option<PathBuf>, default_mode: InputMode, + keybinds: HashMap<ClientId, Keybinds>, } impl WasmBridge { @@ -149,6 +151,7 @@ impl WasmBridge { pending_pipes: Default::default(), layout_dir, default_mode, + keybinds: HashMap::new(), } } pub fn load_plugin( @@ -206,6 +209,7 @@ impl WasmBridge { let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); let default_mode = self.default_mode; + let keybinds = self.keybinds.get(&client_id).cloned(); async move { let _ = senders.send_to_background_jobs( BackgroundJob::AnimatePluginLoading(plugin_id), @@ -254,6 +258,7 @@ impl WasmBridge { skip_cache, layout_dir, default_mode, + keybinds, ) { Ok(_) => handle_plugin_successful_loading(&senders, plugin_id), Err(e) => handle_plugin_loading_failure( @@ -346,6 +351,7 @@ impl WasmBridge { let default_layout = self.default_layout.clone(); let layout_dir = self.layout_dir.clone(); let default_mode = self.default_mode; + let keybinds = self.keybinds.clone(); async move { match PluginLoader::reload_plugin( first_plugin_id, @@ -364,6 +370,7 @@ impl WasmBridge { default_layout.clone(), layout_dir.clone(), default_mode, + &keybinds, ) { Ok(_) => { handle_plugin_successful_loading(&senders, first_plugin_id); @@ -390,6 +397,7 @@ impl WasmBridge { default_layout.clone(), layout_dir.clone(), default_mode, + &keybinds, ) { Ok(_) => handle_plugin_successful_loading(&senders, *plugin_id), Err(e) => handle_plugin_loading_failure( @@ -443,6 +451,7 @@ impl WasmBridge { self.default_layout.clone(), self.layout_dir.clone(), self.default_mode, + self.keybinds.get(&client_id).cloned(), ) { Ok(_) => { let _ = self @@ -795,6 +804,51 @@ impl WasmBridge { .unwrap() .run_plugin_of_plugin_id(plugin_id) } + + pub fn reconfigure( + &mut self, + client_id: ClientId, + keybinds: Option<Keybinds>, + default_mode: Option<InputMode>, + ) -> Result<()> { + let plugins_to_reconfigure: Vec<Arc<Mutex<RunningPlugin>>> = self + .plugin_map + .lock() + .unwrap() + .running_plugins() + .iter() + .cloned() + .filter_map(|(_plugin_id, c_id, running_plugin)| { + if c_id == client_id { + Some(running_plugin.clone()) + } else { + None + } + }) + .collect(); + if let Some(default_mode) = default_mode.as_ref() { + self.default_mode = *default_mode; + } + if let Some(keybinds) = keybinds.as_ref() { + self.keybinds.insert(client_id, keybinds.clone()); + } + for running_plugin in plugins_to_reconfigure { + task::spawn({ + let running_plugin = running_plugin.clone(); + let keybinds = keybinds.clone(); + async move { + let mut running_plugin = running_plugin.lock().unwrap(); + if let Some(keybinds) = keybinds { + running_plugin.update_keybinds(keybinds); + } + if let Some(default_mode) = default_mode { + running_plugin.update_default_mode(default_mode); + } + } + }); + } + Ok(()) + } fn apply_cached_events_and_resizes_for_plugin( &mut self, plugin_id: PluginId, @@ -1288,6 +1342,18 @@ pub fn apply_event_to_plugin( let err_context = || format!("Failed to apply event to plugin {plugin_id}"); match check_event_permission(running_plugin.store.data(), event) { (PermissionStatus::Granted, _) => { + let mut event = event.clone(); + if let Event::ModeUpdate(mode_info) = &mut event { + // we do this because there can be some cases where this event arrives here with + // the wrong keybindings or default mode (for example: when triggered from the CLI, + // where we do not know the target client_id and thus don't know if their keybindings are the + // default or if they have changed at runtime), the keybindings in running_plugin + // should always be up-to-date. Ideally, we would have changed the keybindings in + // ModeInfo to an Option, but alas - this is already part of our contract and that + // would be a breaking change. + mode_info.keybinds = running_plugin.store.data().keybinds.to_keybinds_vec(); + mode_info.base_mode = Some(running_plugin.store.data().default_mode); + } let protobuf_event: ProtobufEvent = event .clone() .try_into() diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index a517df06..21b8b01d 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -61,6 +61,7 @@ macro_rules! apply_action { $env.default_shell.clone(), $env.default_layout.clone(), None, + $env.keybinds.clone(), $env.default_mode.clone(), ) { log::error!("{}: {:?}", $error_message(), e); @@ -244,7 +245,7 @@ fn host_run_plugin_command(caller: Caller<'_, PluginEnv>) { PluginCommand::WatchFilesystem => watch_filesystem(env), PluginCommand::DumpSessionLayout => dump_session_layout(env), PluginCommand::CloseSelf => close_self(env), - PluginCommand::RebindKeys(new_keybinds) => rebind_keys(env, new_keybinds)?, + PluginCommand::Reconfigure(new_config) => reconfigure(env, new_config)?, }, (PermissionStatus::Denied, permission) => { log::error!( @@ -781,11 +782,11 @@ fn close_self(env: &PluginEnv) { .non_fatal(); } -fn rebind_keys(env: &PluginEnv, new_keybinds: String) -> Result<()> { - let err_context = || "Failed to rebind keys"; +fn reconfigure(env: &PluginEnv, new_config: String) -> Result<()> { + let err_context = || "Failed to reconfigure"; let client_id = env.client_id; env.senders - .send_to_server(ServerInstruction::RebindKeys(client_id, new_keybinds)) + .send_to_server(ServerInstruction::Reconfigure(client_id, new_config)) .with_context(err_context)?; Ok(()) } @@ -1446,7 +1447,7 @@ fn check_command_permission( | PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes, PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins, PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState, - PluginCommand::RebindKeys(..) => PermissionType::RebindKeys, + PluginCommand::Reconfigure(..) => PermissionType::Reconfigure, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 3092310a..ab5db049 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -19,6 +19,7 @@ use zellij_utils::{ actions::{Action, SearchDirection, SearchOption}, command::TerminalAction, get_mode_info, + keybinds::Keybinds, layout::Layout, }, ipc::{ @@ -38,6 +39,7 @@ pub(crate) fn route_action( default_shell: Option<TerminalAction>, default_layout: Box<Layout>, mut seen_cli_pipes: Option<&mut HashSet<String>>, + client_keybinds: Keybinds, default_mode: InputMode, ) -> Result<bool> { let mut should_break = false; @@ -99,7 +101,13 @@ pub(crate) fn route_action( .send_to_plugin(PluginInstruction::Update(vec![( None, Some(client_id), - Event::ModeUpdate(get_mode_info(mode, attrs, capabilities, Some(default_mode))), + Event::ModeUpdate(get_mode_info( + mode, + attrs, + capabilities, + &client_keybinds, + Some(default_mode), + )), )])) .with_context(err_context)?; senders @@ -107,7 +115,13 @@ pub(crate) fn route_action( .with_context(err_context)?; senders .send_to_screen(ScreenInstruction::ChangeMode( - get_mode_info(mode, attrs, capabilities, Some(default_mode)), + get_mode_info( + mode, + attrs, + capabilities, + &client_keybinds, + Some(default_mode), + ), client_id, )) .with_context(err_context)?; @@ -349,6 +363,7 @@ pub(crate) fn route_action( input_mode, attrs, capabilities, + &client_keybinds, Some(default_mode), )), )])) @@ -363,6 +378,7 @@ pub(crate) fn route_action( input_mode, attrs, capabilities, + &client_keybinds, Some(default_mode), ))) .with_context(err_context)?; @@ -1025,7 +1041,20 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), - rlocked_sessions.default_mode, + rlocked_sessions + .client_keybinds + .get(&client_id) + .unwrap_or( + &rlocked_sessions + .client_attributes + .keybinds, + ) + .clone(), + rlocked_sessions + .default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), )? { should_break = true; } @@ -1050,7 +1079,16 @@ pub(crate) fn route_thread_main( rlocked_sessions.default_shell.clone(), rlocked_sessions.layout.clone(), Some(&mut seen_cli_pipes), - rlocked_sessions.default_mode, + rlocked_sessions + .client_keybinds + .get(&client_id) + .unwrap_or(&rlocked_sessions.client_attributes.keybinds) + .clone(), + rlocked_sessions + .default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), )? { should_break = true; } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index e61b5d19..189c2571 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -361,7 +361,11 @@ pub enum ScreenInstruction { DumpLayoutToHd, RenameSession(String, ClientId), // String -> new name ListClientsMetadata(Option<PathBuf>, ClientId), // Option<PathBuf> - default shell - RebindKeys(Keybinds, ClientId), + Reconfigure { + client_id: ClientId, + keybinds: Option<Keybinds>, + default_mode: Option<InputMode>, + }, } impl From<&ScreenInstruction> for ScreenContext { @@ -546,7 +550,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd, ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession, ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata, - ScreenInstruction::RebindKeys(..) => ScreenContext::RebindKeys, + ScreenInstruction::Reconfigure { .. } => ScreenContext::Reconfigure, } } } @@ -1214,7 +1218,7 @@ impl Screen { let tab_name = tab_name.unwrap_or_else(|| String::new()); let position = self.tabs.len(); - let tab = Tab::new( + let mut tab = Tab::new( tab_index, position, tab_name, @@ -1245,6 +1249,9 @@ impl Screen { self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, ); + for (client_id, mode_info) in &self.mode_info { + tab.change_mode_info(mode_info.clone(), *client_id); + } self.tabs.insert(tab_index, tab); Ok(()) } @@ -2151,17 +2158,30 @@ impl Screen { } Ok(()) } - pub fn rebind_keys(&mut self, new_keybinds: Keybinds, client_id: ClientId) -> Result<()> { + pub fn reconfigure_mode_info( + &mut self, + new_keybinds: Option<Keybinds>, + new_default_mode: Option<InputMode>, + client_id: ClientId, + ) -> Result<()> { if self.connected_clients_contains(&client_id) { + let should_update_mode_info = new_keybinds.is_some() || new_default_mode.is_some(); 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); - tab.mark_active_pane_for_rerender(client_id); - tab.update_input_modes()?; + if let Some(new_keybinds) = new_keybinds { + mode_info.update_keybinds(new_keybinds); + } + if let Some(new_default_mode) = new_default_mode { + mode_info.update_default_mode(new_default_mode); + } + if should_update_mode_info { + for tab in self.tabs.values_mut() { + tab.change_mode_info(mode_info.clone(), client_id); + tab.mark_active_pane_for_rerender(client_id); + tab.update_input_modes()?; + } } } else { log::error!("Could not find client_id {client_id} to rebind keys"); @@ -2363,6 +2383,7 @@ pub(crate) fn screen_thread_main( // ¯\_(ツ)_/¯ arrow_fonts: !arrow_fonts, }, + &client_attributes.keybinds, config_options.default_mode, ), draw_pane_frames, @@ -4028,8 +4049,14 @@ pub(crate) fn screen_thread_main( } screen.unblock_input()?; }, - ScreenInstruction::RebindKeys(new_keybinds, client_id) => { - screen.rebind_keys(new_keybinds, client_id).non_fatal(); + ScreenInstruction::Reconfigure { + client_id, + keybinds, + default_mode, + } => { + screen + .reconfigure_mode_info(keybinds, default_mode, client_id) + .non_fatal(); }, } } diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 5f7f0a66..09d2d794 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -114,6 +114,7 @@ fn send_cli_action_to_server( let get_current_dir = || PathBuf::from("."); let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap(); let senders = session_metadata.senders.clone(); + let client_keybinds = session_metadata.client_keybinds.clone(); let default_mode = session_metadata.default_mode.clone(); let capabilities = PluginCapabilities::default(); let client_attributes = ClientAttributes::default(); @@ -130,7 +131,14 @@ fn send_cli_action_to_server( default_shell.clone(), default_layout.clone(), None, - default_mode, + client_keybinds + .get(&client_id) + .unwrap_or(&session_metadata.client_attributes.keybinds) + .clone(), + default_mode + .get(&client_id) + .unwrap_or(&InputMode::Normal) + .clone(), ) .unwrap(); } @@ -578,7 +586,7 @@ impl MockScreen { layout, client_input_modes: HashMap::new(), client_keybinds: HashMap::new(), - default_mode: InputMode::Normal, + default_mode: HashMap::new(), }; let os_input = FakeInputOutput::default(); |
