From 5a67ac8b8ead666fe2ab19ad133fe3b7fe724493 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 28 Apr 2026 03:04:46 +0300 Subject: [PATCH 1/2] Ignore hidden and editor temporary files in livereload file watcher Filter out files that match common editor temporary/backup file patterns in the livereload file watcher callback: - Files starting with '.' (dotfiles, vim swap files like .foo.md.swp) - Files ending with '~' (backup files) - Files matching '#*#' (Emacs auto-save files) This prevents live reload from being triggered repeatedly by editor temporary files, which was especially problematic for vim users where editing .md files creates .foo.md.swp files. Fixes #2519 --- mkdocs/livereload/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mkdocs/livereload/__init__.py b/mkdocs/livereload/__init__.py index 8c4af1c7..8da46dce 100644 --- a/mkdocs/livereload/__init__.py +++ b/mkdocs/livereload/__init__.py @@ -162,6 +162,17 @@ def watch(self, path: str, func: None = None, *, recursive: bool = True) -> None def callback(event): if event.is_directory: return + # Ignore hidden files and editor temporary/backup files: + # - .dotfiles (vim swap .foo.md.swp, .foo.md.swo, .foo.md.swn, etc.) + # - files ending with ~ (editor backup) + # - #files# matching Emacs auto-save pattern + name = os.path.basename(event.src_path) + if ( + name.startswith(".") + or name.endswith("~") + or (name.startswith("#") and name.endswith("#")) + ): + return log.debug(str(event)) with self._rebuild_cond: self._want_rebuild = True From 248cecdd1213af9160938a7610e8651e277e706a Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Tue, 28 Apr 2026 03:06:00 +0300 Subject: [PATCH 2/2] Add tests for livereload hidden/temp file filtering Add test cases for: - Dotfiles (.vim swap, .hidden) not triggering rebuild - Editor backup files (~ suffix) not triggering rebuild - Emacs auto-save files (#file#) not triggering rebuild Refs #2519 --- mkdocs/tests/livereload_tests.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/mkdocs/tests/livereload_tests.py b/mkdocs/tests/livereload_tests.py index 04594448..4f7d6cdf 100644 --- a/mkdocs/tests/livereload_tests.py +++ b/mkdocs/tests/livereload_tests.py @@ -619,6 +619,46 @@ def test_watches_through_relative_symlinks(self, origin_dir, site_dir): Path(origin_dir, "README.md").write_text("edited") self.assertTrue(started_building.wait(timeout=10)) + @tempdir({"foo.md": "original"}) + def test_ignores_dotfile_changes(self, docs_dir): + """Hidden files (starting with '.') should not trigger rebuild.""" + started_building = threading.Event() + with testing_server(docs_dir, started_building.set) as server: + server.watch(docs_dir) + time.sleep(0.01) + + # Vim swap file + Path(docs_dir, ".foo.md.swp").write_text("swap") + self.assertFalse(started_building.wait(timeout=0.5)) + + # Generic dotfile + Path(docs_dir, ".hidden").write_text("hidden") + self.assertFalse(started_building.wait(timeout=0.5)) + + @tempdir({"foo.md": "original"}) + def test_ignores_tilde_backup_files(self, docs_dir): + """Editor backup files (ending with '~') should not trigger rebuild.""" + started_building = threading.Event() + with testing_server(docs_dir, started_building.set) as server: + server.watch(docs_dir) + time.sleep(0.01) + + # Backup file created by editors + Path(docs_dir, "foo.md~").write_text("backup") + self.assertFalse(started_building.wait(timeout=0.5)) + + @tempdir({"foo.md": "original"}) + def test_ignores_emacs_autosave_files(self, docs_dir): + """Emacs auto-save files (#*#) should not trigger rebuild.""" + started_building = threading.Event() + with testing_server(docs_dir, started_building.set) as server: + server.watch(docs_dir) + time.sleep(0.01) + + # Emacs auto-save pattern + Path(docs_dir, "#foo.md#").write_text("autosave") + self.assertFalse(started_building.wait(timeout=0.5)) + @tempdir() def test_watch_with_broken_symlinks(self, docs_dir): Path(docs_dir, "subdir").mkdir()