use std::collections::HashSet;
use std::path::{Path, PathBuf};
pub const MCP_DEFAULT_DEBOUNCE_MS: u64 = 2000;
pub const MAX_DEBOUNCE_MS: u64 = 10000;
pub const MIN_DEBOUNCE_MS: u64 = 500;
pub const DEFAULT_ADDITIONAL_DELAY_MS: u64 = 1000;
pub const MAX_ADDITIONAL_DELAY_MS: u64 = 5000;
pub const WATCH_DEFAULT_DEBOUNCE_SECS: u64 = 2;
pub const WATCH_MAX_DEBOUNCE_SECS: u64 = 30;
pub const WATCH_MIN_DEBOUNCE_SECS: u64 = 1;
pub struct IgnorePatterns {
gitignore_patterns: HashSet<String>,
noindex_patterns: HashSet<String>,
working_directory: PathBuf,
}
impl IgnorePatterns {
pub fn new(working_directory: PathBuf) -> Self {
let mut ignore_patterns = Self {
gitignore_patterns: HashSet::new(),
noindex_patterns: HashSet::new(),
working_directory,
};
ignore_patterns.load_gitignore();
ignore_patterns.load_noindex();
ignore_patterns
}
fn load_gitignore(&mut self) {
let gitignore_path = self.working_directory.join(".gitignore");
if let Ok(content) = std::fs::read_to_string(&gitignore_path) {
for line in content.lines() {
let line = line.trim();
if !line.is_empty() && !line.starts_with('#') {
let pattern = line.trim_start_matches('/').trim_end_matches('/');
self.gitignore_patterns.insert(pattern.to_string());
}
}
}
}
fn load_noindex(&mut self) {
let noindex_path = self.working_directory.join(".noindex");
if let Ok(content) = std::fs::read_to_string(&noindex_path) {
for line in content.lines() {
let line = line.trim();
if !line.is_empty() && !line.starts_with('#') {
let pattern = line.trim_start_matches('/').trim_end_matches('/');
self.noindex_patterns.insert(pattern.to_string());
}
}
}
}
pub fn should_ignore_path(&self, path: &Path) -> bool {
let path_str = path.to_string_lossy();
let relative_path = if let Ok(rel_path) = path.strip_prefix(&self.working_directory) {
rel_path.to_string_lossy().to_string()
} else {
path_str.to_string()
};
if self.matches_patterns(&relative_path, &self.gitignore_patterns) {
return true;
}
if self.matches_patterns(&relative_path, &self.noindex_patterns) {
return true;
}
false
}
fn matches_patterns(&self, path: &str, patterns: &HashSet<String>) -> bool {
for pattern in patterns {
if self.matches_pattern(path, pattern) {
return true;
}
}
false
}
fn matches_pattern(&self, path: &str, pattern: &str) -> bool {
if path == pattern {
return true;
}
if pattern.ends_with('/') {
let dir_pattern = pattern.trim_end_matches('/');
if path.starts_with(&format!("{}/", dir_pattern)) || path == dir_pattern {
return true;
}
}
if path.contains(pattern) {
return true;
}
if pattern.contains('*') {
return self.matches_wildcard(path, pattern);
}
if let Some(ext) = pattern.strip_prefix("*.") {
if path.ends_with(&format!(".{}", ext)) {
return true;
}
}
false
}
fn matches_wildcard(&self, path: &str, pattern: &str) -> bool {
if pattern == "*" {
return true;
}
if let Some(star_pos) = pattern.find('*') {
let before = &pattern[..star_pos];
let after = &pattern[star_pos + 1..];
if path.starts_with(before) && path.ends_with(after) {
return true;
}
}
false
}
pub fn reload(&mut self) {
self.gitignore_patterns.clear();
self.noindex_patterns.clear();
self.load_gitignore();
self.load_noindex();
}
}