mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-01 10:14:59 +00:00
refactor: create FileWatcher class to encapsulate file watching logic
This commit is contained in:
parent
af195a610c
commit
094d2e12a4
2 changed files with 96 additions and 99 deletions
36
aider/io.py
36
aider/io.py
|
@ -392,28 +392,10 @@ class InputOutput:
|
||||||
):
|
):
|
||||||
self.rule()
|
self.rule()
|
||||||
|
|
||||||
# ai refactor this chunk ...
|
# Initialize and start the file watcher
|
||||||
self.changed_files = None
|
self.file_watcher = FileWatcher(root, encoding=self.encoding)
|
||||||
stop_event = threading.Event()
|
gitignore = [str(Path(root) / ".gitignore")]
|
||||||
|
self.file_watcher.start(gitignores=gitignore)
|
||||||
def watch_files():
|
|
||||||
try:
|
|
||||||
gitignore = [str(Path(root) / ".gitignore")]
|
|
||||||
for changed in watch_source_files(
|
|
||||||
root, stop_event=stop_event, gitignores=gitignore, encoding=self.encoding
|
|
||||||
):
|
|
||||||
if changed:
|
|
||||||
self.changed_files = changed
|
|
||||||
self.interrupt_input()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
self.tool_error(f"File watcher error: {e}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
# Start the watcher thread
|
|
||||||
watcher = threading.Thread(target=watch_files, daemon=True)
|
|
||||||
watcher.start()
|
|
||||||
# ... to here
|
|
||||||
|
|
||||||
rel_fnames = list(rel_fnames)
|
rel_fnames = list(rel_fnames)
|
||||||
show = ""
|
show = ""
|
||||||
|
@ -476,9 +458,9 @@ class InputOutput:
|
||||||
line = input(show)
|
line = input(show)
|
||||||
|
|
||||||
# Check if we were interrupted by a file change
|
# Check if we were interrupted by a file change
|
||||||
if self.changed_files:
|
if changes := self.file_watcher.get_changes():
|
||||||
res = process_file_changes(self.changed_files)
|
res = process_file_changes(changes)
|
||||||
self.changed_files = None
|
self.file_watcher.changed_files = None
|
||||||
return res
|
return res
|
||||||
|
|
||||||
except EOFError:
|
except EOFError:
|
||||||
|
@ -489,9 +471,7 @@ class InputOutput:
|
||||||
self.tool_error(str(err))
|
self.tool_error(str(err))
|
||||||
return ""
|
return ""
|
||||||
finally:
|
finally:
|
||||||
# ai: we'll need to adjust this too
|
self.file_watcher.stop()
|
||||||
stop_event.set()
|
|
||||||
watcher.join() # Thread should exit quickly due to stop_event
|
|
||||||
|
|
||||||
if line.strip("\r\n") and not multiline_input:
|
if line.strip("\r\n") and not multiline_input:
|
||||||
stripped = line.strip("\r\n")
|
stripped = line.strip("\r\n")
|
||||||
|
|
159
aider/watch.py
159
aider/watch.py
|
@ -61,83 +61,100 @@ def load_gitignores(gitignore_paths: list[Path]) -> Optional[PathSpec]:
|
||||||
return PathSpec.from_lines(GitWildMatchPattern, patterns) if patterns else None
|
return PathSpec.from_lines(GitWildMatchPattern, patterns) if patterns else None
|
||||||
|
|
||||||
|
|
||||||
# ai: make a class for this that includes the code from io!
|
class FileWatcher:
|
||||||
def watch_source_files(
|
"""Watches source files for changes and AI comments"""
|
||||||
directory: str,
|
|
||||||
stop_event=None,
|
def __init__(self, directory: str, encoding="utf-8"):
|
||||||
gitignores: list[str] = None,
|
self.directory = directory
|
||||||
ignore_func=None,
|
self.encoding = encoding
|
||||||
encoding="utf-8",
|
self.root = Path(directory)
|
||||||
) -> Set[str]:
|
self.root_abs = self.root.absolute()
|
||||||
"""
|
self.stop_event = None
|
||||||
Watch for changes to source files in the given directory and its subdirectories.
|
self.watcher_thread = None
|
||||||
Returns a set of changed file paths whenever changes are detected.
|
self.changed_files = None
|
||||||
|
|
||||||
Args:
|
def create_filter_func(self, gitignore_spec, ignore_func):
|
||||||
directory: Root directory to watch
|
"""Creates a filter function for the file watcher"""
|
||||||
stop_event: Threading event to signal when to stop watching
|
def filter_func(change_type, path):
|
||||||
gitignores: List of paths to .gitignore files (optional)
|
path_obj = Path(path)
|
||||||
ignore_func: Optional function that takes a path (relative to watched directory)
|
path_abs = path_obj.absolute()
|
||||||
and returns True if it should be ignored
|
|
||||||
"""
|
|
||||||
root = Path(directory)
|
|
||||||
|
|
||||||
if VERBOSE:
|
if not path_abs.is_relative_to(self.root_abs):
|
||||||
dump(root)
|
return False
|
||||||
|
|
||||||
gitignore_paths = [Path(g) for g in gitignores] if gitignores else []
|
rel_path = path_abs.relative_to(self.root_abs)
|
||||||
gitignore_spec = load_gitignores(gitignore_paths)
|
|
||||||
root_abs = root.absolute()
|
|
||||||
|
|
||||||
# Create a filter function that only accepts source files and respects gitignore
|
|
||||||
def filter_func(change_type, path):
|
|
||||||
path_obj = Path(path)
|
|
||||||
path_abs = path_obj.absolute()
|
|
||||||
|
|
||||||
if not path_abs.is_relative_to(root_abs):
|
|
||||||
return False
|
|
||||||
|
|
||||||
rel_path = path_abs.relative_to(root_abs)
|
|
||||||
if VERBOSE:
|
|
||||||
dump(rel_path)
|
|
||||||
|
|
||||||
if gitignore_spec and gitignore_spec.match_file(str(rel_path)):
|
|
||||||
return False
|
|
||||||
if ignore_func and ignore_func(rel_path):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not is_source_file(path_obj):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if VERBOSE:
|
|
||||||
dump("ok", rel_path)
|
|
||||||
|
|
||||||
# Check if file contains AI markers
|
|
||||||
try:
|
|
||||||
with open(path_abs, encoding=encoding, errors="ignore") as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
res = bool(re.search(r"(?:#|//) *ai\b", content, re.IGNORECASE))
|
|
||||||
if VERBOSE:
|
|
||||||
dump(res)
|
|
||||||
return res
|
|
||||||
except (IOError, UnicodeDecodeError) as err:
|
|
||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
dump(err)
|
dump(rel_path)
|
||||||
return False
|
|
||||||
|
|
||||||
# Watch the directory for changes
|
if gitignore_spec and gitignore_spec.match_file(str(rel_path)):
|
||||||
for changes in watch(root, watch_filter=filter_func, stop_event=stop_event):
|
return False
|
||||||
# Convert the changes to a set of unique file paths
|
if ignore_func and ignore_func(rel_path):
|
||||||
changed_files = {str(Path(change[1])) for change in changes}
|
return False
|
||||||
result = {}
|
|
||||||
for file in changed_files:
|
|
||||||
if comments := get_ai_comment(file, encoding=encoding):
|
|
||||||
result[file] = comments
|
|
||||||
|
|
||||||
if VERBOSE:
|
if not is_source_file(path_obj):
|
||||||
dump(result)
|
return False
|
||||||
yield result
|
|
||||||
|
if VERBOSE:
|
||||||
|
dump("ok", rel_path)
|
||||||
|
|
||||||
|
# Check if file contains AI markers
|
||||||
|
try:
|
||||||
|
with open(path_abs, encoding=self.encoding, errors="ignore") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
res = bool(re.search(r"(?:#|//) *ai\b", content, re.IGNORECASE))
|
||||||
|
if VERBOSE:
|
||||||
|
dump(res)
|
||||||
|
return res
|
||||||
|
except (IOError, UnicodeDecodeError) as err:
|
||||||
|
if VERBOSE:
|
||||||
|
dump(err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return filter_func
|
||||||
|
|
||||||
|
def start(self, gitignores: list[str] = None, ignore_func=None):
|
||||||
|
"""Start watching for file changes"""
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
|
||||||
|
gitignore_paths = [Path(g) for g in gitignores] if gitignores else []
|
||||||
|
gitignore_spec = load_gitignores(gitignore_paths)
|
||||||
|
filter_func = self.create_filter_func(gitignore_spec, ignore_func)
|
||||||
|
|
||||||
|
def watch_files():
|
||||||
|
try:
|
||||||
|
for changes in watch(self.root, watch_filter=filter_func, stop_event=self.stop_event):
|
||||||
|
changed_files = {str(Path(change[1])) for change in changes}
|
||||||
|
result = {}
|
||||||
|
for file in changed_files:
|
||||||
|
if comments := get_ai_comment(file, encoding=self.encoding):
|
||||||
|
result[file] = comments
|
||||||
|
|
||||||
|
if VERBOSE:
|
||||||
|
dump(result)
|
||||||
|
if result:
|
||||||
|
self.changed_files = result
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
if VERBOSE:
|
||||||
|
dump(f"File watcher error: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
self.watcher_thread = threading.Thread(target=watch_files, daemon=True)
|
||||||
|
self.watcher_thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stop watching for file changes"""
|
||||||
|
if self.stop_event:
|
||||||
|
self.stop_event.set()
|
||||||
|
if self.watcher_thread:
|
||||||
|
self.watcher_thread.join()
|
||||||
|
self.watcher_thread = None
|
||||||
|
self.stop_event = None
|
||||||
|
|
||||||
|
def get_changes(self):
|
||||||
|
"""Get any detected file changes"""
|
||||||
|
return self.changed_files
|
||||||
|
|
||||||
|
|
||||||
def get_ai_comment(filepath, encoding="utf-8"):
|
def get_ai_comment(filepath, encoding="utf-8"):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue