matrixcode_core/tools/codegraph/
ignore.rs1use std::path::Path;
4
5pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &[
7 "target", "dist", "build", "out", "bin", "obj", ".output",
8 "node_modules", "vendor", "Pods", ".venv", "venv", "__pycache__",
9 ".cache", ".tmp", ".temp", "tmp", "temp",
10 ".idea", ".vscode", ".eclipse", ".project", ".classpath",
11 ".generated", "generated", ".codegraph",
12 "package-lock.json", "yarn.lock", "Cargo.lock", "pnpm-lock.yaml",
13 "coverage", ".nyc_output", "test-results", "logs",
14];
15
16pub const WATCH_EXTENSIONS: &[&str] = &[
18 "rs", "ts", "tsx", "js", "jsx", "mjs", "py", "go",
19 "java", "kt", "kts", "c", "cpp", "cc", "h", "hpp",
20 "rb", "php", "swift", "cs", "scala", "lua", "sh",
21];
22
23pub struct IgnoreMatcher {
25 patterns: Vec<String>,
26 negation_patterns: Vec<String>,
27}
28
29impl IgnoreMatcher {
30 pub fn load(project_path: &Path) -> Self {
32 let mut patterns = Vec::new();
33 let mut negation_patterns = Vec::new();
34
35 for p in DEFAULT_IGNORE_PATTERNS {
37 patterns.push(p.to_string());
38 }
39
40 let gitignore_path = project_path.join(".gitignore");
42 if gitignore_path.exists() {
43 if let Ok(content) = std::fs::read_to_string(&gitignore_path) {
44 for line in content.lines() {
45 let line = line.trim();
46 if line.is_empty() || line.starts_with('#') {
47 continue;
48 }
49 if let Some(stripped) = line.strip_prefix('!') {
50 negation_patterns.push(stripped.to_string());
51 } else {
52 patterns.push(line.to_string());
53 }
54 }
55 }
56 }
57
58 Self { patterns, negation_patterns }
59 }
60
61 pub fn should_ignore(&self, path: &Path, project_path: &Path) -> bool {
63 let path_str = path.to_string_lossy();
64 let relative_path = path.strip_prefix(project_path)
65 .unwrap_or(path)
66 .to_string_lossy();
67
68 for pattern in &self.negation_patterns {
70 if Self::matches_pattern(&relative_path, pattern) {
71 return false;
72 }
73 }
74
75 for pattern in &self.patterns {
77 if Self::matches_pattern(&relative_path, pattern) || path_str.contains(pattern) {
78 return true;
79 }
80 }
81
82 for component in path.components() {
84 if let std::path::Component::Normal(name) = component {
85 let name_str = name.to_string_lossy();
86 if name_str.starts_with('.')
87 && name_str != ".codegraph"
88 && !WATCH_EXTENSIONS.contains(&name_str.split('.').next_back().unwrap_or("")) {
89 return true;
90 }
91 }
92 }
93
94 false
95 }
96
97 fn matches_pattern(path: &str, pattern: &str) -> bool {
99 let pattern = pattern.trim_start_matches('/');
100
101 if let Some(dir_pattern) = pattern.strip_suffix('/') {
103 return path.contains(dir_pattern) || path.starts_with(dir_pattern);
104 }
105
106 if pattern.contains('*') {
108 let parts = pattern.split('*').collect::<Vec<_>>();
109 if parts.len() == 2 {
110 let prefix = parts[0];
111 let suffix = parts[1];
112 return (prefix.is_empty() || path.starts_with(prefix))
113 && (suffix.is_empty() || path.ends_with(suffix));
114 }
115 }
116
117 path == pattern || path.contains(pattern) || path.starts_with(&format!("{}/", pattern))
119 }
120}