From b985a8d47ae4ec5e6743d068b8c18cfd195d2f72 Mon Sep 17 00:00:00 2001 From: "Paul Gauthier (aider)" Date: Fri, 25 Oct 2024 12:43:31 -0700 Subject: [PATCH] feat: add clean shutdown for file watcher thread --- aider/io.py | 30 +++++++++++++++++++++++++++++- aider/watch.py | 5 +++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/aider/io.py b/aider/io.py index 4db74d378..b5651f73b 100644 --- a/aider/io.py +++ b/aider/io.py @@ -1,5 +1,6 @@ import base64 import os +import threading from collections import defaultdict from dataclasses import dataclass from datetime import datetime @@ -360,7 +361,27 @@ class InputOutput: ): self.rule() - rel_fnames = list(rel_fnames) + # Add a variable to store changed files and create stop event + self.changed_files = None + stop_event = threading.Event() + + # Define the watcher thread function + def watch_files(): + try: + for changed in watch_source_files(root, stop_event=stop_event): + if changed: + self.changed_files = list(changed)[0] # Take the first changed file + self.interrupt_input() + break + except Exception as e: + self.tool_error(f"File watcher error: {e}") + + # Start the watcher thread + watcher = threading.Thread(target=watch_files, daemon=True) + watcher.start() + + try: + rel_fnames = list(rel_fnames) show = "" if rel_fnames: rel_read_only_fnames = [ @@ -437,6 +458,13 @@ class InputOutput: print() self.user_input(inp) return inp + + finally: + # Clean up the watcher thread + stop_event.set() + watcher.join(timeout=1.0) # Wait up to 1 second for thread to finish + if watcher.is_alive(): + self.tool_warning("Warning: File watcher thread did not shut down cleanly") def add_to_input_history(self, inp): if not self.input_history_file: diff --git a/aider/watch.py b/aider/watch.py index 762840f31..071871be2 100644 --- a/aider/watch.py +++ b/aider/watch.py @@ -58,13 +58,14 @@ def load_gitignores(gitignore_paths: list[Path]) -> Optional[PathSpec]: return PathSpec.from_lines(GitWildMatchPattern, patterns) if patterns else None -def watch_source_files(directory: str, gitignores: list[str] = None, ignore_func=None) -> Set[str]: +def watch_source_files(directory: str, stop_event=None, gitignores: list[str] = None, ignore_func=None) -> Set[str]: """ Watch for changes to source files in the given directory and its subdirectories. Returns a set of changed file paths whenever changes are detected. Args: directory: Root directory to watch + stop_event: Threading event to signal when to stop watching gitignores: List of paths to .gitignore files (optional) ignore_func: Optional function that takes a path (relative to watched directory) and returns True if it should be ignored @@ -92,7 +93,7 @@ def watch_source_files(directory: str, gitignores: list[str] = None, ignore_func return is_source_file(path_obj) # Watch the directory for changes - for changes in watch(root, watch_filter=filter_func): + for changes in watch(root, watch_filter=filter_func, stop_event=stop_event): # Convert the changes to a set of unique file paths changed_files = {str(Path(change[1])) for change in changes} yield changed_files