diff options
| author | Aram Drevekenin <aram@poor.dev> | 2024-08-19 19:02:52 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-08-19 19:02:52 +0200 |
| commit | cfbc0ff49036edeeb25e52eb67948de7dfe5590b (patch) | |
| tree | 284e2900b54f0d816ed7dd7cfdefc8485c61882e | |
| parent | docs(changelog): first run setup wizard (diff) | |
| download | sl0ppij-cfbc0ff49036edeeb25e52eb67948de7dfe5590b.tar.gz sl0ppij-cfbc0ff49036edeeb25e52eb67948de7dfe5590b.tar.xz sl0ppij-cfbc0ff49036edeeb25e52eb67948de7dfe5590b.zip | |
feat(ux): reload config at runtime (#3558)
* feat(ux): reload config at runtime
* style(fmt): rustfmt
| -rw-r--r-- | zellij-client/src/lib.rs | 64 | ||||
| -rw-r--r-- | zellij-server/src/lib.rs | 75 | ||||
| -rw-r--r-- | zellij-utils/src/input/config.rs | 2 |
3 files changed, 107 insertions, 34 deletions
diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 9146e755..660fbf0c 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -12,12 +12,14 @@ use log::info; use std::env::current_exe; use std::io::{self, Write}; use std::path::Path; -use std::path::PathBuf; use std::process::Command; use std::sync::{Arc, Mutex}; use std::thread; use zellij_utils::errors::FatalError; +use zellij_utils::notify_debouncer_full::notify::{self, Event, RecursiveMode, Watcher}; +use zellij_utils::setup::Setup; + use crate::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser, SyncOutput}; use crate::{ command_is_executing::CommandIsExecuting, input_handler::input_loop, @@ -357,7 +359,13 @@ pub fn start_client( .name("signal_listener".to_string()) .spawn({ let os_input = os_input.clone(); + let opts = opts.clone(); move || { + // we keep the config_file_watcher here so that it is only dropped when this thread + // exits (which is when the client disconnects/detaches), once it's dropped it + // stops watching and we want it to keep watching the config file path for changes + // as long as the client is alive + let _config_file_watcher = report_changes_in_config_file(&opts, &os_input); os_input.handle_signals( Box::new({ let os_api = os_input.clone(); @@ -660,3 +668,57 @@ pub fn start_server_detached( os_input.connect_to_server(&*ipc_pipe); os_input.send_to_server(first_msg); } + +fn report_changes_in_config_file( + opts: &CliArgs, + os_input: &Box<dyn ClientOsApi>, +) -> Option<Box<dyn Watcher>> { + match Config::config_file_path(&opts) { + Some(config_file_path) => { + let mut watcher = notify::recommended_watcher({ + let os_input = os_input.clone(); + let opts = opts.clone(); + let config_file_path = config_file_path.clone(); + move |res: Result<Event, _>| match res { + Ok(event) + if (event.kind.is_create() || event.kind.is_modify()) + && event.paths.contains(&config_file_path) => + { + match Setup::from_cli_args(&opts) { + Ok(( + new_config, + _layout, + _config_options, + _config_without_layout, + _config_options_without_layout, + )) => { + os_input.send_to_server(ClientToServerMsg::ConfigWrittenToDisk( + new_config, + )); + }, + Err(e) => { + log::error!("Failed to reload config: {}", e); + }, + } + }, + Err(e) => log::error!("watch error: {:?}", e), + _ => {}, + } + }) + .unwrap(); + if let Some(config_file_parent_folder) = config_file_path.parent() { + watcher + .watch(&config_file_parent_folder, RecursiveMode::Recursive) + .unwrap(); + Some(Box::new(watcher)) + } else { + log::error!("Could not find config parent folder"); + None + } + }, + None => { + log::error!("Failed to find config path"); + None + }, + } +} diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index b9b23f6c..fe1b3ada 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -157,24 +157,29 @@ impl ErrorInstruction for ServerInstruction { #[derive(Debug, Clone, Default)] pub(crate) struct SessionConfiguration { runtime_config: HashMap<ClientId, Config>, // if present, overrides the saved_config - saved_config: HashMap<ClientId, Config>, // config guaranteed to have been saved to disk + saved_config: HashMap<ClientId, Config>, // the config as it is on disk (not guaranteed), + // when changed, this resets the runtime config to + // be identical to it and override any previous + // changes } impl SessionConfiguration { - pub fn new_saved_config(&mut self, client_id: ClientId, mut new_saved_config: Config) { + pub fn new_saved_config( + &mut self, + client_id: ClientId, + new_saved_config: Config, + ) -> Vec<(ClientId, Config)> { self.saved_config .insert(client_id, new_saved_config.clone()); - if let Some(runtime_config) = self.runtime_config.get_mut(&client_id) { - match new_saved_config.merge(runtime_config.clone()) { - Ok(_) => { - *runtime_config = new_saved_config; - }, - Err(e) => { - log::error!("Failed to update runtime config: {}", e); - }, + + let mut config_changes = vec![]; + for (client_id, current_runtime_config) in self.runtime_config.iter_mut() { + if *current_runtime_config != new_saved_config { + *current_runtime_config = new_saved_config.clone(); + config_changes.push((*client_id, new_saved_config.clone())) } } - // TODO: handle change by propagating to all the relevant places + config_changes } pub fn set_client_saved_configuration(&mut self, client_id: ClientId, client_config: Config) { self.saved_config.insert(client_id, client_config); @@ -255,6 +260,24 @@ impl SessionMetaData { self.current_input_modes.insert(client_id, input_mode); } } + pub fn propagate_configuration_changes(&mut self, config_changes: Vec<(ClientId, Config)>) { + for (client_id, new_config) in config_changes { + self.senders + .send_to_screen(ScreenInstruction::Reconfigure { + client_id, + keybinds: Some(new_config.keybinds.clone()), + default_mode: new_config.options.default_mode, + }) + .unwrap(); + self.senders + .send_to_plugin(PluginInstruction::Reconfigure { + client_id, + keybinds: Some(new_config.keybinds), + default_mode: new_config.options.default_mode, + }) + .unwrap(); + } + } } impl Drop for SessionMetaData { @@ -1032,38 +1055,26 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { session_data .write() .unwrap() - .as_ref() + .as_mut() .unwrap() - .senders - .send_to_screen(ScreenInstruction::Reconfigure { - client_id, - keybinds: Some(new_config.keybinds.clone()), - default_mode: new_config.options.default_mode, - }) - .unwrap(); - session_data - .write() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_plugin(PluginInstruction::Reconfigure { - client_id, - keybinds: Some(new_config.keybinds), - default_mode: new_config.options.default_mode, - }) - .unwrap(); + .propagate_configuration_changes(vec![(client_id, new_config)]); } } }, ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => { - session_data + let changes = session_data .write() .unwrap() .as_mut() .unwrap() .session_configuration .new_saved_config(client_id, new_config); + session_data + .write() + .unwrap() + .as_mut() + .unwrap() + .propagate_configuration_changes(changes); }, ServerInstruction::FailedToWriteConfigToDisk(client_id, file_path) => { session_data diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 92174eb2..cf3ba2cc 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -234,7 +234,7 @@ impl Config { self.env = self.env.merge(other.env); Ok(()) } - fn config_file_path(opts: &CliArgs) -> Option<PathBuf> { + pub fn config_file_path(opts: &CliArgs) -> Option<PathBuf> { opts.config_dir .clone() .or_else(home::find_default_config_dir) |
