Search files for pattern matches
(
&self,
query: &str,
max_results: usize,
)
| 132 | |
| 133 | /// Search files for pattern matches |
| 134 | async fn search_files( |
| 135 | &self, |
| 136 | query: &str, |
| 137 | max_results: usize, |
| 138 | ) -> anyhow::Result<Vec<FileMatch>> { |
| 139 | let root = self.config.root_path.clone(); |
| 140 | let max_file_size = self.config.max_file_size; |
| 141 | let include = self.config.include_patterns.clone(); |
| 142 | let exclude = self.config.exclude_patterns.clone(); |
| 143 | let case_insensitive = self.config.case_insensitive; |
| 144 | let context_lines = self.config.context_lines; |
| 145 | let query = query.to_string(); |
| 146 | |
| 147 | // Run search in blocking task |
| 148 | tokio::task::spawn_blocking(move || { |
| 149 | // Build regex pattern |
| 150 | let pattern = if case_insensitive { |
| 151 | format!("(?i){}", regex::escape(&query)) |
| 152 | } else { |
| 153 | regex::escape(&query) |
| 154 | }; |
| 155 | |
| 156 | let regex = Regex::new(&pattern)?; |
| 157 | |
| 158 | let mut file_matches = Vec::new(); |
| 159 | |
| 160 | let walker = WalkBuilder::new(&root) |
| 161 | .hidden(false) |
| 162 | .git_ignore(true) |
| 163 | .build(); |
| 164 | |
| 165 | for entry in walker { |
| 166 | let entry = entry.map_err(|e| anyhow::anyhow!("Walk error: {}", e))?; |
| 167 | let path = entry.path(); |
| 168 | |
| 169 | if !path.is_file() { |
| 170 | continue; |
| 171 | } |
| 172 | |
| 173 | let metadata = fs::metadata(path) |
| 174 | .map_err(|e| anyhow::anyhow!("Metadata error for {}: {}", path.display(), e))?; |
| 175 | |
| 176 | if metadata.len() > max_file_size as u64 { |
| 177 | continue; |
| 178 | } |
| 179 | |
| 180 | if !matches_patterns(path, &include, true) { |
| 181 | continue; |
| 182 | } |
| 183 | |
| 184 | if matches_patterns(path, &exclude, false) { |
| 185 | continue; |
| 186 | } |
| 187 | |
| 188 | let content = match fs::read_to_string(path) { |
| 189 | Ok(c) => c, |
| 190 | Err(_) => continue, // Skip binary files |
| 191 | }; |