1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

Restart config monitor on import change

This patch checks the hash of the import paths on every config change
and restarts the config monitor whenever the current monitor's hash
diverges from the updated config's list of imports.

Closes #7981.
This commit is contained in:
Christian Duerr 2024-07-21 10:49:47 +02:00 committed by GitHub
parent c3075f189c
commit 6bd1674bd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 15 deletions

View file

@ -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

View file

@ -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<Result<NotifyEvent, NotifyError>>,
watched_hash: Option<u64>,
}
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<u64> {
// 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())
}
}

View file

@ -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<ConfigMonitor>,
clipboard: Clipboard,
scheduler: Scheduler,
initial_window_options: Option<WindowOptions>,
@ -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<Event>) -> Result<(), Box<dyn Error>> {
let result = event_loop.run_app(&mut self);
pub fn run(&mut self, event_loop: EventLoop<Event>) -> Result<(), Box<dyn Error>> {
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<Event> 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());
}

View file

@ -172,16 +172,6 @@ fn alacritty(mut options: Options) -> Result<(), Box<dyn Error>> {
#[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<dyn Error>> {
};
// 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<dyn Error>> {
// 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();
}