From 2980d16da3fa3cf170cb53e4153c0b9ec0bb0ce5 Mon Sep 17 00:00:00 2001 From: orphen Date: Sat, 25 May 2024 03:57:04 +0900 Subject: [PATCH] feat: fix hot loading for config.toml changes on Linux (#2498) --- src/cmd/serve.rs | 47 +++++++++++++++++++++++++++-------------------- src/fs_utils.rs | 6 ++++++ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index de139542..00954706 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -46,7 +46,6 @@ use notify_debouncer_full::{new_debouncer, notify::RecursiveMode, notify::Watche use ws::{Message, Sender, WebSocket}; use errors::{anyhow, Context, Error, Result}; -use pathdiff::diff_paths; use site::sass::compile_sass; use site::{Site, SITE_CONTENT}; use utils::fs::{clean_site_output_folder, copy_file}; @@ -458,18 +457,21 @@ pub fn serve( } let config_path = PathBuf::from(config_file); - let config_path_rel = diff_paths(&config_path, root_dir).unwrap_or_else(|| config_path.clone()); + let root_dir_str = root_dir.to_str().expect("Project root dir is not valid UTF-8."); - // An array of (path, WatchMode) where the path should be watched for changes, - // and the WatchMode value indicates whether this file/folder must exist for - // zola serve to operate + // An array of (path, WatchMode, RecursiveMode) where the path is watched for changes, + // the WatchMode value indicates whether this path must exist for zola serve to operate, + // and the RecursiveMode value indicates whether to watch nested directories. let watch_this = vec![ - (config_path_rel.to_str().unwrap_or("config.toml"), WatchMode::Required), - ("content", WatchMode::Required), - ("sass", WatchMode::Condition(site.config.compile_sass)), - ("static", WatchMode::Optional), - ("templates", WatchMode::Optional), - ("themes", WatchMode::Condition(site.config.theme.is_some())), + // The first entry is ultimtely to watch config.toml in a more robust manner on Linux when + // the file changes by way of a caching strategy used by editors such as vim. + // https://github.com/getzola/zola/issues/2266 + (root_dir_str, WatchMode::Required, RecursiveMode::NonRecursive), + ("content", WatchMode::Required, RecursiveMode::Recursive), + ("sass", WatchMode::Condition(site.config.compile_sass), RecursiveMode::Recursive), + ("static", WatchMode::Optional, RecursiveMode::Recursive), + ("templates", WatchMode::Optional, RecursiveMode::Recursive), + ("themes", WatchMode::Condition(site.config.theme.is_some()), RecursiveMode::Recursive), ]; // Setup watchers @@ -482,16 +484,16 @@ pub fn serve( // - the path exists but has incorrect permissions // watchers will contain the paths we're actually watching let mut watchers = Vec::new(); - for (entry, mode) in watch_this { + for (entry, watch_mode, recursive_mode) in watch_this { let watch_path = root_dir.join(entry); - let should_watch = match mode { + let should_watch = match watch_mode { WatchMode::Required => true, WatchMode::Optional => watch_path.exists(), WatchMode::Condition(b) => b && watch_path.exists(), }; if should_watch { debouncer.watcher() - .watch(&root_dir.join(entry), RecursiveMode::Recursive) + .watch(&root_dir.join(entry), recursive_mode) .with_context(|| format!("Can't watch `{}` for changes in folder `{}`. Does it exist, and do you have correct permissions?", entry, root_dir.display()))?; watchers.push(entry.to_string()); } @@ -575,12 +577,17 @@ pub fn serve( broadcaster }; - println!( - "Listening for changes in {}{}{{{}}}", - root_dir.display(), - MAIN_SEPARATOR, - watchers.join(",") - ); + // We watch for changes in the config by monitoring its parent directory, but we ignore all + // ordinary peer files. Map the parent directory back to the config file name to not confuse + // the end user. + let config_name = + config_path.file_name().unwrap().to_str().expect("Config name is not valid UTF-8."); + let watch_list = watchers + .iter() + .map(|w| if w == root_dir_str { config_name } else { w }) + .collect::>() + .join(","); + println!("Listening for changes in {}{}{{{}}}", root_dir.display(), MAIN_SEPARATOR, watch_list); let preserve_dotfiles_in_output = site.config.preserve_dotfiles_in_output; diff --git a/src/fs_utils.rs b/src/fs_utils.rs index b788b56b..1f70496c 100644 --- a/src/fs_utils.rs +++ b/src/fs_utils.rs @@ -96,6 +96,12 @@ pub fn filter_events( continue; } + // Ignore ordinary files peer to config.toml. This assumes all other files we care + // about are nested more deeply than config.toml or are directories peer to config.toml. + if path != config_path && path.is_file() && path.parent() == config_path.parent() { + continue; + } + let (change_k, partial_p) = detect_change_kind(root_dir, &path, config_path); meaningful_events.insert(path, (partial_p, simple_kind.unwrap(), change_k)); }