diff --git a/CHANGELOG.md b/CHANGELOG.md index 203624fe..20df64f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its - Config emitting errors for nonexistent import paths - Kitty keyboard protocol reporting shifted key codes - Broken search with words broken across line boundary on the first character +- Config import changes not being live reloaded ## 0.13.2 diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs index 53cff1c9..3f73f120 100644 --- a/alacritty/src/config/monitor.rs +++ b/alacritty/src/config/monitor.rs @@ -1,3 +1,5 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::sync::mpsc::{self, RecvTimeoutError, Sender}; use std::thread::JoinHandle; @@ -23,6 +25,7 @@ const FALLBACK_POLLING_TIMEOUT: Duration = Duration::from_secs(1); pub struct ConfigMonitor { thread: JoinHandle<()>, shutdown_tx: Sender>, + watched_hash: Option, } impl ConfigMonitor { @@ -32,6 +35,9 @@ impl ConfigMonitor { return None; } + // Calculate the hash for the unmodified list of paths. + let watched_hash = Self::hash_paths(&paths); + // Exclude char devices like `/dev/null`, sockets, and so on, by checking that file type is // a regular file. paths.retain(|path| { @@ -139,7 +145,7 @@ impl ConfigMonitor { } }); - Some(Self { thread: join_handle, shutdown_tx: tx }) + Some(Self { watched_hash, thread: join_handle, shutdown_tx: tx }) } /// Synchronously shut down the monitor. @@ -154,4 +160,33 @@ impl ConfigMonitor { warn!("config monitor shutdown failed: {err:?}"); } } + + /// Check if the config monitor needs to be restarted. + /// + /// This checks the supplied list of files against the monitored files to determine if a + /// restart is necessary. + pub fn needs_restart(&self, files: &[PathBuf]) -> bool { + Self::hash_paths(files).map_or(true, |hash| Some(hash) == self.watched_hash) + } + + /// Generate the hash for a list of paths. + fn hash_paths(files: &[PathBuf]) -> Option { + // Use file count limit to avoid allocations. + const MAX_PATHS: usize = 1024; + if files.len() > MAX_PATHS { + return None; + } + + // Sort files to avoid restart on order change. + let mut sorted_files = [None; MAX_PATHS]; + for (i, file) in files.iter().enumerate() { + sorted_files[i] = Some(file); + } + sorted_files.sort_unstable(); + + // Calculate hash for the paths, regardless of order. + let mut hasher = DefaultHasher::new(); + Hash::hash_slice(&sorted_files, &mut hasher); + Some(hasher.finish()) + } } diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 8da758df..72009d88 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -1,5 +1,6 @@ //! Process window events. +use crate::ConfigMonitor; use std::borrow::Cow; use std::cmp::min; use std::collections::{HashMap, HashSet, VecDeque}; @@ -70,6 +71,8 @@ const TOUCH_ZOOM_FACTOR: f32 = 0.01; /// Stores some state from received events and dispatches actions when they are /// triggered. pub struct Processor { + pub config_monitor: Option, + clipboard: Clipboard, scheduler: Scheduler, initial_window_options: Option, @@ -101,6 +104,16 @@ impl Processor { // which is done in `loop_exiting`. let clipboard = unsafe { Clipboard::new(event_loop.display_handle().unwrap().as_raw()) }; + // Create a config monitor. + // + // The monitor watches the config file for changes and reloads it. Pending + // config changes are processed in the main loop. + let mut config_monitor = None; + if config.live_config_reload { + config_monitor = + ConfigMonitor::new(config.config_paths.clone(), event_loop.create_proxy()); + } + Processor { initial_window_options, initial_window_error: None, @@ -113,6 +126,7 @@ impl Processor { windows: Default::default(), #[cfg(unix)] global_ipc_options: Default::default(), + config_monitor, } } @@ -160,8 +174,8 @@ impl Processor { /// Run the event loop. /// /// The result is exit code generate from the loop. - pub fn run(mut self, event_loop: EventLoop) -> Result<(), Box> { - let result = event_loop.run_app(&mut self); + pub fn run(&mut self, event_loop: EventLoop) -> Result<(), Box> { + let result = event_loop.run_app(self); if let Some(initial_window_error) = self.initial_window_error.take() { Err(initial_window_error) } else { @@ -297,6 +311,17 @@ impl ApplicationHandler for Processor { if let Ok(config) = config::reload(path, &mut self.cli_options) { self.config = Rc::new(config); + // Restart config monitor if imports changed. + if let Some(monitor) = self.config_monitor.take() { + let paths = &self.config.config_paths; + self.config_monitor = if monitor.needs_restart(paths) { + monitor.shutdown(); + ConfigMonitor::new(paths.clone(), self.proxy.clone()) + } else { + Some(monitor) + }; + } + for window_context in self.windows.values_mut() { window_context.update_config(self.config.clone()); } diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 6219dd78..da50c3e4 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -172,16 +172,6 @@ fn alacritty(mut options: Options) -> Result<(), Box> { #[cfg(target_os = "macos")] locale::set_locale_environment(); - // Create a config monitor when config was loaded from path. - // - // The monitor watches the config file for changes and reloads it. Pending - // config changes are processed in the main loop. - let mut config_monitor = None; - if config.live_config_reload { - config_monitor = - ConfigMonitor::new(config.config_paths.clone(), window_event_loop.create_proxy()); - } - // Create the IPC socket listener. #[cfg(unix)] let socket_path = if config.ipc_socket { @@ -199,7 +189,7 @@ fn alacritty(mut options: Options) -> Result<(), Box> { }; // Event processor. - let processor = Processor::new(config, options, &window_event_loop); + let mut processor = Processor::new(config, options, &window_event_loop); // Start event loop and block until shutdown. let result = processor.run(window_event_loop); @@ -219,7 +209,7 @@ fn alacritty(mut options: Options) -> Result<(), Box> { // FIXME: Change PTY API to enforce the correct drop order with the typesystem. // Terminate the config monitor. - if let Some(config_monitor) = config_monitor.take() { + if let Some(config_monitor) = processor.config_monitor.take() { config_monitor.shutdown(); }