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