ratatui_toolkit/services/repo_watcher/
mod.rs

1//! Repository watcher for detecting git and working tree changes.
2//!
3//! Combines the git watcher (for `.git` state changes) with a file watcher
4//! (for working tree edits) and provides a cached list of modified files
5//! via `git status --porcelain`.
6//!
7//! # Example
8//!
9//! ```no_run
10//! use ratatui_toolkit::services::repo_watcher::RepoWatcher;
11//! use std::path::Path;
12//!
13//! let mut watcher = RepoWatcher::new().unwrap();
14//! watcher.watch(Path::new("/path/to/repo")).unwrap();
15//!
16//! // In your event loop:
17//! if watcher.check_for_changes() {
18//!     let changes = watcher.get_change_set();
19//!     for path in changes.all_paths() {
20//!         println!("Changed: {}", path.display());
21//!     }
22//! }
23//! ```
24
25mod constructors;
26mod helpers;
27mod methods;
28mod traits;
29
30use std::path::PathBuf;
31
32use crate::services::file_watcher::{FileWatcher, WatchConfig, WatchMode};
33use crate::services::git_watcher::{GitWatchConfig, GitWatcher};
34
35pub use constructors::*;
36
37/// Configuration for the repository watcher.
38#[derive(Debug, Clone)]
39pub struct RepoWatchConfig {
40    /// Configuration for the git watcher.
41    pub git: GitWatchConfig,
42    /// Configuration for the file watcher.
43    pub files: WatchConfig,
44    /// Whether to include untracked files in the change set.
45    pub include_untracked: bool,
46}
47
48impl Default for RepoWatchConfig {
49    fn default() -> Self {
50        Self {
51            git: GitWatchConfig::default(),
52            files: WatchConfig {
53                mode: WatchMode::Recursive,
54                debounce_ms: 200,
55            },
56            include_untracked: true,
57        }
58    }
59}
60
61impl RepoWatchConfig {
62    /// Create a new config with default settings.
63    pub fn new() -> Self {
64        Self::default()
65    }
66
67    /// Set the git watcher configuration.
68    pub fn git_config(mut self, config: GitWatchConfig) -> Self {
69        self.git = config;
70        self
71    }
72
73    /// Set the file watcher configuration.
74    pub fn file_config(mut self, config: WatchConfig) -> Self {
75        self.files = config;
76        self
77    }
78
79    /// Control whether untracked files are included in the change set.
80    pub fn include_untracked(mut self, include: bool) -> Self {
81        self.include_untracked = include;
82        self
83    }
84}
85
86/// Status for a file reported by git.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum GitFileStatus {
89    /// File was added or copied.
90    Added,
91    /// File was modified.
92    Modified,
93    /// File was deleted.
94    Deleted,
95    /// File was renamed.
96    Renamed,
97    /// File is untracked.
98    Untracked,
99}
100
101/// Collection of git changes discovered for a repository.
102///
103/// All paths are repository-relative.
104#[derive(Debug, Clone, Default, PartialEq, Eq)]
105pub struct GitChangeSet {
106    /// Added files.
107    pub added: Vec<PathBuf>,
108    /// Modified files.
109    pub modified: Vec<PathBuf>,
110    /// Deleted files.
111    pub deleted: Vec<PathBuf>,
112    /// Renamed files (new paths).
113    pub renamed: Vec<PathBuf>,
114    /// Untracked files.
115    pub untracked: Vec<PathBuf>,
116}
117
118impl GitChangeSet {
119    /// Returns true if no changes are present.
120    pub fn is_empty(&self) -> bool {
121        self.added.is_empty()
122            && self.modified.is_empty()
123            && self.deleted.is_empty()
124            && self.renamed.is_empty()
125            && self.untracked.is_empty()
126    }
127
128    /// Returns all changed paths in a single list.
129    pub fn all_paths(&self) -> Vec<PathBuf> {
130        let mut paths = Vec::new();
131        paths.extend(self.added.iter().cloned());
132        paths.extend(self.modified.iter().cloned());
133        paths.extend(self.deleted.iter().cloned());
134        paths.extend(self.renamed.iter().cloned());
135        paths.extend(self.untracked.iter().cloned());
136        paths
137    }
138}
139
140/// A watcher that combines git and working tree signals.
141pub struct RepoWatcher {
142    /// Underlying git watcher for `.git` events.
143    pub(crate) git_watcher: GitWatcher,
144    /// Underlying file watcher for working tree events.
145    pub(crate) file_watcher: FileWatcher,
146    /// Configuration for this watcher.
147    pub(crate) config: RepoWatchConfig,
148    /// Path to the repository root being watched.
149    pub(crate) repo_path: Option<PathBuf>,
150    /// Cached change set from the last update.
151    pub(crate) change_set: GitChangeSet,
152    /// Whether a refresh is pending.
153    pub(crate) has_pending_changes: bool,
154}