feat(ux): reload config at runtime (#3558)
* feat(ux): reload config at runtime * style(fmt): rustfmt
This commit is contained in:
parent
d76c4e5e49
commit
cfbc0ff490
3 changed files with 107 additions and 34 deletions
|
|
@ -12,12 +12,14 @@ use log::info;
|
||||||
use std::env::current_exe;
|
use std::env::current_exe;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use zellij_utils::errors::FatalError;
|
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::stdin_ansi_parser::{AnsiStdinInstruction, StdinAnsiParser, SyncOutput};
|
||||||
use crate::{
|
use crate::{
|
||||||
command_is_executing::CommandIsExecuting, input_handler::input_loop,
|
command_is_executing::CommandIsExecuting, input_handler::input_loop,
|
||||||
|
|
@ -357,7 +359,13 @@ pub fn start_client(
|
||||||
.name("signal_listener".to_string())
|
.name("signal_listener".to_string())
|
||||||
.spawn({
|
.spawn({
|
||||||
let os_input = os_input.clone();
|
let os_input = os_input.clone();
|
||||||
|
let opts = opts.clone();
|
||||||
move || {
|
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(
|
os_input.handle_signals(
|
||||||
Box::new({
|
Box::new({
|
||||||
let os_api = os_input.clone();
|
let os_api = os_input.clone();
|
||||||
|
|
@ -660,3 +668,57 @@ pub fn start_server_detached(
|
||||||
os_input.connect_to_server(&*ipc_pipe);
|
os_input.connect_to_server(&*ipc_pipe);
|
||||||
os_input.send_to_server(first_msg);
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,24 +157,29 @@ impl ErrorInstruction for ServerInstruction {
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub(crate) struct SessionConfiguration {
|
pub(crate) struct SessionConfiguration {
|
||||||
runtime_config: HashMap<ClientId, Config>, // if present, overrides the saved_config
|
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 {
|
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
|
self.saved_config
|
||||||
.insert(client_id, new_saved_config.clone());
|
.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()) {
|
let mut config_changes = vec![];
|
||||||
Ok(_) => {
|
for (client_id, current_runtime_config) in self.runtime_config.iter_mut() {
|
||||||
*runtime_config = new_saved_config;
|
if *current_runtime_config != new_saved_config {
|
||||||
},
|
*current_runtime_config = new_saved_config.clone();
|
||||||
Err(e) => {
|
config_changes.push((*client_id, new_saved_config.clone()))
|
||||||
log::error!("Failed to update runtime config: {}", e);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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) {
|
pub fn set_client_saved_configuration(&mut self, client_id: ClientId, client_config: Config) {
|
||||||
self.saved_config.insert(client_id, client_config);
|
self.saved_config.insert(client_id, client_config);
|
||||||
|
|
@ -255,6 +260,24 @@ impl SessionMetaData {
|
||||||
self.current_input_modes.insert(client_id, input_mode);
|
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 {
|
impl Drop for SessionMetaData {
|
||||||
|
|
@ -1032,38 +1055,26 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||||
session_data
|
session_data
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_ref()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.senders
|
.propagate_configuration_changes(vec![(client_id, new_config)]);
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => {
|
ServerInstruction::ConfigWrittenToDisk(client_id, new_config) => {
|
||||||
session_data
|
let changes = session_data
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.session_configuration
|
.session_configuration
|
||||||
.new_saved_config(client_id, new_config);
|
.new_saved_config(client_id, new_config);
|
||||||
|
session_data
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.propagate_configuration_changes(changes);
|
||||||
},
|
},
|
||||||
ServerInstruction::FailedToWriteConfigToDisk(client_id, file_path) => {
|
ServerInstruction::FailedToWriteConfigToDisk(client_id, file_path) => {
|
||||||
session_data
|
session_data
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ impl Config {
|
||||||
self.env = self.env.merge(other.env);
|
self.env = self.env.merge(other.env);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn config_file_path(opts: &CliArgs) -> Option<PathBuf> {
|
pub fn config_file_path(opts: &CliArgs) -> Option<PathBuf> {
|
||||||
opts.config_dir
|
opts.config_dir
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(home::find_default_config_dir)
|
.or_else(home::find_default_config_dir)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue