aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-08-19 19:02:52 +0200
committerGitHub <noreply@github.com>2024-08-19 19:02:52 +0200
commitcfbc0ff49036edeeb25e52eb67948de7dfe5590b (patch)
tree284e2900b54f0d816ed7dd7cfdefc8485c61882e
parentdocs(changelog): first run setup wizard (diff)
downloadsl0ppij-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.rs64
-rw-r--r--zellij-server/src/lib.rs75
-rw-r--r--zellij-utils/src/input/config.rs2
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)